diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..e4e8e72ea1464b0cd691eb905e0fb6be457be1f8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.github +.git +*.md +.vscode +.gitignore +Makefile +docs \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..bece06dbc8276aef137f8a16080e08579c99afdd --- /dev/null +++ b/.env.example @@ -0,0 +1,67 @@ +# 端口号 +# PORT=3000 +# 前端基础URL +# FRONTEND_BASE_URL=https://your-frontend-url.com + + +# 调试相关配置 +# 启用pprof +# ENABLE_PPROF=true + +# 数据库相关配置 +# 数据库连接字符串 +# SQL_DSN=user:password@tcp(127.0.0.1:3306)/dbname?parseTime=true +# 日志数据库连接字符串 +# LOG_SQL_DSN=user:password@tcp(127.0.0.1:3306)/logdb?parseTime=true +# SQLite数据库路径 +# SQLITE_PATH=/path/to/sqlite.db +# 数据库最大空闲连接数 +# SQL_MAX_IDLE_CONNS=100 +# 数据库最大打开连接数 +# SQL_MAX_OPEN_CONNS=1000 +# 数据库连接最大生命周期(秒) +# SQL_MAX_LIFETIME=60 + + +# 缓存相关配置 +# Redis连接字符串 +# REDIS_CONN_STRING=redis://user:password@localhost:6379/0 +# 同步频率(单位:秒) +# SYNC_FREQUENCY=60 +# 内存缓存启用 +# MEMORY_CACHE_ENABLED=true +# 渠道更新频率(单位:秒) +# CHANNEL_UPDATE_FREQUENCY=30 +# 批量更新启用 +# BATCH_UPDATE_ENABLED=true +# 批量更新间隔(单位:秒) +# BATCH_UPDATE_INTERVAL=5 + +# 任务和功能配置 +# 更新任务启用 +# UPDATE_TASK=true + + +# 会话密钥 +# SESSION_SECRET=random_string + +# 其他配置 +# 渠道测试频率(单位:秒) +# CHANNEL_TEST_FREQUENCY=10 +# 生成默认token +# GENERATE_DEFAULT_TOKEN=false +# Cohere 安全设置 +# COHERE_SAFETY_SETTING=NONE +# 是否统计图片token +# GET_MEDIA_TOKEN=true +# 是否在非流(stream=false)情况下统计图片token +# GET_MEDIA_TOKEN_NOT_STREAM=true +# 设置 Dify 渠道是否输出工作流和节点信息到客户端 +# DIFY_DEBUG=true +# 设置流式一次回复的超时时间 +# STREAMING_TIMEOUT=90 + + +# 节点类型 +# 如果是主节点则为master +# NODE_TYPE=master diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..0de03d4fbd30a1fea06cade91ce55f8710f80d3c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +web/bun.lockb filter=lfs diff=lfs merge=lfs -text +web/public/ratio.png filter=lfs diff=lfs merge=lfs -text diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..6d4332980184f54f263d10828fd7718edeb7dd26 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +ko_fi: isplexus diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000000000000000000000000000000..6448bf3af7f615c3244eec74a57d26270ae10461 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: 报告问题 +about: 使用简练详细的语言描述你遇到的问题 +title: '' +labels: bug +assignees: '' + +--- + +**例行检查** + +[//]: # (方框内删除已有的空格,填 x 号) + +**必选** ++ [ ] 我已确认目前没有类似 issue ++ [ ] 我已确认我已升级到最新版本 ++ [ ] 我已完整查看过项目 README,尤其是常见问题部分 ++ [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈 ++ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 issue 可能会被无视或直接关闭** + +**可选** ++ [ ] 我有能力解决这个问题并且提交 pull request + +**问题描述** + +**复现步骤** + +**预期结果** + +**相关截图** +如果没有的话,请删除此节。 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000000000000000000000000000000000..b1796750c19235c8f2089c6eba87c2a453626e5b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Discussions + url: https://github.com/Veloera/Veloera/discussions + about: 如果这不是一个问题/功能请求, 请在 Discussions 提出 \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000000000000000000000000000000..375c8ff63797a481c53dc4398c9b8e720d7bb7e9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,26 @@ +--- +name: 功能请求 +about: 使用简练详细的语言描述希望加入的新功能 +title: '' +labels: enhancement +assignees: '' + +--- + +**例行检查** + +[//]: # (方框内删除已有的空格,填 x 号) + +**必选** ++ [ ] 我已确认目前没有类似 issue ++ [ ] 我已确认我已升级到最新版本 ++ [ ] 我已完整查看过项目 README,已确定现有版本无法满足需求 ++ [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈 ++ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 issue 可能会被无视或直接关闭** + +**可选** ++ [ ] 我有能力解决这个问题并且提交 pull request + +**功能描述** + +**应用场景** diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 0000000000000000000000000000000000000000..3ec5e3880183ed8b2d418eef3da1e6677cbdb173 --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,59 @@ +name: Generate Release Notes + +on: + push: + tags: + - '*' # 每次有新的tag推送到远程仓库时触发 + workflow_dispatch: # 允许手动触发 + +env: + # AI 配置,确保在 GitHub Secrets 中设置 + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + OPENAI_API_BASE_URL: ${{ secrets.OPENAI_API_BASE_URL }} + OPENAI_MODEL: gpt-4.1-mini # 可以根据需要修改模型 + +jobs: + generate_and_push: + runs-on: ubuntu-latest + + permissions: + contents: write # 允许写入文件到仓库 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # Fetch all tags and history for accurate git log + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install Python dependencies + run: pip install openai + + - name: Run release notes generator script + id: generate_notes # 添加一个id以便引用脚本的输出 + run: python scripts/release_notes_generator.py + env: + GITHUB_REPOSITORY: ${{ github.repository }} # 传递仓库信息给Python脚本 + + - name: Commit and push release notes + # 仅当生成了文件时才执行 + if: success() && steps.generate_notes.outputs.release_notes_file != '' + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # 获取脚本输出的tag名称和文件名 + TAG_NAME="${{ steps.generate_notes.outputs.tag_name }}" + RELEASE_FILE="${{ steps.generate_notes.outputs.release_notes_file }}" + + echo "Committing release notes for tag: $TAG_NAME" + echo "File path: $RELEASE_FILE" + + git add "$RELEASE_FILE" + git commit -m "docs(release): Add release notes for $TAG_NAME" || echo "No changes to commit" + git push origin HEAD:main diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000000000000000000000000000000000..92948c61dff4f9374e3b8c6f970bd76f19dc773b --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,92 @@ +name: Publish Docker images + +on: + push: + tags: + - '*' + workflow_dispatch: + inputs: + reason: + description: 'Reason for manual trigger' + required: false + +jobs: + push_to_registries: + name: Build and push Docker images + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Save version info + run: | + git describe --tags > VERSION + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + # Use buildx driver with multiple concurrent builders + driver-opts: | + network=host + # Enable BuildKit cache + buildkitd-flags: --debug + + - name: Cache Docker layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Login to Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ghcr.io/${{ github.repository }} + tags: | + type=ref,event=tag + type=raw,value=latest,enable=${{ !endsWith(github.ref, '-alpha') && !endsWith(github.ref, '-beta') }} + type=raw,value=alpha,enable=${{ endsWith(github.ref, '-alpha') }} + type=raw,value=beta,enable=${{ endsWith(github.ref, '-beta') }} + + - name: Build and push Docker images + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + # Use cache for faster builds + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max + # Optimize build performance + build-args: | + BUILDKIT_INLINE_CACHE=1 + # Parallel processing + builder: ${{ steps.buildx.outputs.name }} + # Set output to suppress verbose logs + outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=Docker image for ${{ github.repository }} + + # Temp fix for https://github.com/docker/build-push-action/issues/252 + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000000000000000000000000000000000..4132ed9d4c12f93f64898e50383f411b883f9bd6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,202 @@ +name: Create Release + +permissions: + contents: write + +on: + push: + tags: + - '*' + workflow_dispatch: + inputs: + tag_name: + description: 'Tag name for release (e.g. v1.0.0)' + required: true + type: string + +jobs: + build-linux: + name: Build Linux Version + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 20 + cache: 'pnpm' + cache-dependency-path: 'web/pnpm-lock.yaml' + + - name: Build Frontend + env: + CI: "" + run: | + cd web + pnpm install + VITE_REACT_APP_VERSION=$(git describe --tags) pnpm run build + cd .. + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + cache-dependency-path: '**/go.sum' + + - name: Build Backend (amd64) + run: | + go build -ldflags "-s -w -X 'veloera/common.Version=$(git describe --tags)' -extldflags '-static'" -o veloera-linux + + - name: Build Backend (arm64) + run: | + sudo apt-get update + DEBIAN_FRONTEND=noninteractive sudo apt-get install -y gcc-aarch64-linux-gnu + CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X 'veloera/common.Version=$(git describe --tags)' -extldflags '-static'" -o veloera-linux-arm64 + + - name: Upload Linux builds + uses: actions/upload-artifact@v4 + with: + name: linux-builds + path: | + veloera-linux + veloera-linux-arm64 + retention-days: 1 + + build-windows: + name: Build Windows Version + runs-on: windows-latest + defaults: + run: + shell: bash + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 20 + cache: 'pnpm' + cache-dependency-path: 'web/pnpm-lock.yaml' + + - name: Build Frontend + env: + CI: "" + run: | + cd web + pnpm install + VITE_REACT_APP_VERSION=$(git describe --tags) pnpm run build + cd .. + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + cache-dependency-path: '**/go.sum' + + - name: Build Backend + run: | + go build -ldflags "-s -w -X 'veloera/common.Version=$(git describe --tags)'" -o veloera-windows.exe + + - name: Upload Windows builds + uses: actions/upload-artifact@v4 + with: + name: windows-builds + path: veloera-windows.exe + retention-days: 1 + + build-macos: + name: Build macOS Version + runs-on: macos-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 20 + cache: 'pnpm' + cache-dependency-path: 'web/pnpm-lock.yaml' + + - name: Build Frontend + env: + CI: "" + run: | + cd web + pnpm install + VITE_REACT_APP_VERSION=$(git describe --tags) pnpm run build + cd .. + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + cache-dependency-path: '**/go.sum' + + - name: Build Backend + run: | + go build -ldflags "-X 'veloera/common.Version=$(git describe --tags)'" -o veloera-macos + + - name: Upload macOS builds + uses: actions/upload-artifact@v4 + with: + name: macos-builds + path: veloera-macos + retention-days: 1 + + create-release: + name: Create and Publish Release + needs: [build-linux, build-windows, build-macos] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Download all build artifacts + uses: actions/download-artifact@v4 + + - name: Display downloaded files + run: ls -R + + - name: Create Release + id: create_release + uses: softprops/action-gh-release@v1 + with: + files: | + linux-builds/veloera-linux + linux-builds/veloera-linux-arm64 + windows-builds/veloera-windows.exe + macos-builds/veloera-macos + draft: false + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4c26c7d604d343d16c768d084e9b687193f5c137 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +.idea +.vscode +upload +*.exe +*.db +build +*.db-journal +logs +web/dist +.env +veloera +one-api +.DS_Store +tiktoken_cache +.aider* +.history/ +.kiro/ +.venv \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..28d0032e6964f5d327955f615e1f0ea63870a19f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2997 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and to [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). + +## Unreleased changes + +### Documentation +- :robot: changelog file generated [`d0c8365b`](https://github.com/Veloera/Veloera/commit/d0c8365b) +- :robot: changelog file generated [`870be6ef`](https://github.com/Veloera/Veloera/commit/870be6ef) +- :robot: changelog file generated [`88aafb02`](https://github.com/Veloera/Veloera/commit/88aafb02) +- :robot: changelog file generated [`46629169`](https://github.com/Veloera/Veloera/commit/46629169) +- :robot: changelog file generated [`474c94f2`](https://github.com/Veloera/Veloera/commit/474c94f2) +- :robot: changelog file generated [`edcead5f`](https://github.com/Veloera/Veloera/commit/edcead5f) +- :robot: changelog file generated [`bd405a7a`](https://github.com/Veloera/Veloera/commit/bd405a7a) +- :robot: changelog file generated [`48f4877a`](https://github.com/Veloera/Veloera/commit/48f4877a) +- :robot: changelog file generated [`ebdd544d`](https://github.com/Veloera/Veloera/commit/ebdd544d) +- :robot: changelog file generated [`8bfb23e3`](https://github.com/Veloera/Veloera/commit/8bfb23e3) +- :robot: changelog file generated [`bbc8bfbd`](https://github.com/Veloera/Veloera/commit/bbc8bfbd) + +### Chore +- add copyright header to most source files [`ffc8c43a`](https://github.com/Veloera/Veloera/commit/ffc8c43a) +- add script to update copyright headers in web files [`111a5633`](https://github.com/Veloera/Veloera/commit/111a5633) +- add script to update copyright headers in Go files [`0d843243`](https://github.com/Veloera/Veloera/commit/0d843243) +- delete a useless bin file [`f1337203`](https://github.com/Veloera/Veloera/commit/f1337203) + +### Other +- Merge branch 'main' of https://github.com/Veloera/Veloera [`1deb6e68`](https://github.com/Veloera/Veloera/commit/1deb6e68) + +## v0.3.29.1 (2025-07-07) + +### Chore +- update dependencies and upgrade @visactor packages to latest versions [`077ef9a3`](https://github.com/Veloera/Veloera/commit/077ef9a3) + +## v0.3.29 (2025-07-07) + +### Feature +- add PR merge readiness check workflow [`9ac574b2`](https://github.com/Veloera/Veloera/commit/9ac574b2) +- add exemption settings (#123) [`cc8a497a`](https://github.com/Veloera/Veloera/commit/cc8a497a) + +### Bug Fixes +- fix submit when using key list mode by filtering empty keys and updating input key [`afade86f`](https://github.com/Veloera/Veloera/commit/afade86f) +- upgrade @visactor/react-vchart from 1.8.11 to 1.13.11 (#125) [`29f7c060`](https://github.com/Veloera/Veloera/commit/29f7c060) +- upgrade @visactor/vchart from 1.8.11 to 1.13.11 (#124) [`0e276ca2`](https://github.com/Veloera/Veloera/commit/0e276ca2) +- add custom endpoint support to aws bedrock adapter (#126) [`00b2b0b1`](https://github.com/Veloera/Veloera/commit/00b2b0b1) +- add Redis null check in redemption Redeem function (#121) [`0d9354e8`](https://github.com/Veloera/Veloera/commit/0d9354e8) + +### Continuous Integration +- remove merge readiness check workflow [`cae72d2a`](https://github.com/Veloera/Veloera/commit/cae72d2a) +- remove comment from GITHUB_TOKEN line in merge readiness check workflow [`37424cf3`](https://github.com/Veloera/Veloera/commit/37424cf3) + +### Documentation +- :robot: changelog file generated [`95570017`](https://github.com/Veloera/Veloera/commit/95570017) + +### Chore +- remove add_responses_method.sh script [`52be55d6`](https://github.com/Veloera/Veloera/commit/52be55d6) + +### Other +- Merge branch 'main' of https://github.com/Veloera/Veloera [`e72425ef`](https://github.com/Veloera/Veloera/commit/e72425ef) + +## v0.3.28.1 (2025-06-28) + +### Feature +- add support for GitHub channel and model listing (#113) [`55a54f4d`](https://github.com/Veloera/Veloera/commit/55a54f4d) + +## v0.3.28 (2025-06-28) + +### Feature +- implement error logging (#112) [`7e55b846`](https://github.com/Veloera/Veloera/commit/7e55b846) + +### Documentation +- :robot: changelog file generated [`1af99f86`](https://github.com/Veloera/Veloera/commit/1af99f86) + +## v0.3.27.2 (2025-06-28) + +### Chore +- switch project license to GPL v3 with additional terms and update related documentation and footer [`07715152`](https://github.com/Veloera/Veloera/commit/07715152) + +## v0.3.27.1 (2025-06-26) + +### Other +- feature: dd token rate limit feature (#111) [`c956fd33`](https://github.com/Veloera/Veloera/commit/c956fd33) + +## v0.3.27 (2025-06-26) + +### Feature +- add token rate limit [`4e50d162`](https://github.com/Veloera/Veloera/commit/4e50d162) + +### Bug Fixes +- correct in-memory rate limiter key usage in token rate limit handler [`a885eb52`](https://github.com/Veloera/Veloera/commit/a885eb52) +- fix username missing in check-in log (#109) [`199a7afb`](https://github.com/Veloera/Veloera/commit/199a7afb) +- fetch saved model list via channelId in edit mode (#108) [`2a02e1a6`](https://github.com/Veloera/Veloera/commit/2a02e1a6) + +### Documentation +- add sponsor section to README.md to acknowledge project supporters [`7e294f7e`](https://github.com/Veloera/Veloera/commit/7e294f7e) + +### Style +- remove unused timeFormat constant in token-rate-limit.go [`185c0037`](https://github.com/Veloera/Veloera/commit/185c0037) + +### Refactor +- 优化速率限制输入框的标签和布局结构 [`2241614b`](https://github.com/Veloera/Veloera/commit/2241614b) + +## v0.3.26 (2025-06-25) + +### Feature +- implement conversation logging for gemini channel (#107) [`d6800a71`](https://github.com/Veloera/Veloera/commit/d6800a71) +- support custom start&end date for redeem code (#97) [`8117f302`](https://github.com/Veloera/Veloera/commit/8117f302) + +### Bug Fixes +- modify the API address input box prompt to require a trailing / to disable the default /v1 prefix [`52fcc290`](https://github.com/Veloera/Veloera/commit/52fcc290) +- update confirmation message to include used redemptions in deletion prompt [`2bd1f3b5`](https://github.com/Veloera/Veloera/commit/2bd1f3b5) +- delete both disabled and used redemption codes in batch cleanup [`f80e5780`](https://github.com/Veloera/Veloera/commit/f80e5780) +- upgrade lucide-react from 0.503.0 to 0.511.0 (#101) [`89f42f13`](https://github.com/Veloera/Veloera/commit/89f42f13) +- upgrade next from 15.3.1 to 15.3.3 (#102) [`7908b87d`](https://github.com/Veloera/Veloera/commit/7908b87d) + +## v0.3.25 (2025-06-15) + +### Feature +- add stream_support pseudo streaming (#95) [`a5b82123`](https://github.com/Veloera/Veloera/commit/a5b82123) +- add chat content log switch (#92) [`9a5dcb3c`](https://github.com/Veloera/Veloera/commit/9a5dcb3c) +- block browser extension via global switch (#91) [`f8b51cda`](https://github.com/Veloera/Veloera/commit/f8b51cda) + +### Bug Fixes +- disable stream flag if using pseudo stream mode [`bb08a4d4`](https://github.com/Veloera/Veloera/commit/bb08a4d4) +- fix type in gemini pseudo stream (#96) [`e2c3689f`](https://github.com/Veloera/Veloera/commit/e2c3689f) + +### Documentation +- add ko-fi link [`7f939052`](https://github.com/Veloera/Veloera/commit/7f939052) +- 补充必选与可选项用于澄清 issue 提交要求 [`9143b84f`](https://github.com/Veloera/Veloera/commit/9143b84f) +- :robot: changelog file generated [`dae1c898`](https://github.com/Veloera/Veloera/commit/dae1c898) + +## v0.3.24 (2025-06-09) + +### Bug Fixes +- change RedisHDelObj to use Del instead of HDel [`aad6531b`](https://github.com/Veloera/Veloera/commit/aad6531b) +- prevent multiple redemptions of the same code under high concurrency (#88) [`19ffa60c`](https://github.com/Veloera/Veloera/commit/19ffa60c) + +## v0.3.23.1 (2025-06-09) + +### Bug Fixes +- remove duplicate gin import in relay-gemini.go (#86) [`fc184745`](https://github.com/Veloera/Veloera/commit/fc184745) + +## v0.3.23 (2025-06-09) + +### Feature +- enhance Gemini relay to support Thought processing (#80) [`0fd7c5a4`](https://github.com/Veloera/Veloera/commit/0fd7c5a4) + +### Bug Fixes +- display duplicate models with and without prefixes (#85) [`3f37ad53`](https://github.com/Veloera/Veloera/commit/3f37ad53) +- enhance schema cleaning (#79) [`5c4ce2a1`](https://github.com/Veloera/Veloera/commit/5c4ce2a1) + +### Refactor +- format code [`1b74bb95`](https://github.com/Veloera/Veloera/commit/1b74bb95) + +### Other +- Merge branch 'main' of https://github.com/Veloera/Veloera [`40a7d134`](https://github.com/Veloera/Veloera/commit/40a7d134) + +## v0.3.22 (2025-06-05) + +### Feature +- refresh prefix cache when channel changes (#77) [`a23d6c3d`](https://github.com/Veloera/Veloera/commit/a23d6c3d) + +### Bug Fixes +- remove deprecated Claude API (#73) [`eaaeb315`](https://github.com/Veloera/Veloera/commit/eaaeb315) +- support multiple channels with different prefixes for same model id without conflict (#70) [`082a7b69`](https://github.com/Veloera/Veloera/commit/082a7b69) + +### Documentation +- :robot: changelog file generated [`b69df048`](https://github.com/Veloera/Veloera/commit/b69df048) + +### Refactor +- simplify request URL generation logic [`8c445790`](https://github.com/Veloera/Veloera/commit/8c445790) + +## v0.3.21.1 (2025-06-01) + +### Bug Fixes +- key cannot be cleared in edit channel mode (#68) [`c09c25ba`](https://github.com/Veloera/Veloera/commit/c09c25ba) +- wrongly add ThinkingConfig to normal models (#66) [`dff0f84b`](https://github.com/Veloera/Veloera/commit/dff0f84b) +- fetch_models use wrong apiKey and release.yml cache invalid (#67) [`c93f8cf9`](https://github.com/Veloera/Veloera/commit/c93f8cf9) + +## v0.3.21 (2025-06-01) + +## v0.3.20 (2025-06-01) + +### Feature +- add URL code handling and user quota retrieval [`bf0562e3`](https://github.com/Veloera/Veloera/commit/bf0562e3) +- update navigation to handle return URLs [`54ed4bcd`](https://github.com/Veloera/Veloera/commit/54ed4bcd) +- Add new new customer rebate function (#49) [`9ed91de9`](https://github.com/Veloera/Veloera/commit/9ed91de9) + +### Bug Fixes +- encode URL parameters for login redirection [`6b9fde84`](https://github.com/Veloera/Veloera/commit/6b9fde84) +- region global is not working for Vertex AI [`9dfc4abe`](https://github.com/Veloera/Veloera/commit/9dfc4abe) +- fix pop-ups blocked issuse (#63) [`9295addb`](https://github.com/Veloera/Veloera/commit/9295addb) + +### Refactor +- remove checkedText from most switches [`0bd43945`](https://github.com/Veloera/Veloera/commit/0bd43945) + +## v0.3.19 (2025-05-30) + +### Feature +- use a banner to show that online recharge is not enabled [`87de5635`](https://github.com/Veloera/Veloera/commit/87de5635) +- add option to hide upstream error details (#61) [`460990c4`](https://github.com/Veloera/Veloera/commit/460990c4) +- Remember tag aggregation mode state (#56) [`cd38d7e9`](https://github.com/Veloera/Veloera/commit/cd38d7e9) + +### Other +- merge: Merge branch 'main' of https://github.com/Veloera/Veloera [`46db3401`](https://github.com/Veloera/Veloera/commit/46db3401) + +## v0.3.18 (2025-05-25) + +### Feature +- no longer remove the dots from the model name from Azure channel [`7d67a02b`](https://github.com/Veloera/Veloera/commit/7d67a02b) + +### Bug Fixes +- add user-level auth for /api/log/token, and make sure the token belongs to that user [`529dc4f4`](https://github.com/Veloera/Veloera/commit/529dc4f4) +- removed unused import of net/textproto [`43bd609a`](https://github.com/Veloera/Veloera/commit/43bd609a) +- fix typo in aws constants [`eb91dabc`](https://github.com/Veloera/Veloera/commit/eb91dabc) + +### Continuous Integration +- let changelog workflow no longer run when new tag are pushed [`9bb69a31`](https://github.com/Veloera/Veloera/commit/9bb69a31) + +## v0.3.17 (2025-05-24) + +### Feature +- Enhance channel refresh and search logic [`88bbdeac`](https://github.com/Veloera/Veloera/commit/88bbdeac) +- support for Claude 4 series [`bd398e1e`](https://github.com/Veloera/Veloera/commit/bd398e1e) + +### Bug Fixes +- fix cannot get max user id [`ca8d7645`](https://github.com/Veloera/Veloera/commit/ca8d7645) +- only use thinking adapter for model in Gemini 2.5 series [`fc3334f4`](https://github.com/Veloera/Veloera/commit/fc3334f4) + +### Chore +- fix typo (#47) [`7af24d0f`](https://github.com/Veloera/Veloera/commit/7af24d0f) + +### Other +- merge: Merge branch 'main' of https://github.com/Veloera/Veloera [`22504d19`](https://github.com/Veloera/Veloera/commit/22504d19) +- revert: revert "feat: add setting to disable chat content recording" [`1dd9bc82`](https://github.com/Veloera/Veloera/commit/1dd9bc82) + +## v0.3.16 (2025-05-19) + +### Feature +- enhance channel edit page key input and model selection UI [`5b1e6d60`](https://github.com/Veloera/Veloera/commit/5b1e6d60) + +### Continuous Integration +- fix permission err [`be854d14`](https://github.com/Veloera/Veloera/commit/be854d14) +- fix branch err [`39e747f2`](https://github.com/Veloera/Veloera/commit/39e747f2) + +### Documentation +- :robot: changelog file generated [`a92b34a5`](https://github.com/Veloera/Veloera/commit/a92b34a5) + +### Chore +- Merge branch 'main' of https://github.com/Veloera/Veloera [`6fe18323`](https://github.com/Veloera/Veloera/commit/6fe18323) + +## v0.3.15 (2025-05-18) + +### Bug Fixes +- fix 404 error when trying to call gemini models [`2cb1ce20`](https://github.com/Veloera/Veloera/commit/2cb1ce20) + +### Continuous Integration +- add ci to generate changelog [`b50c5699`](https://github.com/Veloera/Veloera/commit/b50c5699) + +### Chore +- add link to discussion in issue config [`66155c71`](https://github.com/Veloera/Veloera/commit/66155c71) + +## v0.3.14.1 (2025-05-17) + +### Bug Fixes +- add missing dep(sass-embedded) for web [`fc83f673`](https://github.com/Veloera/Veloera/commit/fc83f673) + +## v0.3.14 (2025-05-17) + +### Feature +- update theme [`5139d464`](https://github.com/Veloera/Veloera/commit/5139d464) + +### Documentation +- add hero image at readme [`20506072`](https://github.com/Veloera/Veloera/commit/20506072) +- add more details in about section in readme [`ee45d1e4`](https://github.com/Veloera/Veloera/commit/ee45d1e4) +- update about section in readme [`85a8684c`](https://github.com/Veloera/Veloera/commit/85a8684c) + +## v0.3.13 (2025-05-16) + +### Feature +- support multi-key view for all type of channels [`1deddbe3`](https://github.com/Veloera/Veloera/commit/1deddbe3) +- add setting to disable chat content recording [`91885cfe`](https://github.com/Veloera/Veloera/commit/91885cfe) + +### Bug Fixes +- fix gemini default setting [`44ef0957`](https://github.com/Veloera/Veloera/commit/44ef0957) + +## v0.3.12.2 (2025-05-14) + +### Bug Fixes +- quick fix to package.json [`6d1a4941`](https://github.com/Veloera/Veloera/commit/6d1a4941) + +## v0.3.12.1 (2025-05-14) + +### Bug Fixes +- fix the unexcepted link: in package.json [`f4f6d816`](https://github.com/Veloera/Veloera/commit/f4f6d816) + +## v0.3.12 (2025-05-14) + +### Feature +- support custom redeem code [`2c4d94d2`](https://github.com/Veloera/Veloera/commit/2c4d94d2) +- create a dedicated log category for check-ins [`fb50b4d8`](https://github.com/Veloera/Veloera/commit/fb50b4d8) +- Support Stripe as a payment method (#23) [`f85487d0`](https://github.com/Veloera/Veloera/commit/f85487d0) +- Support for gemini thinking suffix [`d76dc7af`](https://github.com/Veloera/Veloera/commit/d76dc7af) +- Support for gemini thinking suffix [`d4af0070`](https://github.com/Veloera/Veloera/commit/d4af0070) +- Option to temp disable multi-key view (GH-18) [`ef3cfcb4`](https://github.com/Veloera/Veloera/commit/ef3cfcb4) +- Support /v1/responses endpoint [`aff7e126`](https://github.com/Veloera/Veloera/commit/aff7e126) +- Support /v1/responses endpoint [`885b9d85`](https://github.com/Veloera/Veloera/commit/885b9d85) +- List view for multi key [`2a27e0c4`](https://github.com/Veloera/Veloera/commit/2a27e0c4) +- Add support for pasting multi-line key [`4c949d3b`](https://github.com/Veloera/Veloera/commit/4c949d3b) + +### Bug Fixes +- use reflection to get model name in migration log [`58364b54`](https://github.com/Veloera/Veloera/commit/58364b54) +- Handle PostgreSQL 'relation already exists' error during migration [`65498936`](https://github.com/Veloera/Veloera/commit/65498936) +- Correct misspelling in common/go-channel.go comments [`cc500ef3`](https://github.com/Veloera/Veloera/commit/cc500ef3) +- use latest form values when fetching models in advanced selector #19 [`696985cf`](https://github.com/Veloera/Veloera/commit/696985cf) +- Include prefixed model names in user model list [`e3c416b1`](https://github.com/Veloera/Veloera/commit/e3c416b1) + +### Continuous Integration +- Change download-artifact@v3 to v4 [`e1289b50`](https://github.com/Veloera/Veloera/commit/e1289b50) +- Use npm install [`dc1f717f`](https://github.com/Veloera/Veloera/commit/dc1f717f) +- Disable build cache [`5aca6ebb`](https://github.com/Veloera/Veloera/commit/5aca6ebb) +- Update checkout&artifact action version to v4 [`767d78e1`](https://github.com/Veloera/Veloera/commit/767d78e1) + +### Documentation +- Add migrate guide [`eee63476`](https://github.com/Veloera/Veloera/commit/eee63476) +- Add whitespace between badges [`c52b112b`](https://github.com/Veloera/Veloera/commit/c52b112b) + +### Chore +- Delete useless files [`755bea8c`](https://github.com/Veloera/Veloera/commit/755bea8c) +- Rename to veloera [`baab9fb9`](https://github.com/Veloera/Veloera/commit/baab9fb9) +- Rename to Veloera [`5df1fc6a`](https://github.com/Veloera/Veloera/commit/5df1fc6a) +- Add aider files to gitignore [`56f6c089`](https://github.com/Veloera/Veloera/commit/56f6c089) + +### Refactor +- reduce complexity of ResponsesHelper function [`e5bd366e`](https://github.com/Veloera/Veloera/commit/e5bd366e) +- Update check-in logic to use UTC for date comparisons [`83592237`](https://github.com/Veloera/Veloera/commit/83592237) + +### Other +- Revert "feat: Support Stripe as a payment method (#23)" (#30) [`c1fba255`](https://github.com/Veloera/Veloera/commit/c1fba255) +- format: Format code using gofmt [`abdb966a`](https://github.com/Veloera/Veloera/commit/abdb966a) +- format: Format code use gofmt [`70915028`](https://github.com/Veloera/Veloera/commit/70915028) +- Fix workflow badge path in README [`123fc658`](https://github.com/Veloera/Veloera/commit/123fc658) +- Simplify CI [`e030c702`](https://github.com/Veloera/Veloera/commit/e030c702) + +## v0.3.6 (2025-04-24) + +### Feature +- Add advanced model fetch [`a3f2f514`](https://github.com/Veloera/Veloera/commit/a3f2f514) + +### Chore +- Fix proxy path in Vite config [`fd7265c6`](https://github.com/Veloera/Veloera/commit/fd7265c6) +- Working on new interface [`664017a5`](https://github.com/Veloera/Veloera/commit/664017a5) + +## v0.3.5 (2025-04-23) + +### Feature +- Migrate databases from one-api.db to veloera.db [`79b1ca51`](https://github.com/Veloera/Veloera/commit/79b1ca51) + +### Bug Fixes +- Fix username confilct [`62142698`](https://github.com/Veloera/Veloera/commit/62142698) + +## v0.3.4 (2025-04-22) + +### Feature +- Implement round-robin key selection [`3ab82c44`](https://github.com/Veloera/Veloera/commit/3ab82c44) + +### Bug Fixes +- No longer accept multi-key in Vertex channel [`fdd609c3`](https://github.com/Veloera/Veloera/commit/fdd609c3) + +### Style +- Format code using gofmt [`270a37f4`](https://github.com/Veloera/Veloera/commit/270a37f4) + +## v0.3.3 (2025-04-22) + +### Feature +- Channel prefix [`8f28acc5`](https://github.com/Veloera/Veloera/commit/8f28acc5) +- Add daily check-in feature [`78bfeda9`](https://github.com/Veloera/Veloera/commit/78bfeda9) +- Add CheckIn settings component [`8ba4a540`](https://github.com/Veloera/Veloera/commit/8ba4a540) + +### Bug Fixes +- Cannot save check in settings [`8996d68a`](https://github.com/Veloera/Veloera/commit/8996d68a) + +## v0.3.2-beta (2025-04-21) + +### Feature +- Add check-in [`670c668b`](https://github.com/Veloera/Veloera/commit/670c668b) +- Add batch operations for redemptions [`0221ea63`](https://github.com/Veloera/Veloera/commit/0221ea63) +- Add check-in [`29b3e0c2`](https://github.com/Veloera/Veloera/commit/29b3e0c2) + +### Documentation +- Add cool badges in README [`f0df9d66`](https://github.com/Veloera/Veloera/commit/f0df9d66) +- Add CLAUDE.md [`b5fac30a`](https://github.com/Veloera/Veloera/commit/b5fac30a) + +### Chore +- Delete patch files [`4946af08`](https://github.com/Veloera/Veloera/commit/4946af08) + +### Other +- format: Format code [`125396b2`](https://github.com/Veloera/Veloera/commit/125396b2) + +## v0.3.1 (2025-04-20) + +## v0.3.0 (2025-04-20) + +### Feature +- Show chat content and context [`ced6b0ba`](https://github.com/Veloera/Veloera/commit/ced6b0ba) +- More detail in LogsTable [`fa503645`](https://github.com/Veloera/Veloera/commit/fa503645) +- Avoid charging for empty responses [`649ae76b`](https://github.com/Veloera/Veloera/commit/649ae76b) +- Empty reply now does not count as usage [`9872cd86`](https://github.com/Veloera/Veloera/commit/9872cd86) +- Remove the banner at general setting [`05f9b131`](https://github.com/Veloera/Veloera/commit/05f9b131) +- Update loading effect [`679e6730`](https://github.com/Veloera/Veloera/commit/679e6730) +- Only show the chat button when the chat link is configed [`8fe3817e`](https://github.com/Veloera/Veloera/commit/8fe3817e) +- Show channel name at LogsTable [`c66f684a`](https://github.com/Veloera/Veloera/commit/c66f684a) +- Add refresh button for LogsTable [`3abe72f0`](https://github.com/Veloera/Veloera/commit/3abe72f0) + +### Bug Fixes +- Fix missing VERSION file which is required by Dockerfile [`f93e2f94`](https://github.com/Veloera/Veloera/commit/f93e2f94) +- Fixed display error when success redeem a code [`4099b331`](https://github.com/Veloera/Veloera/commit/4099b331) + +### Documentation +- Sync features section with latest features [`8012db8b`](https://github.com/Veloera/Veloera/commit/8012db8b) +- Add features section at README [`c35392a6`](https://github.com/Veloera/Veloera/commit/c35392a6) + +### Other +- revert: 9872cd86 [`7a0604f4`](https://github.com/Veloera/Veloera/commit/7a0604f4) +- revert: Revert changes in commit f258225 [`cf60bb1d`](https://github.com/Veloera/Veloera/commit/cf60bb1d) + +## v0.2.1 (2025-04-17) + +### Feature +- Let the channel key visible and editable at frontend [`260ab856`](https://github.com/Veloera/Veloera/commit/260ab856) +- Start working on the new UI [`025a4d72`](https://github.com/Veloera/Veloera/commit/025a4d72) +- Auto re-order channel id when delete channel [`f2582252`](https://github.com/Veloera/Veloera/commit/f2582252) + +### Bug Fixes +- Fix sometimes max_uses will be string [`b3f7bdbf`](https://github.com/Veloera/Veloera/commit/b3f7bdbf) +- Fix a little typo in EditUser [`7bcec0d2`](https://github.com/Veloera/Veloera/commit/7bcec0d2) +- Typo in docker-compose.yml [`a27c86a7`](https://github.com/Veloera/Veloera/commit/a27c86a7) + +### Documentation +- Replace most new-api with Veloera [`4c03f368`](https://github.com/Veloera/Veloera/commit/4c03f368) + +### Chore +- Make session more stable [`3deccb02`](https://github.com/Veloera/Veloera/commit/3deccb02) + +## v0.2 (2025-04-17) + +### Documentation +- Update README [`8d02adee`](https://github.com/Veloera/Veloera/commit/8d02adee) + +## v0.1.2-alpha (2025-04-17) + +### Continuous Integration +- Let CI only push to ghcr.io [`1b510d19`](https://github.com/Veloera/Veloera/commit/1b510d19) +- Correct the filename in linux_release [`acf20b08`](https://github.com/Veloera/Veloera/commit/acf20b08) + +## v0.1.0 (2025-04-17) + +## 0.3.11 (2025-05-05) + +### Feature +- Option to temp disable multi-key view (GH-18) [`ef3cfcb4`](https://github.com/Veloera/Veloera/commit/ef3cfcb4) + +### Bug Fixes +- use latest form values when fetching models in advanced selector #19 [`696985cf`](https://github.com/Veloera/Veloera/commit/696985cf) + +### Chore +- Delete useless files [`755bea8c`](https://github.com/Veloera/Veloera/commit/755bea8c) + +## 0.3.10 (2025-05-04) + +### Feature +- Support /v1/responses endpoint [`aff7e126`](https://github.com/Veloera/Veloera/commit/aff7e126) +- Support /v1/responses endpoint [`885b9d85`](https://github.com/Veloera/Veloera/commit/885b9d85) + +### Chore +- Rename to veloera [`baab9fb9`](https://github.com/Veloera/Veloera/commit/baab9fb9) + +### Refactor +- reduce complexity of ResponsesHelper function [`e5bd366e`](https://github.com/Veloera/Veloera/commit/e5bd366e) +- Update check-in logic to use UTC for date comparisons [`83592237`](https://github.com/Veloera/Veloera/commit/83592237) + +## 0.3.9 (2025-05-01) + +### Feature +- List view for multi key [`2a27e0c4`](https://github.com/Veloera/Veloera/commit/2a27e0c4) +- Add support for pasting multi-line key [`4c949d3b`](https://github.com/Veloera/Veloera/commit/4c949d3b) + +## 0.3.8 (2025-04-28) + +### Continuous Integration +- Change download-artifact@v3 to v4 [`e1289b50`](https://github.com/Veloera/Veloera/commit/e1289b50) +- Use npm install [`dc1f717f`](https://github.com/Veloera/Veloera/commit/dc1f717f) +- Disable build cache [`5aca6ebb`](https://github.com/Veloera/Veloera/commit/5aca6ebb) + +## 0.3.7.1 (2025-04-28) + +### Continuous Integration +- Update checkout&artifact action version to v4 [`767d78e1`](https://github.com/Veloera/Veloera/commit/767d78e1) + +## 0.3.7 (2025-04-28) + +### Feature +- Add advanced model fetch [`a3f2f514`](https://github.com/Veloera/Veloera/commit/a3f2f514) +- Migrate databases from one-api.db to veloera.db [`79b1ca51`](https://github.com/Veloera/Veloera/commit/79b1ca51) +- Implement round-robin key selection [`3ab82c44`](https://github.com/Veloera/Veloera/commit/3ab82c44) +- Channel prefix [`8f28acc5`](https://github.com/Veloera/Veloera/commit/8f28acc5) +- Add daily check-in feature [`78bfeda9`](https://github.com/Veloera/Veloera/commit/78bfeda9) +- Add CheckIn settings component [`8ba4a540`](https://github.com/Veloera/Veloera/commit/8ba4a540) +- Add check-in [`670c668b`](https://github.com/Veloera/Veloera/commit/670c668b) +- Add batch operations for redemptions [`0221ea63`](https://github.com/Veloera/Veloera/commit/0221ea63) +- Add check-in [`29b3e0c2`](https://github.com/Veloera/Veloera/commit/29b3e0c2) +- Show chat content and context [`ced6b0ba`](https://github.com/Veloera/Veloera/commit/ced6b0ba) +- More detail in LogsTable [`fa503645`](https://github.com/Veloera/Veloera/commit/fa503645) +- Avoid charging for empty responses [`649ae76b`](https://github.com/Veloera/Veloera/commit/649ae76b) +- Empty reply now does not count as usage [`9872cd86`](https://github.com/Veloera/Veloera/commit/9872cd86) +- Remove the banner at general setting [`05f9b131`](https://github.com/Veloera/Veloera/commit/05f9b131) +- Update loading effect [`679e6730`](https://github.com/Veloera/Veloera/commit/679e6730) +- Only show the chat button when the chat link is configed [`8fe3817e`](https://github.com/Veloera/Veloera/commit/8fe3817e) +- Show channel name at LogsTable [`c66f684a`](https://github.com/Veloera/Veloera/commit/c66f684a) +- Add refresh button for LogsTable [`3abe72f0`](https://github.com/Veloera/Veloera/commit/3abe72f0) +- Let the channel key visible and editable at frontend [`260ab856`](https://github.com/Veloera/Veloera/commit/260ab856) +- Start working on the new UI [`025a4d72`](https://github.com/Veloera/Veloera/commit/025a4d72) +- Auto re-order channel id when delete channel [`f2582252`](https://github.com/Veloera/Veloera/commit/f2582252) +- Add regex support for sensitive word detect [`b97ec2d5`](https://github.com/Veloera/Veloera/commit/b97ec2d5) +- Add support for /hf/v1 prefix` [`62a88eae`](https://github.com/Veloera/Veloera/commit/62a88eae) +- support gemini output text and inline images. (close #866) [`473e8e0e`](https://github.com/Veloera/Veloera/commit/473e8e0e) +- 添加流模式下的SSE保活机制 #945 [`2f3acd9d`](https://github.com/Veloera/Veloera/commit/2f3acd9d) +- enhance Claude to OpenAI request conversion with additional relay info support [`90576d02`](https://github.com/Veloera/Veloera/commit/90576d02) +- 完善openai转claude支持 [`4b3e30e6`](https://github.com/Veloera/Veloera/commit/4b3e30e6) +- enhance file handling and logging in the application [`cca9c047`](https://github.com/Veloera/Veloera/commit/cca9c047) +- implement parameter cleaning for Gemini functions [`2ec45656`](https://github.com/Veloera/Veloera/commit/2ec45656) +- support zhipu_4v embeddings path [`a4fb3395`](https://github.com/Veloera/Veloera/commit/a4fb3395) +- add xAI handling and response processing [`8723e3f2`](https://github.com/Veloera/Veloera/commit/8723e3f2) +- update adaptor methods and add new image model [`700c05b8`](https://github.com/Veloera/Veloera/commit/700c05b8) +- add xai grok-3-mini reasoning effort [`c5103237`](https://github.com/Veloera/Veloera/commit/c5103237) +- add xai channel [`f500eb17`](https://github.com/Veloera/Veloera/commit/f500eb17) +- Add CheckSetup function call in main to ensure proper initialization #942 [`08221817`](https://github.com/Veloera/Veloera/commit/08221817) +- Integrate SetupCheck component for improved setup validation in routing [`5813ca78`](https://github.com/Veloera/Veloera/commit/5813ca78) +- Initialize model settings and improve concurrency control in operation settings [`aa34c303`](https://github.com/Veloera/Veloera/commit/aa34c303) +- Add concurrency control to group ratio management with mutexes [`fb9f5950`](https://github.com/Veloera/Veloera/commit/fb9f5950) +- e5baa6ee ✨ feat: Enhance ModelSettingsVisualEditor with pricing modes and improved model management features [`e5baa6ee`](https://github.com/Veloera/Veloera/commit/e5baa6ee) +- 9207d729 ✨ feat: Add new localization strings for system initialization [`9207d729`](https://github.com/Veloera/Veloera/commit/9207d729) +- Add timestamp and version to setup initialization in PostSetup function [`454dac17`](https://github.com/Veloera/Veloera/commit/454dac17) +- 5fa64624 ✨ feat: Refine personal mode description in setup page for clarity [`5fa64624`](https://github.com/Veloera/Veloera/commit/5fa64624) +- a882e680 ✨ feat: Implement system setup functionality [`a882e680`](https://github.com/Veloera/Veloera/commit/a882e680) +- c418d9ed ✨ feat: Enhance user settings and notification options [`c418d9ed`](https://github.com/Veloera/Veloera/commit/c418d9ed) +- 3c2a86f9 ✨ feat: Update option handling in SystemSetting [`3c2a86f9`](https://github.com/Veloera/Veloera/commit/3c2a86f9) +- Add Parameters Override [`1b072821`](https://github.com/Veloera/Veloera/commit/1b072821) +- Add new cache ratios for o3-mini and gpt-4.5-preview models [`a378665b`](https://github.com/Veloera/Veloera/commit/a378665b) +- Enhance GetCompletionRatio function [`58525c57`](https://github.com/Veloera/Veloera/commit/58525c57) +- Add support for cross-region AWS model handling in awsStreamHandler [`7143b0f1`](https://github.com/Veloera/Veloera/commit/7143b0f1) +- Enhance ConvertClaudeRequest method to set request model and handle vertex-specific request conversion [`19935ee8`](https://github.com/Veloera/Veloera/commit/19935ee8) +- Update RerankerInfo structure and modify GenRelayInfoRerank function to accept RerankRequest [`6fef5aaf`](https://github.com/Veloera/Veloera/commit/6fef5aaf) +- support xinference rerank to jina format [`d1c62a58`](https://github.com/Veloera/Veloera/commit/d1c62a58) +- Introduce JSON decoding utility functions and update error handling in Claude and OpenAI response structures [`b3b1c803`](https://github.com/Veloera/Veloera/commit/b3b1c803) +- Add warning modal for base URL input and display warning banner for specific channel type in EditChannel component [`54e73894`](https://github.com/Veloera/Veloera/commit/54e73894) +- support dify upload image file [`dd393cd0`](https://github.com/Veloera/Veloera/commit/dd393cd0) +- support AWS Model CrossRegion [`892d014c`](https://github.com/Veloera/Veloera/commit/892d014c) +- Add HasSentThinkingContent field to ThinkingContentInfo struct [`9a78db84`](https://github.com/Veloera/Veloera/commit/9a78db84) +- 初步兼容流模式下openai渠道类型转为claude格式访问 #862 [`7e46d421`](https://github.com/Veloera/Veloera/commit/7e46d421) +- claude relay [`bd48f434`](https://github.com/Veloera/Veloera/commit/bd48f434) +- Support postgresql:// dsn format [`c47d8a10`](https://github.com/Veloera/Veloera/commit/c47d8a10) +- Add Xinference channel support [`a981e107`](https://github.com/Veloera/Veloera/commit/a981e107) +- Improve model testing button layout and styling [`1a2bf8df`](https://github.com/Veloera/Veloera/commit/1a2bf8df) +- Enhance error handling with optional detailed error messages [`1819c4d5`](https://github.com/Veloera/Veloera/commit/1819c4d5) +- Add pass-through request option for global settings [`6f24dddc`](https://github.com/Veloera/Veloera/commit/6f24dddc) +- add 'Document Link' option i18n support [`5259acfa`](https://github.com/Veloera/Veloera/commit/5259acfa) +- add oidc support [`c433af28`](https://github.com/Veloera/Veloera/commit/c433af28) +- Improve route handling and dynamic chat navigation in SiderBar [`2af05c16`](https://github.com/Veloera/Veloera/commit/2af05c16) +- gemini Embeddings support [`e1b9f164`](https://github.com/Veloera/Veloera/commit/e1b9f164) +- Enhance mobile UI responsiveness and layout for ChannelsTable and SiderBar [`49bfd2b7`](https://github.com/Veloera/Veloera/commit/49bfd2b7) +- Introduce configurable docs link and remove hardcoded chat links [`00c2d6c1`](https://github.com/Veloera/Veloera/commit/00c2d6c1) +- Improve decimal precision for quota and payment calculations [`68097c13`](https://github.com/Veloera/Veloera/commit/68097c13) +- Add column visibility settings for Channels and Logs tables [`7fcb14e2`](https://github.com/Veloera/Veloera/commit/7fcb14e2) +- update readme and i18n [`3ad96d3b`](https://github.com/Veloera/Veloera/commit/3ad96d3b) +- Add prompt cache hit tokens support for DeepSeek channel #406 [`a9bfcb0d`](https://github.com/Veloera/Veloera/commit/a9bfcb0d) +- Implement cache token ratio for more precise token pricing [`4f194f4e`](https://github.com/Veloera/Veloera/commit/4f194f4e) +- Enhance channel status update with success tracking and dynamic notification #812 [`7f74a966`](https://github.com/Veloera/Veloera/commit/7f74a966) +- Add context-aware goroutine pool for safer concurrent operations [`cbdf26bf`](https://github.com/Veloera/Veloera/commit/cbdf26bf) +- Improve image download and validation in GetImageFromUrl [`6ecfb81c`](https://github.com/Veloera/Veloera/commit/6ecfb81c) +- yanjingxia [`254c25c2`](https://github.com/Veloera/Veloera/commit/254c25c2) +- Add model testing modal with search functionality in ChannelsTable [`8731a32e`](https://github.com/Veloera/Veloera/commit/8731a32e) +- Persist models expanded state in PersonalSetting component [`816e831a`](https://github.com/Veloera/Veloera/commit/816e831a) +- Enhance update checking and system information display [`a3ceae4a`](https://github.com/Veloera/Veloera/commit/a3ceae4a) +- Add self-use mode and demo site mode indicators to HeaderBar [`eb163d9c`](https://github.com/Veloera/Veloera/commit/eb163d9c) +- Add translations for self-use mode and demo site mode settings [`bb300d19`](https://github.com/Veloera/Veloera/commit/bb300d19) +- Add self-use mode for model ratio and price configuration [`7dbb6b01`](https://github.com/Veloera/Veloera/commit/7dbb6b01) +- Add new model management features [`18d3706f`](https://github.com/Veloera/Veloera/commit/18d3706f) +- add new GPT-4.5 preview model ratios [`d6fd50e3`](https://github.com/Veloera/Veloera/commit/d6fd50e3) +- Enhance Claude default max tokens configuration [`cfd3f6c0`](https://github.com/Veloera/Veloera/commit/cfd3f6c0) +- Implement model-specific headers configuration for Claude [`45c56b5d`](https://github.com/Veloera/Veloera/commit/45c56b5d) +- Enhance Claude MaxTokens configuration handling [`d0bc8d17`](https://github.com/Veloera/Veloera/commit/d0bc8d17) +- Refactor model configuration management with new config system [`929668be`](https://github.com/Veloera/Veloera/commit/929668be) +- Add Claude model configuration management #791 [`06a78f90`](https://github.com/Veloera/Veloera/commit/06a78f90) +- Add Jina reranking support for OpenAI adaptor [`287caf8e`](https://github.com/Veloera/Veloera/commit/287caf8e) +- Add Gemini version settings configuration support (close #568) [`bf80d71d`](https://github.com/Veloera/Veloera/commit/bf80d71d) +- Add Gemini safety settings configuration support (close #703) [`e19b244e`](https://github.com/Veloera/Veloera/commit/e19b244e) +- Update Claude relay temperature setting [`f4512688`](https://github.com/Veloera/Veloera/commit/f4512688) +- redis poolsize [`ccf13d44`](https://github.com/Veloera/Veloera/commit/ccf13d44) +- Add support for Claude thinking parameter in request [`83feb492`](https://github.com/Veloera/Veloera/commit/83feb492) +- Add Claude 3.7 Sonnet thinking mode support [`4f212be4`](https://github.com/Veloera/Veloera/commit/4f212be4) +- Add Claude 3.7 Sonnet model to AWS channel mapping [`92918e37`](https://github.com/Veloera/Veloera/commit/92918e37) +- Add support for Claude 3.7 Sonnet model [`de155515`](https://github.com/Veloera/Veloera/commit/de155515) +- Support max_tokens parameter for Ollama channel #782 [`a81a28b7`](https://github.com/Veloera/Veloera/commit/a81a28b7) +- Add model rate limit settings in system configuration [`e9ba392a`](https://github.com/Veloera/Veloera/commit/e9ba392a) +- Add model request rate limiting functionality [`83a37e46`](https://github.com/Veloera/Veloera/commit/83a37e46) +- Add support for different Dify bot types and request URLs [`b6f95dca`](https://github.com/Veloera/Veloera/commit/b6f95dca) +- Enhance token counting and content parsing for messages [`7ff4cebd`](https://github.com/Veloera/Veloera/commit/7ff4cebd) +- Add thinking-to-content option in channel extra settings #780 [`d5ab7d2d`](https://github.com/Veloera/Veloera/commit/d5ab7d2d) +- Add thinking-to-content conversion for stream responses [`115a181d`](https://github.com/Veloera/Veloera/commit/115a181d) +- Add reasoning content support in OpenAI response handling [`6e7587ab`](https://github.com/Veloera/Veloera/commit/6e7587ab) +- Enhance sensitive word detection with detailed logging [`9cc6385b`](https://github.com/Veloera/Veloera/commit/9cc6385b) +- Add base URL input with localized tooltip for channel configuration [`94736407`](https://github.com/Veloera/Veloera/commit/94736407) +- Add localization for notification and webhook settings [`de859c3c`](https://github.com/Veloera/Veloera/commit/de859c3c) +- Improve mobile text truncation and sidebar visibility [`4ce12ea6`](https://github.com/Veloera/Veloera/commit/4ce12ea6) +- Improve image handling for Ollama channels [`971aea09`](https://github.com/Veloera/Veloera/commit/971aea09) +- Enhance Ollama channel support with additional request parameters #771 [`a4b2b9c9`](https://github.com/Veloera/Veloera/commit/a4b2b9c9) +- Implement comprehensive webhook notification system [`4e871507`](https://github.com/Veloera/Veloera/commit/4e871507) +- Implement notification rate limiting mechanism [`56f6b2ab`](https://github.com/Veloera/Veloera/commit/56f6b2ab) +- Add user notification settings with quota warning and multiple notification methods [`3da13448`](https://github.com/Veloera/Veloera/commit/3da13448) +- add Gemini Imagen image generation support [`61d2a2f9`](https://github.com/Veloera/Veloera/commit/61d2a2f9) +- Add support for DeepSeek completions endpoint [`7b384cb9`](https://github.com/Veloera/Veloera/commit/7b384cb9) +- add 火山引擎 support stream options [`e7e5a167`](https://github.com/Veloera/Veloera/commit/e7e5a167) +- Enhance VolcEngine channel support with bot model routing (fix #757) [`6bf99f21`](https://github.com/Veloera/Veloera/commit/6bf99f21) +- Add automatic channel disabling based on configurable keywords [`9edb9f7a`](https://github.com/Veloera/Veloera/commit/9edb9f7a) +- Add invite link banner for specific channel type [`6b923ef7`](https://github.com/Veloera/Veloera/commit/6b923ef7) +- Improve embedding request handling and support across channels [`f5e3063f`](https://github.com/Veloera/Veloera/commit/f5e3063f) +- Add Baidu Qianfan V2 channel support #725 [`eceb6afc`](https://github.com/Veloera/Veloera/commit/eceb6afc) +- Add support for VolcEngine (Doubao) channel #313 #734 [`28c13e5a`](https://github.com/Veloera/Veloera/commit/28c13e5a) +- enhance OpenAI request and response DTOs [`1f527ffc`](https://github.com/Veloera/Veloera/commit/1f527ffc) +- enhance session store security and configuration [`cb4d40c3`](https://github.com/Veloera/Veloera/commit/cb4d40c3) +- configure session store options for API routes [`6acc37cf`](https://github.com/Veloera/Veloera/commit/6acc37cf) +- add `Suffix` to GeneralOpenAIRequest in order to support FIM [`80829051`](https://github.com/Veloera/Veloera/commit/80829051) +- modify channel model_mapping column type to TEXT [`675e62d8`](https://github.com/Veloera/Veloera/commit/675e62d8) +- add SOCKS5 proxy authentication support [`0f5c090a`](https://github.com/Veloera/Veloera/commit/0f5c090a) +- add demo site configuration flag [`a0fe5270`](https://github.com/Veloera/Veloera/commit/a0fe5270) +- add Azure default API version configuration [`187c3361`](https://github.com/Veloera/Veloera/commit/187c3361) +- enhance model name handling and logging [`c68ea565`](https://github.com/Veloera/Veloera/commit/c68ea565) +- add reasoning effort logging and display [`834ceda8`](https://github.com/Veloera/Veloera/commit/834ceda8) +- add reasoning effort configuration for models [`d5746ac3`](https://github.com/Veloera/Veloera/commit/d5746ac3) +- add other_setting docs link [`0831ba26`](https://github.com/Veloera/Veloera/commit/0831ba26) +- support channel request proxy [`cf63ab59`](https://github.com/Veloera/Veloera/commit/cf63ab59) +- add support for o3-mini models in model ratio and request handling [`69102d14`](https://github.com/Veloera/Veloera/commit/69102d14) +- enhance model ratio lookup with case-insensitive and direct matching [`2aca637b`](https://github.com/Veloera/Veloera/commit/2aca637b) +- add chat link for AIaW [`324d127a`](https://github.com/Veloera/Veloera/commit/324d127a) +- 更新模型和模型倍率 [`6e2c8710`](https://github.com/Veloera/Veloera/commit/6e2c8710) +- modify fetching model list in add channel to fetch by type [`f5be2868`](https://github.com/Veloera/Veloera/commit/f5be2868) +- add channel balance for siliconflow and deepseek [`fc33f2f0`](https://github.com/Veloera/Veloera/commit/fc33f2f0) +- support gpt-4o-mini-realtime-preview [`4a0a841e`](https://github.com/Veloera/Veloera/commit/4a0a841e) +- implement pagination and total count for redemptions API #386 [`4f196a62`](https://github.com/Veloera/Veloera/commit/4f196a62) +- enhance user search functionality with pagination support [`014fb7ed`](https://github.com/Veloera/Veloera/commit/014fb7ed) +- enhance user management and pagination features #518 [`be0b2f6a`](https://github.com/Veloera/Veloera/commit/be0b2f6a) +- enhance environment variable handling and security features [`2f01a212`](https://github.com/Veloera/Veloera/commit/2f01a212) +- add multi-file type support for Gemini and Claude [`2b38e8ed`](https://github.com/Veloera/Veloera/commit/2b38e8ed) +- update o1 default token encoder [`d2297d27`](https://github.com/Veloera/Veloera/commit/d2297d27) +- support azure stream_options [`62ae46b5`](https://github.com/Veloera/Veloera/commit/62ae46b5) +- Implement batch tagging functionality for channels [`72d6898e`](https://github.com/Veloera/Veloera/commit/72d6898e) +- Enhance pricing functionality with user group support [`8129aa76`](https://github.com/Veloera/Veloera/commit/8129aa76) +- Update localization and enhance token editing functionality [`fb8595da`](https://github.com/Veloera/Veloera/commit/fb8595da) +- Add FetchModels endpoint and refactor FetchUpstreamModels [`93cda60d`](https://github.com/Veloera/Veloera/commit/93cda60d) +- Enhance LogsTable component with mobile support and date handling improvements [`2ec5eafb`](https://github.com/Veloera/Veloera/commit/2ec5eafb) +- Enhance logging functionality with group support [`7180e6f1`](https://github.com/Veloera/Veloera/commit/7180e6f1) +- Add request start time context key and update middleware [`f3f1817a`](https://github.com/Veloera/Veloera/commit/f3f1817a) +- Enhance GeminiChatHandler to include RelayInfo [`58fac129`](https://github.com/Veloera/Veloera/commit/58fac129) +- Add FunctionResponse type and enhance GeminiPart structure [`1d0ef89c`](https://github.com/Veloera/Veloera/commit/1d0ef89c) +- Introduce settings package and refactor constants [`a7e1d17c`](https://github.com/Veloera/Veloera/commit/a7e1d17c) +- Enhance Gemini function parameter handling [`53ab2aae`](https://github.com/Veloera/Veloera/commit/53ab2aae) +- Enhance LogsTable to render group information [`a5c48c27`](https://github.com/Veloera/Veloera/commit/a5c48c27) +- Add log information generation and enhance LogsTable component [`cffaf0d6`](https://github.com/Veloera/Veloera/commit/cffaf0d6) +- support for Gemini structured output. [`43a7b59b`](https://github.com/Veloera/Veloera/commit/43a7b59b) +- 适配o1模型 [`1fa478af`](https://github.com/Veloera/Veloera/commit/1fa478af) +- 适配o1模型 [`0b1ba2ee`](https://github.com/Veloera/Veloera/commit/0b1ba2ee) +- 适配o1模型 [`35277f2b`](https://github.com/Veloera/Veloera/commit/35277f2b) +- Add GEMINI_VISION_MAX_IMAGE_NUM configuration [`f9a7f608`](https://github.com/Veloera/Veloera/commit/f9a7f608) +- Add new experimental Gemini versions to ModelList [`cab30014`](https://github.com/Veloera/Veloera/commit/cab30014) +- support gemini-2.0-flash-thinking #639 #637 [`9a54b345`](https://github.com/Veloera/Veloera/commit/9a54b345) +- Enhance Home component to support language messaging [`fe6f3d79`](https://github.com/Veloera/Veloera/commit/fe6f3d79) +- Enhance HeaderBar to support language change messaging [`fd86de19`](https://github.com/Veloera/Veloera/commit/fd86de19) +- support gemini SystemInstructions #408 [`e6c6bbde`](https://github.com/Veloera/Veloera/commit/e6c6bbde) +- Add collapsible section for available models in PersonalSettings [`e8444edc`](https://github.com/Veloera/Veloera/commit/e8444edc) +- implement channel settings configuration [`f2809917`](https://github.com/Veloera/Veloera/commit/f2809917) +- Enhance Operation Settings with Group and Model Ratio Management [`a4c43bb8`](https://github.com/Veloera/Veloera/commit/a4c43bb8) +- Refactor App and ChannelsTable components for improved i18n support [`41a7cee9`](https://github.com/Veloera/Veloera/commit/41a7cee9) +- Enhance i18n support in Home component and update translations [`68b87736`](https://github.com/Veloera/Veloera/commit/68b87736) +- Implement status loading in App component and refactor SiderBar [`b86aeb91`](https://github.com/Veloera/Veloera/commit/b86aeb91) +- support i18n [`5f06feb9`](https://github.com/Veloera/Veloera/commit/5f06feb9) +- Integrate i18n support and enhance UI text localization [`221d7b5c`](https://github.com/Veloera/Veloera/commit/221d7b5c) +- 增加价格和倍率的互斥验证,优化模型名称输入提示 [`3587f2c6`](https://github.com/Veloera/Veloera/commit/3587f2c6) +- 优化模型设置可视化编辑器,增强输入验证和提示信息 [`498590d9`](https://github.com/Veloera/Veloera/commit/498590d9) +- 添加保存功能并优化模型数据提交逻辑 [`8504e072`](https://github.com/Veloera/Veloera/commit/8504e072) +- 添加模型设置可视化编辑器组件 [`369ecf36`](https://github.com/Veloera/Veloera/commit/369ecf36) +- init i18n [`cd21aa1c`](https://github.com/Veloera/Veloera/commit/cd21aa1c) +- 兼容OpenAI格式下设置gemini模型联网搜索 #615 [`5d338337`](https://github.com/Veloera/Veloera/commit/5d338337) +- add model gemini-2.0-flash-exp [`b1fb5956`](https://github.com/Veloera/Veloera/commit/b1fb5956) +- Enhance group label display in Playground component [`44512d3c`](https://github.com/Veloera/Veloera/commit/44512d3c) +- Enhance quota data handling and CSS styling [`6625563f`](https://github.com/Veloera/Veloera/commit/6625563f) +- Update SiderBar and Detail components for improved navigation and data visualization [`b2d36b94`](https://github.com/Veloera/Veloera/commit/b2d36b94) +- Enhance color mapping and chart rendering in Detail component [`ab4c9fdb`](https://github.com/Veloera/Veloera/commit/ab4c9fdb) +- Add pricing link to HeaderBar component [`f0d9c896`](https://github.com/Veloera/Veloera/commit/f0d9c896) +- Refactor style management for inner padding in layout components [`28fa77cc`](https://github.com/Veloera/Veloera/commit/28fa77cc) +- Update model lists and enhance model retrieval in Adaptor [`024cdb08`](https://github.com/Veloera/Veloera/commit/024cdb08) +- Add filtering and search functionality to model selection in EditChannel and EditTagModal [`89136dfa`](https://github.com/Veloera/Veloera/commit/89136dfa) +- Add custom model input functionality in EditTagModal [`5f0322b6`](https://github.com/Veloera/Veloera/commit/5f0322b6) +- Update user group handling in Playground component [`379b08f6`](https://github.com/Veloera/Veloera/commit/379b08f6) +- Implement chat page state management in layout and sidebar [`afb7b661`](https://github.com/Veloera/Veloera/commit/afb7b661) +- Add renderModelPriceSimple function and update LogsTable component [`60710d6c`](https://github.com/Veloera/Veloera/commit/60710d6c) +- Update dependencies and restructure Playground component [`7cab9d7c`](https://github.com/Veloera/Veloera/commit/7cab9d7c) +- Enhance EditRedemption component with default name handling [`713de36e`](https://github.com/Veloera/Veloera/commit/713de36e) +- 首页优化 [`64e085dc`](https://github.com/Veloera/Veloera/commit/64e085dc) +- 侧边栏移动端优化 [`3622c664`](https://github.com/Veloera/Veloera/commit/3622c664) +- 优化playground搜索模型功能 [`18a8216a`](https://github.com/Veloera/Veloera/commit/18a8216a) +- update playground roleConfig [`2c79811c`](https://github.com/Veloera/Veloera/commit/2c79811c) +- support Azure Comm Service SMTP [`568d4e3f`](https://github.com/Veloera/Veloera/commit/568d4e3f) +- 兼容渠道搜索下标签聚合功能 [`aa82adc5`](https://github.com/Veloera/Veloera/commit/aa82adc5) +- add gemini tool_calls finish reason [`195ab1fd`](https://github.com/Veloera/Veloera/commit/195ab1fd) +- add deepseek channel type [`be556a23`](https://github.com/Veloera/Veloera/commit/be556a23) +- update go-epay [`98373f48`](https://github.com/Veloera/Veloera/commit/98373f48) +- support br [`4c809277`](https://github.com/Veloera/Veloera/commit/4c809277) +- support gzip [`3089af6b`](https://github.com/Veloera/Veloera/commit/3089af6b) +- add Cache-Control header to API requests [`de9a0d65`](https://github.com/Veloera/Veloera/commit/de9a0d65) +- add tag aggregation mode to channels API and UI [`88b0e6a7`](https://github.com/Veloera/Veloera/commit/88b0e6a7) +- 完善标签编辑(优先级,权重) [`6693072c`](https://github.com/Veloera/Veloera/commit/6693072c) +- 完善标签编辑 [`3053d941`](https://github.com/Veloera/Veloera/commit/3053d941) +- 完善标签编辑 [`9c4d3060`](https://github.com/Veloera/Veloera/commit/9c4d3060) +- add claude-3-5-haiku-20241022 [`999ba113`](https://github.com/Veloera/Veloera/commit/999ba113) +- support audio response_format #580 [`15842163`](https://github.com/Veloera/Veloera/commit/15842163) +- 暂时禁用透传功能 [`ed2ec695`](https://github.com/Veloera/Veloera/commit/ed2ec695) +- 暂时禁用透传功能 [`a167dd9a`](https://github.com/Veloera/Veloera/commit/a167dd9a) +- 一键编辑标签下渠道重定向 [`6e6e390f`](https://github.com/Veloera/Veloera/commit/6e6e390f) +- 渠道标签分组 [`0ce600ed`](https://github.com/Veloera/Veloera/commit/0ce600ed) +- add support for gemini-exp-1114 model / 添加 gemini-exp-1114 模型支持 [`46019329`](https://github.com/Veloera/Veloera/commit/46019329) +- 增加`GLOBAL_API_RATE_LIMIT_ENABLE`与`GLOBAL_WEB_RATE_LIMIT_ENABLE`环境变量,支持是否开启访问速率控制 [`08023f6d`](https://github.com/Veloera/Veloera/commit/08023f6d) +- 增加`GLOBAL_API_RATE_LIMIT_DURATION`与`GLOBAL_WEB_RATE_LIMIT_DURATION`环境变量,支持控制访问速率时间设置 [`fad29a8c`](https://github.com/Veloera/Veloera/commit/fad29a8c) +- 优化数据管理操作栏均为顶部 [`67d09d68`](https://github.com/Veloera/Veloera/commit/67d09d68) +- 优化switch组件的大小规格与整体表单一致 [`cdc02f66`](https://github.com/Veloera/Veloera/commit/cdc02f66) +- 统一运营设置页面的保存按钮大小规格 [`674abe5a`](https://github.com/Veloera/Veloera/commit/674abe5a) +- update LinuxDo icon [`66fa020b`](https://github.com/Veloera/Veloera/commit/66fa020b) +- playground用户分组设为默认选项 [`e291bb02`](https://github.com/Veloera/Veloera/commit/e291bb02) +- integrate Linux DO OAuth authentication [`046f859d`](https://github.com/Veloera/Veloera/commit/046f859d) +- realtime扣费时检测令牌额度 [`41311833`](https://github.com/Veloera/Veloera/commit/41311833) +- 完善audio倍率 [`3b53a2a5`](https://github.com/Veloera/Veloera/commit/3b53a2a5) +- 完善audio计费 [`97fdcd8e`](https://github.com/Veloera/Veloera/commit/97fdcd8e) +- update model ratio [`cbf0688b`](https://github.com/Veloera/Veloera/commit/cbf0688b) +- 美化日志页面 [`b40c2e10`](https://github.com/Veloera/Veloera/commit/b40c2e10) +- 日志详情完善 [`ee04dbd9`](https://github.com/Veloera/Veloera/commit/ee04dbd9) +- realtime pre consume [`e5c05d77`](https://github.com/Veloera/Veloera/commit/e5c05d77) +- realtime pre consume [`24b3ed50`](https://github.com/Veloera/Veloera/commit/24b3ed50) +- azure realtime [`8de79382`](https://github.com/Veloera/Veloera/commit/8de79382) +- realtime [`74f9006b`](https://github.com/Veloera/Veloera/commit/74f9006b) +- realtime [`33af069f`](https://github.com/Veloera/Veloera/commit/33af069f) +- 添加Mistral渠道 (close #546) [`4b48e490`](https://github.com/Veloera/Veloera/commit/4b48e490) +- support gpt-4o-audio-preview [`139a104b`](https://github.com/Veloera/Veloera/commit/139a104b) +- aws claude tools [`65e65097`](https://github.com/Veloera/Veloera/commit/65e65097) +- update claude models [`312ab448`](https://github.com/Veloera/Veloera/commit/312ab448) +- 上游渠道为OpenAI渠道类型时,透传请求 (close #532) [`8b676649`](https://github.com/Veloera/Veloera/commit/8b676649) +- 完善自定义聊天配置 [`d6359ec4`](https://github.com/Veloera/Veloera/commit/d6359ec4) +- 弃用旧的聊天配置 [`89ddf83b`](https://github.com/Veloera/Veloera/commit/89ddf83b) +- playground token name [`e298f2e5`](https://github.com/Veloera/Veloera/commit/e298f2e5) +- support embedding encoding_format param [`8cea6dff`](https://github.com/Veloera/Veloera/commit/8cea6dff) +- update aws claude [`5035cd05`](https://github.com/Veloera/Veloera/commit/5035cd05) +- update auto disable [`02c0c650`](https://github.com/Veloera/Veloera/commit/02c0c650) +- update model ratio [`f0b808a4`](https://github.com/Veloera/Veloera/commit/f0b808a4) +- update model ratio [`31d84ee3`](https://github.com/Veloera/Veloera/commit/31d84ee3) +- update model ratio [`9969ed2d`](https://github.com/Veloera/Veloera/commit/9969ed2d) +- 优化playground样式 [`04a68a85`](https://github.com/Veloera/Veloera/commit/04a68a85) +- Playground相关接口禁用AccessToken [`0cf53ac5`](https://github.com/Veloera/Veloera/commit/0cf53ac5) +- playground [`9a4ca1e2`](https://github.com/Veloera/Veloera/commit/9a4ca1e2) +- 更新令牌生成算法 [`0f95502b`](https://github.com/Veloera/Veloera/commit/0f95502b) +- 更新令牌生成算法 [`b58b1dc0`](https://github.com/Veloera/Veloera/commit/b58b1dc0) +- 不自动生成系统访问令牌 [`05d9aa61`](https://github.com/Veloera/Veloera/commit/05d9aa61) +- 添加.env配置文件和初始化环境变量 [`84f40b63`](https://github.com/Veloera/Veloera/commit/84f40b63) +- pricing page support multi groups #487 [`ed972eef`](https://github.com/Veloera/Veloera/commit/ed972eef) +- 无可选分组时关闭令牌分组功能 #485 [`c6ff785a`](https://github.com/Veloera/Veloera/commit/c6ff785a) +- update gemini flash completion ratio #479 [`af33f36c`](https://github.com/Veloera/Veloera/commit/af33f36c) +- update gemini completion ratio #479 [`3aa86a8c`](https://github.com/Veloera/Veloera/commit/3aa86a8c) +- 令牌分组 [`052bc207`](https://github.com/Veloera/Veloera/commit/052bc207) +- 添加令牌ip白名单功能 [`f505afdc`](https://github.com/Veloera/Veloera/commit/f505afdc) +- 优化界面显示 [`feb1d769`](https://github.com/Veloera/Veloera/commit/feb1d769) +- format o1 model max tokens param [`13c993d8`](https://github.com/Veloera/Veloera/commit/13c993d8) +- support o1 channel test [`cb738893`](https://github.com/Veloera/Veloera/commit/cb738893) +- support o1 channel test [`804aad3f`](https://github.com/Veloera/Veloera/commit/804aad3f) +- support OpenAI o1-preview and o1-mini [`3af62a3e`](https://github.com/Veloera/Veloera/commit/3af62a3e) +- support ollama multi-text embedding [`0cbf8e07`](https://github.com/Veloera/Veloera/commit/0cbf8e07) +- claude response return model name [`2650ec9b`](https://github.com/Veloera/Veloera/commit/2650ec9b) +- update chatgpt-4o token encoder [`e3b3fdec`](https://github.com/Veloera/Veloera/commit/e3b3fdec) +- remove lobe chat link #457 [`5863aa80`](https://github.com/Veloera/Veloera/commit/5863aa80) +- support jina embedding [`0830ef33`](https://github.com/Veloera/Veloera/commit/0830ef33) +- support more zhipu models [`97c18d0c`](https://github.com/Veloera/Veloera/commit/97c18d0c) +- support siliconflow embedding #447 [`4b1e83c4`](https://github.com/Veloera/Veloera/commit/4b1e83c4) +- 检测vertex渠道部署地区是否填写 [`01fd8b53`](https://github.com/Veloera/Veloera/commit/01fd8b53) +- 支持vertex ai渠道多个部署地区 [`e60f2001`](https://github.com/Veloera/Veloera/commit/e60f2001) +- support vertex ai #377 [`ac4262c5`](https://github.com/Veloera/Veloera/commit/ac4262c5) +- format claude messages when first role is not user [`a8ac8a25`](https://github.com/Veloera/Veloera/commit/a8ac8a25) +- rerank model mapping (close #444) [`144513f1`](https://github.com/Veloera/Veloera/commit/144513f1) +- support SiliconFlow (close #437, close #403) [`7c4d9d22`](https://github.com/Veloera/Veloera/commit/7c4d9d22) +- support gpt-4o-gizmo-* (close #436) [`d0f76a5c`](https://github.com/Veloera/Veloera/commit/d0f76a5c) +- update openai models list [`748e34fd`](https://github.com/Veloera/Veloera/commit/748e34fd) +- 避免暴露内部错误 [`f9392ca9`](https://github.com/Veloera/Veloera/commit/f9392ca9) +- update chatgpt-4o-latest model ratio [`1988c418`](https://github.com/Veloera/Veloera/commit/1988c418) +- update claude tools calling [`6cb0eb4b`](https://github.com/Veloera/Veloera/commit/6cb0eb4b) +- 区分额度不足和预扣费失败提示 [`4b5303a7`](https://github.com/Veloera/Veloera/commit/4b5303a7) +- 区分额度不足和预扣费失败提示 [`6eab0cc3`](https://github.com/Veloera/Veloera/commit/6eab0cc3) +- support gpt-4o-2024-08-06 [`93c6d765`](https://github.com/Veloera/Veloera/commit/93c6d765) +- log user id [`67878731`](https://github.com/Veloera/Veloera/commit/67878731) +- 优化充值订单号 [`88ba8a84`](https://github.com/Veloera/Veloera/commit/88ba8a84) +- 优化Gemini模型版本获取逻辑 [`e504665f`](https://github.com/Veloera/Veloera/commit/e504665f) +- 优化rpm查询 [`58b4c237`](https://github.com/Veloera/Veloera/commit/58b4c237) +- 优化日志查初始时间 [`54f6e660`](https://github.com/Veloera/Veloera/commit/54f6e660) +- 优化日志查询条件 [`3b1745c7`](https://github.com/Veloera/Veloera/commit/3b1745c7) +- 日志新增rpm和tpm数据。(close #384) [`c92ab3b5`](https://github.com/Veloera/Veloera/commit/c92ab3b5) +- ignore npm build dir [`fe16d51f`](https://github.com/Veloera/Veloera/commit/fe16d51f) +- support dify agent [`fbce3623`](https://github.com/Veloera/Veloera/commit/fbce3623) +- print user id when error [`b7bc205b`](https://github.com/Veloera/Veloera/commit/b7bc205b) +- support ollama tools [`88cc88c5`](https://github.com/Veloera/Veloera/commit/88cc88c5) +- print user id when error [`ab1d61d9`](https://github.com/Veloera/Veloera/commit/ab1d61d9) +- update log search [`12da7f64`](https://github.com/Veloera/Veloera/commit/12da7f64) +- update stream_options again [`9ef3212e`](https://github.com/Veloera/Veloera/commit/9ef3212e) +- update stream_options [`20da8228`](https://github.com/Veloera/Veloera/commit/20da8228) +- update stream_options [`436d08b4`](https://github.com/Veloera/Veloera/commit/436d08b4) +- support gpt-4o-mini image tokens [`e2cf6b1e`](https://github.com/Veloera/Veloera/commit/e2cf6b1e) +- update model ratio [`56afe47a`](https://github.com/Veloera/Veloera/commit/56afe47a) +- update model ratio [`67b74ada`](https://github.com/Veloera/Veloera/commit/67b74ada) +- support ali image [`c9100b21`](https://github.com/Veloera/Veloera/commit/c9100b21) +- support gemini tool calling (close #368) [`f96291a2`](https://github.com/Veloera/Veloera/commit/f96291a2) +- add UPDATE_TASK env [`14bf8650`](https://github.com/Veloera/Veloera/commit/14bf8650) +- 媒体请求计费选项 [`ae00a99c`](https://github.com/Veloera/Veloera/commit/ae00a99c) +- support claude tool calling [`11fd9935`](https://github.com/Veloera/Veloera/commit/11fd9935) +- support cloudflare audio [`ebb9b675`](https://github.com/Veloera/Veloera/commit/ebb9b675) +- update register page [`eb9b4b07`](https://github.com/Veloera/Veloera/commit/eb9b4b07) +- support cloudflare worker ai [`7b36a2b8`](https://github.com/Veloera/Veloera/commit/7b36a2b8) +- support claude stop_sequences [`c88f3741`](https://github.com/Veloera/Veloera/commit/c88f3741) +- update stream options [`0526c857`](https://github.com/Veloera/Veloera/commit/0526c857) +- update FORCE_STREAM_OPTION default value [`a984daa5`](https://github.com/Veloera/Veloera/commit/a984daa5) +- 允许设置是否检测mj任务已完成才可进行action操作 (close #349) [`03b130f2`](https://github.com/Veloera/Veloera/commit/03b130f2) +- able to use email to login (close #343,#348) [`45b9de9d`](https://github.com/Veloera/Veloera/commit/45b9de9d) +- 完善stream_options [`52debe75`](https://github.com/Veloera/Veloera/commit/52debe75) +- 完善stream_options [`df650273`](https://github.com/Veloera/Veloera/commit/df650273) +- support aws stream_options [`9896ba0a`](https://github.com/Veloera/Veloera/commit/9896ba0a) +- support claude stream_options [`e8b93ed6`](https://github.com/Veloera/Veloera/commit/e8b93ed6) +- support stream_options [`b0e234e8`](https://github.com/Veloera/Veloera/commit/b0e234e8) +- add env DIFY_DEBUG [`20d71711`](https://github.com/Veloera/Veloera/commit/20d71711) +- support jina rerank [`8a730cfe`](https://github.com/Veloera/Veloera/commit/8a730cfe) +- support cohere rerank [`8af4e28f`](https://github.com/Veloera/Veloera/commit/8af4e28f) +- support dify (close #299) [`e0ed59bf`](https://github.com/Veloera/Veloera/commit/e0ed59bf) +- 记录兑换时兑换码的ID (close #286) [`bd722211`](https://github.com/Veloera/Veloera/commit/bd722211) +- 记录渠道测试的消费日志 (close #334) [`cf3d8941`](https://github.com/Veloera/Veloera/commit/cf3d8941) +- 统计无限令牌的已用额度 (close #308) [`70110832`](https://github.com/Veloera/Veloera/commit/70110832) +- 统计无限令牌的已用额度 (close #308) [`752048df`](https://github.com/Veloera/Veloera/commit/752048df) +- update baidu [`eb382d28`](https://github.com/Veloera/Veloera/commit/eb382d28) +- log mj task id [`d306aea9`](https://github.com/Veloera/Veloera/commit/d306aea9) +- 完善日志扣费计算过程 [`584eefec`](https://github.com/Veloera/Veloera/commit/584eefec) +- support cohere first response time [`a7e3168c`](https://github.com/Veloera/Veloera/commit/a7e3168c) +- 支持设置流模式超时时间(gemini, claude) [`402a415c`](https://github.com/Veloera/Veloera/commit/402a415c) +- 支持设置流模式超时时间 [`fc6ae6bf`](https://github.com/Veloera/Veloera/commit/fc6ae6bf) +- support Spark4.0 Ultra [`d1778bb2`](https://github.com/Veloera/Veloera/commit/d1778bb2) +- first response time support aws [`11171122`](https://github.com/Veloera/Veloera/commit/11171122) +- first response time support gemini and claude [`f2654692`](https://github.com/Veloera/Veloera/commit/f2654692) +- 记录流模式首字时间 (close #323) [`79010dbf`](https://github.com/Veloera/Veloera/commit/79010dbf) +- only update task on master node [`cadd8aa6`](https://github.com/Veloera/Veloera/commit/cadd8aa6) +- auto ban 403 [`e3f66807`](https://github.com/Veloera/Veloera/commit/e3f66807) +- log channel status update time [`e8845ce1`](https://github.com/Veloera/Veloera/commit/e8845ce1) +- only update midjourney task on master node [`4eb6217b`](https://github.com/Veloera/Veloera/commit/4eb6217b) +- 记录自动禁用原因 (close #300) [`eb798805`](https://github.com/Veloera/Veloera/commit/eb798805) +- suno api 支持 [`c993ab27`](https://github.com/Veloera/Veloera/commit/c993ab27) +- update token encoder [`ecdcb379`](https://github.com/Veloera/Veloera/commit/ecdcb379) +- update tiktoken [`d2a0d9f7`](https://github.com/Veloera/Veloera/commit/d2a0d9f7) +- 支持设置worker访问请求中的图片地址 [`099068f5`](https://github.com/Veloera/Veloera/commit/099068f5) +- 添加自定义渠道提示 [`fa902cca`](https://github.com/Veloera/Veloera/commit/fa902cca) +- 添加自定义渠道提示 [`0c869681`](https://github.com/Veloera/Veloera/commit/0c869681) +- 增加重置模型倍率功能 (close #62) [`36fac2ba`](https://github.com/Veloera/Veloera/commit/36fac2ba) +- 完善获取模型列表功能 (close #237) [`0867d36f`](https://github.com/Veloera/Veloera/commit/0867d36f) +- update SettingsMagnification [`d6c1e3f3`](https://github.com/Veloera/Veloera/commit/d6c1e3f3) +- update model ratio [`774ce719`](https://github.com/Veloera/Veloera/commit/774ce719) +- update model ratio [`dbaa9390`](https://github.com/Veloera/Veloera/commit/dbaa9390) +- 自定义渠道功能变更 (#262) [`84da8850`](https://github.com/Veloera/Veloera/commit/84da8850) +- support minimax [`039fda91`](https://github.com/Veloera/Veloera/commit/039fda91) +- support minimax [`e0df8bbb`](https://github.com/Veloera/Veloera/commit/e0df8bbb) +- pre to delete custom channel type [`5e07ff85`](https://github.com/Veloera/Veloera/commit/5e07ff85) +- 日志显示重试信息 [`71dcf43c`](https://github.com/Veloera/Veloera/commit/71dcf43c) +- 完善模型价格获取逻辑 [`93858c32`](https://github.com/Veloera/Veloera/commit/93858c32) +- 完善模型价格页面 [`ff044de4`](https://github.com/Veloera/Veloera/commit/ff044de4) +- update model ratio [`eda3bd1c`](https://github.com/Veloera/Veloera/commit/eda3bd1c) +- update model ratio [`475dea96`](https://github.com/Veloera/Veloera/commit/475dea96) +- add pricing page [`5715fcf8`](https://github.com/Veloera/Veloera/commit/5715fcf8) +- dalle系列改为使用模型固定价格计费 [`71547849`](https://github.com/Veloera/Veloera/commit/71547849) +- 完善日志详情 [`39f6812a`](https://github.com/Veloera/Veloera/commit/39f6812a) +- 支持自定义特殊模型补全倍率 [`e8800415`](https://github.com/Veloera/Veloera/commit/e8800415) +- 只自动启用被自动禁用的渠道 (close #224) [`ecd06cf2`](https://github.com/Veloera/Veloera/commit/ecd06cf2) +- 填入相关模型 [`2dbf50dc`](https://github.com/Veloera/Veloera/commit/2dbf50dc) +- 编辑额度支持负数 [`d8c00604`](https://github.com/Veloera/Veloera/commit/d8c00604) +- 更方便地编辑用户额度 [`b427f027`](https://github.com/Veloera/Veloera/commit/b427f027) +- 日志详情展示模型价格 [`6fb1fbfe`](https://github.com/Veloera/Veloera/commit/6fb1fbfe) +- 限制邮箱别名 [`4641d446`](https://github.com/Veloera/Veloera/commit/4641d446) +- update model ratio [`d7a343e2`](https://github.com/Veloera/Veloera/commit/d7a343e2) +- log completionRatio [`16b9aacb`](https://github.com/Veloera/Veloera/commit/16b9aacb) +- able to set AccountFilter [`cad380eb`](https://github.com/Veloera/Veloera/commit/cad380eb) +- update midjourney task info update timeout [`234e39dd`](https://github.com/Veloera/Veloera/commit/234e39dd) +- safe send channel [`21f32605`](https://github.com/Veloera/Veloera/commit/21f32605) +- log status code [`be4809b9`](https://github.com/Veloera/Veloera/commit/be4809b9) +- claude 整理prompt [`a14fa1ad`](https://github.com/Veloera/Veloera/commit/a14fa1ad) +- update model ratio [`20aaf307`](https://github.com/Veloera/Veloera/commit/20aaf307) +- support cohere (close #195) [`bfcaccc2`](https://github.com/Veloera/Veloera/commit/bfcaccc2) +- dalle系列日志记录更多信息 [`3f448ba4`](https://github.com/Veloera/Veloera/commit/3f448ba4) +- 完善函数计费 [`28416692`](https://github.com/Veloera/Veloera/commit/28416692) +- update shouldRetry [`89ebd855`](https://github.com/Veloera/Veloera/commit/89ebd855) +- 自动整理claude不规范prompt [`1a39ef74`](https://github.com/Veloera/Veloera/commit/1a39ef74) +- support aws claude [`92941276`](https://github.com/Veloera/Veloera/commit/92941276) +- 支持ollama embedding数组传参 [`6b97842f`](https://github.com/Veloera/Veloera/commit/6b97842f) +- 启用函数计费 [`bdc65bdb`](https://github.com/Veloera/Veloera/commit/bdc65bdb) +- update gemini model [`76dc7af8`](https://github.com/Veloera/Veloera/commit/76dc7af8) +- 登陆美化 [`892b7d1a`](https://github.com/Veloera/Veloera/commit/892b7d1a) +- 状态码复写 [`6b71db7c`](https://github.com/Veloera/Veloera/commit/6b71db7c) +- 在重试时打印重试信息 [`b8fb351f`](https://github.com/Veloera/Veloera/commit/b8fb351f) +- update cache [`e6765ef3`](https://github.com/Veloera/Veloera/commit/e6765ef3) +- update cache [`4ef98ba7`](https://github.com/Veloera/Veloera/commit/4ef98ba7) +- update cache #204 [`65b85377`](https://github.com/Veloera/Veloera/commit/65b85377) +- 完善数据看板 #190 [`c6e85d5b`](https://github.com/Veloera/Veloera/commit/c6e85d5b) +- 可设置是否转发上游mj图片地址 [`1162683b`](https://github.com/Veloera/Veloera/commit/1162683b) +- 前端不显示敏感信息 [`818bd824`](https://github.com/Veloera/Veloera/commit/818bd824) +- support google v1beta and Gemini Ultra [`21250a46`](https://github.com/Veloera/Veloera/commit/21250a46) +- support gpt-4-turbo [`f1881476`](https://github.com/Veloera/Veloera/commit/f1881476) +- automatically ban channels that exceeded quota [`a7cfce24`](https://github.com/Veloera/Veloera/commit/a7cfce24) +- 支持未开启缓存下本地重试 [`462c328d`](https://github.com/Veloera/Veloera/commit/462c328d) +- 超时状态码不重试 [`fed1a1d6`](https://github.com/Veloera/Veloera/commit/fed1a1d6) +- 钱包兼容非货币形式显示额度 [`1cd1e54b`](https://github.com/Veloera/Veloera/commit/1cd1e54b) +- 钱包兼容非货币形式显示额度 [`3db64afc`](https://github.com/Veloera/Veloera/commit/3db64afc) +- 钱包兼容非货币形式显示额度 [`bc9cfa5d`](https://github.com/Veloera/Veloera/commit/bc9cfa5d) +- able to set default test model (#138) [`660b9b3c`](https://github.com/Veloera/Veloera/commit/660b9b3c) +- 本地重试 [`4b60528c`](https://github.com/Veloera/Veloera/commit/4b60528c) +- add Claude TopK [`278fd391`](https://github.com/Veloera/Veloera/commit/278fd391) +- 支持 /mj-{mode} 路径 [`49df4b6e`](https://github.com/Veloera/Veloera/commit/49df4b6e) +- able to set smtp ssl [`5c39f540`](https://github.com/Veloera/Veloera/commit/5c39f540) +- 开启redis的情况下设置SYNC_FREQUENCY默认为60 [`786ccc7d`](https://github.com/Veloera/Veloera/commit/786ccc7d) +- support ollama embedding [`8eedad94`](https://github.com/Veloera/Veloera/commit/8eedad94) +- 进一步防止暴露数上游以及数据库地址 [`d8c91fa4`](https://github.com/Veloera/Veloera/commit/d8c91fa4) +- support gemini-1.5 [`1587ea56`](https://github.com/Veloera/Veloera/commit/1587ea56) +- remove azure model TrimPrefix [`a7a1fc61`](https://github.com/Veloera/Veloera/commit/a7a1fc61) +- prettier [`15e73073`](https://github.com/Veloera/Veloera/commit/15e73073) +- 从本地读取字体 (close #130) [`525fc1b3`](https://github.com/Veloera/Veloera/commit/525fc1b3) +- 首页加载速度优化 [`58f2cf3a`](https://github.com/Veloera/Veloera/commit/58f2cf3a) +- vite [`93be61aa`](https://github.com/Veloera/Veloera/commit/93be61aa) +- 统一错误提示 [`a232afe9`](https://github.com/Veloera/Veloera/commit/a232afe9) +- 保留功能 [`2db42826`](https://github.com/Veloera/Veloera/commit/2db42826) +- 初步兼容生成内容检查 [`64b9d3b5`](https://github.com/Veloera/Veloera/commit/64b9d3b5) +- 初步兼容敏感词过滤 [`7a663d26`](https://github.com/Veloera/Veloera/commit/7a663d26) +- support perplexity [`beadb98a`](https://github.com/Veloera/Veloera/commit/beadb98a) +- support perplexity [`578b5f65`](https://github.com/Veloera/Veloera/commit/578b5f65) +- midjourneys表添加索引 [`f82aa956`](https://github.com/Veloera/Veloera/commit/f82aa956) +- print midjourney-proxy request error [`4b952b85`](https://github.com/Veloera/Veloera/commit/4b952b85) +- 添加回调未开启提示 [`a6ba1d01`](https://github.com/Veloera/Veloera/commit/a6ba1d01) +- Add logs page-size [`25ec9991`](https://github.com/Veloera/Veloera/commit/25ec9991) +- Only update weight and priority when blur [`7f22d585`](https://github.com/Veloera/Veloera/commit/7f22d585) +- Remember and automatically set page-size (close #118) [`dfdeadf1`](https://github.com/Veloera/Veloera/commit/dfdeadf1) +- 允许开关mj回调 [`4917e5a9`](https://github.com/Veloera/Veloera/commit/4917e5a9) +- support InsightFace (close #60) [`9b5353a8`](https://github.com/Veloera/Veloera/commit/9b5353a8) +- support image-seed (close #86) [`bc5a54df`](https://github.com/Veloera/Veloera/commit/bc5a54df) +- 兼容自定义变焦,完善modal操作 [`d704902b`](https://github.com/Veloera/Veloera/commit/d704902b) +- 超过一小时的任务自动失败 [`614220a0`](https://github.com/Veloera/Veloera/commit/614220a0) +- support Anthropic Claude 3.0 Haiku (close #116) [`98c1f66d`](https://github.com/Veloera/Veloera/commit/98c1f66d) +- 将操作拆分成单独的模型 [`3d10c9f0`](https://github.com/Veloera/Veloera/commit/3d10c9f0) +- 操作细分 [`d5ffaf25`](https://github.com/Veloera/Veloera/commit/d5ffaf25) +- support shorten [`2ad59141`](https://github.com/Veloera/Veloera/commit/2ad59141) +- 兼容变焦功能 [`728dbed2`](https://github.com/Veloera/Veloera/commit/728dbed2) +- 请求超时处理 [`fd3a41ba`](https://github.com/Veloera/Veloera/commit/fd3a41ba) +- 初步兼容midjourney-proxy-plus [`37c0c8eb`](https://github.com/Veloera/Veloera/commit/37c0c8eb) +- add parameter top_k [`c0123064`](https://github.com/Veloera/Veloera/commit/c0123064) +- support ollama (close #112) [`d53d3386`](https://github.com/Veloera/Veloera/commit/d53d3386) +- 前端美化 [`c29680b3`](https://github.com/Veloera/Veloera/commit/c29680b3) +- update claude default model ratio [`655dfe0d`](https://github.com/Veloera/Veloera/commit/655dfe0d) +- support Claude 3 [`4a0af1ea`](https://github.com/Veloera/Veloera/commit/4a0af1ea) +- support claude3 not stream [`c2965eb8`](https://github.com/Veloera/Veloera/commit/c2965eb8) +- 添加充值记录按钮 [`280c63e1`](https://github.com/Veloera/Veloera/commit/280c63e1) +- 支持部分渠道的system角色 (close #89) [`3ab4f145`](https://github.com/Veloera/Veloera/commit/3ab4f145) +- add "/api/status/test" [`fe7f42fc`](https://github.com/Veloera/Veloera/commit/fe7f42fc) +- 记录请求的token用量 [`912f46fc`](https://github.com/Veloera/Veloera/commit/912f46fc) +- 解决渠道密钥长度限制问题 [`1ad11123`](https://github.com/Veloera/Veloera/commit/1ad11123) +- update SparkDesk model ratio [`a16e9493`](https://github.com/Veloera/Veloera/commit/a16e9493) +- 可自定义支付回调地址及最低购买数量 [`72194bda`](https://github.com/Veloera/Veloera/commit/72194bda) +- 数据看板颜色统一 [`d6d8ad2b`](https://github.com/Veloera/Veloera/commit/d6d8ad2b) +- telegram login and bind [`84144306`](https://github.com/Veloera/Veloera/commit/84144306) +- 可设置默认折叠侧边栏 [`a8f0c5da`](https://github.com/Veloera/Veloera/commit/a8f0c5da) +- 修复智谱GLM-4V流模式异常 [`84cac72a`](https://github.com/Veloera/Veloera/commit/84cac72a) +- Add channel search by model field (close #72) [`413d4f0a`](https://github.com/Veloera/Veloera/commit/413d4f0a) +- "/v1/models" 只返回用户可用模型 (close #78) [`feb40db2`](https://github.com/Veloera/Veloera/commit/feb40db2) +- 支持智谱GLM-4V [`b4645d10`](https://github.com/Veloera/Veloera/commit/b4645d10) +- 初步重构完成 [`6013219f`](https://github.com/Veloera/Veloera/commit/6013219f) +- 初步重构 [`5b18cd6b`](https://github.com/Veloera/Veloera/commit/5b18cd6b) +- 未配置在线支付时,限制用户输入金额 (close #74) [`608c945a`](https://github.com/Veloera/Veloera/commit/608c945a) +- 在线支付限制输入金额 [`05beade3`](https://github.com/Veloera/Veloera/commit/05beade3) +- 记录更多的错误信息 [`81167c43`](https://github.com/Veloera/Veloera/commit/81167c43) +- support logprobs [`6fa1837c`](https://github.com/Veloera/Veloera/commit/6fa1837c) +- 优化令牌无效提示 [`ba0c5bb4`](https://github.com/Veloera/Veloera/commit/ba0c5bb4) +- add model gpt-3.5-turbo-0125 [`a0c4e9f1`](https://github.com/Veloera/Veloera/commit/a0c4e9f1) +- Happy New Year [`d6d91e43`](https://github.com/Veloera/Veloera/commit/d6d91e43) +- 添加成功时自动启用通道功能, close #27 [`5c8f8b49`](https://github.com/Veloera/Veloera/commit/5c8f8b49) +- 支持测试渠道时自选模型 [`affe5111`](https://github.com/Veloera/Veloera/commit/affe5111) +- add model text-embedding-3 [`364d4f96`](https://github.com/Veloera/Veloera/commit/364d4f96) +- add model gpt-4-turbo-preview [`bce8a3e2`](https://github.com/Veloera/Veloera/commit/bce8a3e2) +- update model-ratio [`3dc5d4d9`](https://github.com/Veloera/Veloera/commit/3dc5d4d9) +- token缓存逻辑更新(实验性) [`ac3e2785`](https://github.com/Veloera/Veloera/commit/ac3e2785) +- 数据看板加入每列总计 [`e8188902`](https://github.com/Veloera/Veloera/commit/e8188902) +- 优化vision计费逻辑 [`1ee8edcf`](https://github.com/Veloera/Veloera/commit/1ee8edcf) +- add midjourney price log [`a3921ea5`](https://github.com/Veloera/Veloera/commit/a3921ea5) +- 请求出现 0 token的时候,加入错误提示并打印日志 [`b3f46223`](https://github.com/Veloera/Veloera/commit/b3f46223) +- 令牌聊天新增ChatGPT Web & Midjourney支持 [`c59a33e8`](https://github.com/Veloera/Veloera/commit/c59a33e8) +- 美化日志详情 [`9bcd24fc`](https://github.com/Veloera/Veloera/commit/9bcd24fc) +- 日志优化逻辑,新增请求时间和是否为stream字段 [`cbdce181`](https://github.com/Veloera/Veloera/commit/cbdce181) +- able to change gemini safety setting [`aca8d253`](https://github.com/Veloera/Veloera/commit/aca8d253) +- able to fix channels [`6a24e895`](https://github.com/Veloera/Veloera/commit/6a24e895) +- support Azure dall-e [`75b6327f`](https://github.com/Veloera/Veloera/commit/75b6327f) +- 可设置令牌能调用的模型 [`1244963e`](https://github.com/Veloera/Veloera/commit/1244963e) +- 完善数据看板选择时间区间 [`1c2bba89`](https://github.com/Veloera/Veloera/commit/1c2bba89) +- 新增数据看板 [`bf8794d2`](https://github.com/Veloera/Veloera/commit/bf8794d2) +- 允许关闭绘图选项 [`c09df83f`](https://github.com/Veloera/Veloera/commit/c09df83f) +- add support for davinci-002 and babbage-002 [`afbd5850`](https://github.com/Veloera/Veloera/commit/afbd5850) +- 加入渠道加权随机功能 [`bdd611fd`](https://github.com/Veloera/Veloera/commit/bdd611fd) +- support xunfei v3 (#637) [`aec343dc`](https://github.com/Veloera/Veloera/commit/aec343dc) +- able to set RELAY_TIMEOUT [`89d458b9`](https://github.com/Veloera/Veloera/commit/89d458b9) +- support ERNIE-Bot-4 (close #608) [`63fafba1`](https://github.com/Veloera/Veloera/commit/63fafba1) +- add cloudflare ai gateway support for image & audio (#607) [`3b483639`](https://github.com/Veloera/Veloera/commit/3b483639) +- able to delete all disabled channels [`82444424`](https://github.com/Veloera/Veloera/commit/82444424) +- support cloudflare AI gateway now (close #565, #598) [`e28d4b17`](https://github.com/Veloera/Veloera/commit/e28d4b17) +- 新增充值分组倍率设置 [`a4e26926`](https://github.com/Veloera/Veloera/commit/a4e26926) +- 充值前弹窗确认 [`bd22fee1`](https://github.com/Veloera/Veloera/commit/bd22fee1) +- support Tencent's model (close #519) [`b4b4acc2`](https://github.com/Veloera/Veloera/commit/b4b4acc2) +- able to delete all manually disabled channels (close #539) [`8d34b7a7`](https://github.com/Veloera/Veloera/commit/8d34b7a7) +- 优先优先级排序渠道 [`d429992b`](https://github.com/Veloera/Veloera/commit/d429992b) +- 优先用优先级排序渠道 [`1bb65fa0`](https://github.com/Veloera/Veloera/commit/1bb65fa0) +- 防止渠道地址传到用户端 [`929cd79e`](https://github.com/Veloera/Veloera/commit/929cd79e) +- 渠道新可选是否自动禁用功能 [`b06c370f`](https://github.com/Veloera/Veloera/commit/b06c370f) +- 渠道新可选是否自动禁用功能 [`80271b33`](https://github.com/Veloera/Veloera/commit/80271b33) +- add support for gpt-3.5-turbo-instruct (close #545) [`f5a1cd34`](https://github.com/Veloera/Veloera/commit/f5a1cd34) +- 新增渠道页面选择每页显示数量 [`3595e352`](https://github.com/Veloera/Veloera/commit/3595e352) +- supprt channel priority now & record channel id in log (#484) [`ecf8a6d8`](https://github.com/Veloera/Veloera/commit/ecf8a6d8) +- support non-stream mode for xunfei (#498) [`24df3e5f`](https://github.com/Veloera/Veloera/commit/24df3e5f) +- able to delete logs now (close #486) [`328aa682`](https://github.com/Veloera/Veloera/commit/328aa682) +- create new log file when too many logs recorded [`4335f005`](https://github.com/Veloera/Veloera/commit/4335f005) +- 渠道支持指定组织 [`cc09dd4f`](https://github.com/Veloera/Veloera/commit/cc09dd4f) +- 避免请求出错时暴露渠道信息 [`d1d78961`](https://github.com/Veloera/Veloera/commit/d1d78961) +- 改为转发的方式获取midjourney图片 [`d9deda18`](https://github.com/Veloera/Veloera/commit/d9deda18) +- 改为转发的方式获取midjourney图片 [`84645128`](https://github.com/Veloera/Veloera/commit/84645128) +- add midjourney log [`4895bd74`](https://github.com/Veloera/Veloera/commit/4895bd74) +- 删除无用功能 [`4a836a4c`](https://github.com/Veloera/Veloera/commit/4a836a4c) +- able to config rate limit (close #477) [`bd6fe1e9`](https://github.com/Veloera/Veloera/commit/bd6fe1e9) +- add channel type FastGPT [`7e575abb`](https://github.com/Veloera/Veloera/commit/7e575abb) +- add batch update support (close #414) [`c3dc315e`](https://github.com/Veloera/Veloera/commit/c3dc315e) +- support aiproxy's library [`04acdb1c`](https://github.com/Veloera/Veloera/commit/04acdb1c) +- supper OpenRouter now (close #333, close #340) [`56b50073`](https://github.com/Veloera/Veloera/commit/56b50073) +- supper whisper now (close #197) [`d09d3174`](https://github.com/Veloera/Veloera/commit/d09d3174) +- support 360's models (close #331, close #461) [`5ee24e8a`](https://github.com/Veloera/Veloera/commit/5ee24e8a) +- support xunfei's v2 api (#442, close #440) [`7e058bfb`](https://github.com/Veloera/Veloera/commit/7e058bfb) +- show total quota consumption only when user click (#448) [`1b56becf`](https://github.com/Veloera/Veloera/commit/1b56becf) +- able to refresh baidu access token automatically (#400, close #401) [`af20063a`](https://github.com/Veloera/Veloera/commit/af20063a) +- support PostgreSQL now [`e0b4f96b`](https://github.com/Veloera/Veloera/commit/e0b4f96b) +- improve frontend (#387) [`c58f7102`](https://github.com/Veloera/Veloera/commit/c58f7102) +- add chat button for each token (#363) [`5a62357c`](https://github.com/Veloera/Veloera/commit/5a62357c) +- able to copy scheme of ama, opencat & chatgpt next web (#343) [`1dfa190e`](https://github.com/Veloera/Veloera/commit/1dfa190e) +- support email domain whitelist (#337) [`3fca6ff5`](https://github.com/Veloera/Veloera/commit/3fca6ff5) +- support xunfei's llm (close #206) [`8a866078`](https://github.com/Veloera/Veloera/commit/8a866078) +- support baidu's embedding model (close #324) [`130e6bfd`](https://github.com/Veloera/Veloera/commit/130e6bfd) +- support ali's llm (close #326) [`e92da792`](https://github.com/Veloera/Veloera/commit/e92da792) +- add access_until field for subscription api (#295) [`dccd66b8`](https://github.com/Veloera/Veloera/commit/dccd66b8) +- able to delete account by self (#294) [`2fcd6852`](https://github.com/Veloera/Veloera/commit/2fcd6852) +- support zhipu's ChatGLM (close #289) [`26c6719e`](https://github.com/Veloera/Veloera/commit/26c6719e) +- support Google PaLM2 (close #105) [`8f721d67`](https://github.com/Veloera/Veloera/commit/8f721d67) +- support baidu's models now (close #286) [`9a1db616`](https://github.com/Veloera/Veloera/commit/9a1db616) +- support claude now (close #150) [`2ff15baf`](https://github.com/Veloera/Veloera/commit/2ff15baf) +- retry on failed (close #112) [`35cfebee`](https://github.com/Veloera/Veloera/commit/35cfebee) +- support ChatGLM2 (close #274) [`0e088f7c`](https://github.com/Veloera/Veloera/commit/0e088f7c) +- support custom model now (close #276) [`ccf7709e`](https://github.com/Veloera/Veloera/commit/ccf7709e) +- add turnstile for login form (#263) [`d592e2c8`](https://github.com/Veloera/Veloera/commit/d592e2c8) +- initial support of Dall-E (#148, #266) [`b520b546`](https://github.com/Veloera/Veloera/commit/b520b546) +- add support for /v1/engines/text-embedding-ada-002/embeddings (#224, close #222) [`81c59011`](https://github.com/Veloera/Veloera/commit/81c59011) +- disable channel when account_deactivated received (close #271) [`abc53cb2`](https://github.com/Veloera/Veloera/commit/abc53cb2) +- able to approximate token (close #207) [`701aaba1`](https://github.com/Veloera/Veloera/commit/701aaba1) +- support balance query for CloseAI (#240) [`d383302e`](https://github.com/Veloera/Veloera/commit/d383302e) +- support channel type AIGC2D (#220) [`b1b3651e`](https://github.com/Veloera/Veloera/commit/b1b3651e) +- support model remap now [`0941e294`](https://github.com/Veloera/Veloera/commit/0941e294) +- support /v1/edits now (close #196) [`9b178a28`](https://github.com/Veloera/Veloera/commit/9b178a28) +- able to query token with admin user [`3ce982d8`](https://github.com/Veloera/Veloera/commit/3ce982d8) +- able to query logs now (close #144) [`cccf5e4a`](https://github.com/Veloera/Veloera/commit/cccf5e4a) +- add English support [`03c05bdb`](https://github.com/Veloera/Veloera/commit/03c05bdb) +- show used quota in token table [`9ac5410d`](https://github.com/Veloera/Veloera/commit/9ac5410d) +- able to display token billing stat via billing api (close #186) [`7edc2b53`](https://github.com/Veloera/Veloera/commit/7edc2b53) +- support automatic channel testing & balance updates (close #11, close #59) [`4463224f`](https://github.com/Veloera/Veloera/commit/4463224f) +- support search channels by key (close #185) [`ad1049b0`](https://github.com/Veloera/Veloera/commit/ad1049b0) +- do not access database before response return (close #158) [`6d961064`](https://github.com/Veloera/Veloera/commit/6d961064) +- select channel without database (#158) [`ba54c719`](https://github.com/Veloera/Veloera/commit/ba54c719) +- support custom base url for channels [`e398f470`](https://github.com/Veloera/Veloera/commit/e398f470) +- able to display quota in dollar [`b179c2f2`](https://github.com/Veloera/Veloera/commit/b179c2f2) +- use cache to avoid database access (#158) [`3d76a974`](https://github.com/Veloera/Veloera/commit/3d76a974) +- able to add chat page link now (close #70) [`d9764037`](https://github.com/Veloera/Veloera/commit/d9764037) +- support aff now (close #75) [`c5837c3b`](https://github.com/Veloera/Veloera/commit/c5837c3b) +- support API2GPT platform (#173) [`a43b1e2a`](https://github.com/Veloera/Veloera/commit/a43b1e2a) +- add token name to log (#172) [`46c43396`](https://github.com/Veloera/Veloera/commit/46c43396) +- support update AIProxy balance (#171) [`b7d71b4f`](https://github.com/Veloera/Veloera/commit/b7d71b4f) +- return a not found response if requested a wrong API endpoints [`70ed126c`](https://github.com/Veloera/Veloera/commit/70ed126c) +- record channel's used quota (close #137) [`0cdab80a`](https://github.com/Veloera/Veloera/commit/0cdab80a) +- record used quota & request count (close #102, #165) [`760183a9`](https://github.com/Veloera/Veloera/commit/760183a9) +- able to disable quota consumption recording (close #156) [`593e1926`](https://github.com/Veloera/Veloera/commit/593e1926) +- now one channel can belong to multiple groups (close #153) [`7f9577a3`](https://github.com/Veloera/Veloera/commit/7f9577a3) +- add new released models [`323f3d26`](https://github.com/Veloera/Veloera/commit/323f3d26) +- add support for updating balance of channel typpe OpenAI-SB (#146, close #125) [`47ca449e`](https://github.com/Veloera/Veloera/commit/47ca449e) +- able to set group ratio now (close #62, close #142) [`596446db`](https://github.com/Veloera/Veloera/commit/596446db) +- support /v1/moderations now (close #117) [`4339f45f`](https://github.com/Veloera/Veloera/commit/4339f45f) +- now able to check all user's log [`64db3932`](https://github.com/Veloera/Veloera/commit/64db3932) +- now user can check its topup & consume history (close #78, close #95) [`74f508e8`](https://github.com/Veloera/Veloera/commit/74f508e8) +- support set proxy for channel OpenAI (close #139) [`8901f038`](https://github.com/Veloera/Veloera/commit/8901f038) +- able to check topup history & consumption history (#78, #95) [`45e9fd66`](https://github.com/Veloera/Veloera/commit/45e9fd66) +- support /v1/completions (close #115) [`4b6adaec`](https://github.com/Veloera/Veloera/commit/4b6adaec) +- able to manage group now [`2c53424d`](https://github.com/Veloera/Veloera/commit/2c53424d) +- support group now (close #17, close #72, close #85, close #104, close #136) [`2ad22e14`](https://github.com/Veloera/Veloera/commit/2ad22e14) +- the format of key is now constant with that of OpenAI [`2847a088`](https://github.com/Veloera/Veloera/commit/2847a088) +- return user's quota with billing api (close #92) [`d4794fc0`](https://github.com/Veloera/Veloera/commit/d4794fc0) +- support channel remain quota query (close #79) [`171b8185`](https://github.com/Veloera/Veloera/commit/171b8185) +- PaLM support is WIP (#105) [`bcca0cc0`](https://github.com/Veloera/Veloera/commit/bcca0cc0) +- able to manage user's quota now [`61e682ca`](https://github.com/Veloera/Veloera/commit/61e682ca) +- support channel AI Proxy now [`cfd58711`](https://github.com/Veloera/Veloera/commit/cfd58711) +- support channel ai.ls now (close #99) [`3711f4a7`](https://github.com/Veloera/Veloera/commit/3711f4a7) +- now slave server can sync options with master server (close #88) [`2d39a135`](https://github.com/Veloera/Veloera/commit/2d39a135) +- support redirecting frontend url now (close #89) [`3c6834a7`](https://github.com/Veloera/Veloera/commit/3c6834a7) +- support dummy sk- prefix for token (#82) [`73d5e0f2`](https://github.com/Veloera/Veloera/commit/73d5e0f2) +- API `/models` & `/models/:model` implemented (close #68) [`efc744ca`](https://github.com/Veloera/Veloera/commit/efc744ca) +- support max_tokens now (#52) [`c9ac5e39`](https://github.com/Veloera/Veloera/commit/c9ac5e39) +- disable operations for root user (close #76) [`69cf1de7`](https://github.com/Veloera/Veloera/commit/69cf1de7) +- able to set pre consumed quota now [`4d6172a2`](https://github.com/Veloera/Veloera/commit/4d6172a2) +- add database migration script [`6d1e5cb5`](https://github.com/Veloera/Veloera/commit/6d1e5cb5) +- show users' remaining quota in user management page (close #46) [`7c56a36a`](https://github.com/Veloera/Veloera/commit/7c56a36a) +- automatically disable channel when error occurred (#59) [`a1f61384`](https://github.com/Veloera/Veloera/commit/a1f61384) +- add refresh button [`44ebae15`](https://github.com/Veloera/Veloera/commit/44ebae15) +- able to test all enabled channels (#59) [`d267211e`](https://github.com/Veloera/Veloera/commit/d267211e) +- save response time & test time (#59) [`225176aa`](https://github.com/Veloera/Veloera/commit/225176aa) +- able to test channels now (#59) [`443a22b7`](https://github.com/Veloera/Veloera/commit/443a22b7) +- double check before deletion [`b44f0519`](https://github.com/Veloera/Veloera/commit/b44f0519) +- able to customize system name & logo now [`926951ee`](https://github.com/Veloera/Veloera/commit/926951ee) +- able to use any link as about page (#60) [`2cdc718f`](https://github.com/Veloera/Veloera/commit/2cdc718f) +- able to customize home page with link (close #60) [`224bebe6`](https://github.com/Veloera/Veloera/commit/224bebe6) +- able to customize home page now (#24) [`d84c2f5c`](https://github.com/Veloera/Veloera/commit/d84c2f5c) +- able to configure smtp from now (close #34) [`f5f4e6fb`](https://github.com/Veloera/Veloera/commit/f5f4e6fb) +- support batch creation of channels (close #58) [`dc4a6cb7`](https://github.com/Veloera/Veloera/commit/dc4a6cb7) +- support specific default api version now (#57) [`83e86b9f`](https://github.com/Veloera/Veloera/commit/83e86b9f) +- Azure API supported without verification (#48, #57) [`7a3378b4`](https://github.com/Veloera/Veloera/commit/7a3378b4) +- now supports custom smtp port [`5c694a15`](https://github.com/Veloera/Veloera/commit/5c694a15) +- support /v1/embeddings now (close #50) [`6e1ef750`](https://github.com/Veloera/Veloera/commit/6e1ef750) +- able to configure ratio for more models now (close #53) [`d9db16e9`](https://github.com/Veloera/Veloera/commit/d9db16e9) +- add Docker Compose support (#55) [`80065de8`](https://github.com/Veloera/Veloera/commit/80065de8) +- double check before deleting a user [`16f53b5a`](https://github.com/Veloera/Veloera/commit/16f53b5a) +- support API `/dashboard/billing/credit_grants` now (#45) [`3071300c`](https://github.com/Veloera/Veloera/commit/3071300c) +- able to configure ratio for different models (close #26) [`d93cb8f6`](https://github.com/Veloera/Veloera/commit/d93cb8f6) +- support configuring ratio when estimating token number in stream mode [`480e789c`](https://github.com/Veloera/Veloera/commit/480e789c) +- now use token as the unit of quota (close #33) [`053bb85a`](https://github.com/Veloera/Veloera/commit/053bb85a) +- able to set top up link now [`f97c2b4c`](https://github.com/Veloera/Veloera/commit/f97c2b4c) +- able to set initial quota for new user (close #22) [`b9cc5dfa`](https://github.com/Veloera/Veloera/commit/b9cc5dfa) +- able to manage system vai access token (close #12) [`8c305dc1`](https://github.com/Veloera/Veloera/commit/8c305dc1) +- download redemption codes as file (#12) [`f62a671f`](https://github.com/Veloera/Veloera/commit/f62a671f) +- now user can top up via redemption code (close #9) [`9e2f2383`](https://github.com/Veloera/Veloera/commit/9e2f2383) +- redirect to login page if login expired (close #18) [`f16a2a56`](https://github.com/Veloera/Veloera/commit/f16a2a56) +- limit the ability of common user to set the remaining usage times of token (#9) [`03491029`](https://github.com/Veloera/Veloera/commit/03491029) +- able to relay dashboard api now [`05dd7dfd`](https://github.com/Veloera/Veloera/commit/05dd7dfd) +- enable gzip on api route & web route [`284beed8`](https://github.com/Veloera/Veloera/commit/284beed8) +- relay all kinds of requests [`46dfc6dc`](https://github.com/Veloera/Veloera/commit/46dfc6dc) +- improve the token edit page [`5ed4a3d4`](https://github.com/Veloera/Veloera/commit/5ed4a3d4) +- able to set the token's expiration time and number of uses [`918ba608`](https://github.com/Veloera/Veloera/commit/918ba608) + +### Bug Fixes +- Include prefixed model names in user model list [`e3c416b1`](https://github.com/Veloera/Veloera/commit/e3c416b1) +- Fix username confilct [`62142698`](https://github.com/Veloera/Veloera/commit/62142698) +- No longer accept multi-key in Vertex channel [`fdd609c3`](https://github.com/Veloera/Veloera/commit/fdd609c3) +- Cannot save check in settings [`8996d68a`](https://github.com/Veloera/Veloera/commit/8996d68a) +- Fix missing VERSION file which is required by Dockerfile [`f93e2f94`](https://github.com/Veloera/Veloera/commit/f93e2f94) +- Fixed display error when success redeem a code [`4099b331`](https://github.com/Veloera/Veloera/commit/4099b331) +- Fix sometimes max_uses will be string [`b3f7bdbf`](https://github.com/Veloera/Veloera/commit/b3f7bdbf) +- Fix a little typo in EditUser [`7bcec0d2`](https://github.com/Veloera/Veloera/commit/7bcec0d2) +- Typo in docker-compose.yml [`a27c86a7`](https://github.com/Veloera/Veloera/commit/a27c86a7) +- claude parallel function calling [`214ca4db`](https://github.com/Veloera/Veloera/commit/214ca4db) +- try to fix claude to openai format mcp #966 [`99efc1fb`](https://github.com/Veloera/Veloera/commit/99efc1fb) +- wrong systemStr for Claude (OpenAI Upstream) [`eee6dee5`](https://github.com/Veloera/Veloera/commit/eee6dee5) +- update model name handling in UI and localization [`dcf78787`](https://github.com/Veloera/Veloera/commit/dcf78787) +- xAI usage [`ef8ae4db`](https://github.com/Veloera/Veloera/commit/ef8ae4db) +- 优化数据流处理 [`9328b907`](https://github.com/Veloera/Veloera/commit/9328b907) +- Update model ratios for gemini-2.5-pro [`c8694554`](https://github.com/Veloera/Veloera/commit/c8694554) +- gemini test MaxTokens [`f24de656`](https://github.com/Veloera/Veloera/commit/f24de656) +- cannot save OIDC settings [`e34dccbc`](https://github.com/Veloera/Veloera/commit/e34dccbc) +- Improve setup check logic and logging for system initialization [`a6bb30af`](https://github.com/Veloera/Veloera/commit/a6bb30af) +- Update option key from SelfUseModeEnabled to DemoSiteEnabled in PostSetup function [`27933da8`](https://github.com/Veloera/Veloera/commit/27933da8) +- Correct option key for SelfUseModeEnabled in setup controller [`1921ac36`](https://github.com/Veloera/Veloera/commit/1921ac36) +- fixed bug where target.id was null when clicking 'x' icon [`9cfa1387`](https://github.com/Veloera/Veloera/commit/9cfa1387) +- claude function calling type [`20c043f5`](https://github.com/Veloera/Veloera/commit/20c043f5) +- Adjust MaxTokens logic for non-Claude models in test request [`73263e02`](https://github.com/Veloera/Veloera/commit/73263e02) +- wrong thinking labels appear in non-thinking models (#861) [`cc1400e9`](https://github.com/Veloera/Veloera/commit/cc1400e9) +- claude to openai tools use [`c0b93507`](https://github.com/Veloera/Veloera/commit/c0b93507) +- claude to openai tools use [`229738cd`](https://github.com/Veloera/Veloera/commit/229738cd) +- claude to openai tools use [`39d95172`](https://github.com/Veloera/Veloera/commit/39d95172) +- Add error logging for OIDC configuration retrieval [`20e34bec`](https://github.com/Veloera/Veloera/commit/20e34bec) +- Improve mobile layout and scrolling behavior [`3122b8a3`](https://github.com/Veloera/Veloera/commit/3122b8a3) +- Correct typo in group_ratio variable name in LogsTable [`5017fabb`](https://github.com/Veloera/Veloera/commit/5017fabb) +- Add optional chaining to prevent potential undefined errors in LogsTable #833 [`bd5c261b`](https://github.com/Veloera/Veloera/commit/bd5c261b) +- Refine embedding model detection in channel test [`4a8bb625`](https://github.com/Veloera/Veloera/commit/4a8bb625) +- Adjust DeepSeek cache ratio to 0.1 [`8c209e2f`](https://github.com/Veloera/Veloera/commit/8c209e2f) +- Update default cache ratio from 0.5 to 1 [`1f4ebddc`](https://github.com/Veloera/Veloera/commit/1f4ebddc) +- possible incomplete return of the think field and incorrect occurrences of the `reasoning` field [`894dce73`](https://github.com/Veloera/Veloera/commit/894dce73) +- adapting return format for openrouter think content (#793) [`b95142bb`](https://github.com/Veloera/Veloera/commit/b95142bb) +- Handle error in NotifyRootUser and log system errors #812 [`a3739f67`](https://github.com/Veloera/Veloera/commit/a3739f67) +- error NotifyRootUser #812 [`e3f9ef18`](https://github.com/Veloera/Veloera/commit/e3f9ef18) +- Prevent resource leaks by adding body close in stream handlers [`558e625a`](https://github.com/Veloera/Veloera/commit/558e625a) +- vertex claude [`8deab221`](https://github.com/Veloera/Veloera/commit/8deab221) +- #810 [`17e9f1a0`](https://github.com/Veloera/Veloera/commit/17e9f1a0) +- #810 [`792754ce`](https://github.com/Veloera/Veloera/commit/792754ce) +- Ignore EOF errors in OpenAI stream scanner [`eb46b71a`](https://github.com/Veloera/Veloera/commit/eb46b71a) +- Handle scanner errors in OpenAI relay stream handler [`b00dd8b4`](https://github.com/Veloera/Veloera/commit/b00dd8b4) +- vertex claude [`b1be64bc`](https://github.com/Veloera/Veloera/commit/b1be64bc) +- Typo in README [`47d3b515`](https://github.com/Veloera/Veloera/commit/47d3b515) +- channel test model mapped [`760514c3`](https://github.com/Veloera/Veloera/commit/760514c3) +- Use channel group in model testing log record [`2ca0d724`](https://github.com/Veloera/Veloera/commit/2ca0d724) +- Correct option map key for PreConsumedQuota [`a592a81b`](https://github.com/Veloera/Veloera/commit/a592a81b) +- Enhance error message for missing model ratio configuration [`ce185484`](https://github.com/Veloera/Veloera/commit/ce185484) +- Improve error handling for model ratio and price validation #800 [`2f9faba4`](https://github.com/Veloera/Veloera/commit/2f9faba4) +- Improve model ratio and price management [`a5085014`](https://github.com/Veloera/Veloera/commit/a5085014) +- Simplify Claude settings value conversion logic [`d306394f`](https://github.com/Veloera/Veloera/commit/d306394f) +- Prevent duplicate headers in Claude settings [`cdba87a7`](https://github.com/Veloera/Veloera/commit/cdba87a7) +- Update Claude thinking adapter token percentage input guidance [`4784ca75`](https://github.com/Veloera/Veloera/commit/4784ca75) +- Correct model request configuration in Vertex Claude adaptor [`3a18c0ce`](https://github.com/Veloera/Veloera/commit/3a18c0ce) +- Add pagination support to user search functionality [`0f1c4c4e`](https://github.com/Veloera/Veloera/commit/0f1c4c4e) +- Improve AWS Claude adaptor request conversion error handling #796 [`5f0b3f6d`](https://github.com/Veloera/Veloera/commit/5f0b3f6d) +- gemini&claude tool call format #795 #766 [`13ab0f8e`](https://github.com/Veloera/Veloera/commit/13ab0f8e) +- claude tool call format #795 #766 [`6d8d40e6`](https://github.com/Veloera/Veloera/commit/6d8d40e6) +- Update Gemini safety settings to use 'OFF' as default [`c802b3b4`](https://github.com/Veloera/Veloera/commit/c802b3b4) +- Update Gemini safety settings category [`ed4e1c23`](https://github.com/Veloera/Veloera/commit/ed4e1c23) +- Update Gemini safety settings default value [`e581ea33`](https://github.com/Veloera/Veloera/commit/e581ea33) +- Adjust Claude thinking mode request parameters [`da4d1861`](https://github.com/Veloera/Veloera/commit/da4d1861) +- Improve 429 error logging with detailed message [`cc1d6e1c`](https://github.com/Veloera/Veloera/commit/cc1d6e1c) +- mistral [`88a2fec1`](https://github.com/Veloera/Veloera/commit/88a2fec1) +- fix image ratio calculation [`27ea231d`](https://github.com/Veloera/Veloera/commit/27ea231d) +- Ensure correct quota warning threshold type conversion [`48926b8a`](https://github.com/Veloera/Veloera/commit/48926b8a) +- ShouldDisableChannel [`7a13fab2`](https://github.com/Veloera/Veloera/commit/7a13fab2) +- mistral adaptor (close #774) [`bf75b308`](https://github.com/Veloera/Veloera/commit/bf75b308) +- Correct Ollama channel authentication header setting [`60aac77c`](https://github.com/Veloera/Veloera/commit/60aac77c) +- Fix Ollama channel authentication [`a13f4d6c`](https://github.com/Veloera/Veloera/commit/a13f4d6c) +- Remove redundant error handling in distributor and relay modules [`ae5875d4`](https://github.com/Veloera/Veloera/commit/ae5875d4) +- Extend temperature handling for OpenAI-like models [`812c188a`](https://github.com/Veloera/Veloera/commit/812c188a) +- Improve OpenAI stream data parsing and handling [`bd4ce9cd`](https://github.com/Veloera/Veloera/commit/bd4ce9cd) +- Update BaseURL placeholder text and label in channel edit page [`871d73ec`](https://github.com/Veloera/Veloera/commit/871d73ec) +- adjust max tokens configuration in test request builder [`cf0ff037`](https://github.com/Veloera/Veloera/commit/cf0ff037) +- update session store configuration [`bbc1550a`](https://github.com/Veloera/Veloera/commit/bbc1550a) +- always use modelMapping in channel test [`882c5970`](https://github.com/Veloera/Veloera/commit/882c5970) +- replace context-based user ID with session-based retrieval #741 [`8418dbe7`](https://github.com/Veloera/Veloera/commit/8418dbe7) +- CI #744 [`68c559c1`](https://github.com/Veloera/Veloera/commit/68c559c1) +- correct JSON tags for `Prompt` and `Suffix` in `GeneralOpenAIRequest` [`1eb72f2f`](https://github.com/Veloera/Veloera/commit/1eb72f2f) +- channels model_mapping [`70083ecd`](https://github.com/Veloera/Veloera/commit/70083ecd) +- update logs table total count display [`f7a4016d`](https://github.com/Veloera/Veloera/commit/f7a4016d) +- improve reasoning effort model suffix handling [`a29e1e0a`](https://github.com/Veloera/Veloera/commit/a29e1e0a) +- update reasoning effort model suffix parsing [`ce77f255`](https://github.com/Veloera/Veloera/commit/ce77f255) +- clear channel name in user logs [`c511c7f0`](https://github.com/Veloera/Veloera/commit/c511c7f0) +- update DeepSeek reasoner model ratio check [`15918b20`](https://github.com/Veloera/Veloera/commit/15918b20) +- display docker build error [`87052e92`](https://github.com/Veloera/Veloera/commit/87052e92) +- remove ffmpeg-tools [`c5b151ed`](https://github.com/Veloera/Veloera/commit/c5b151ed) +- log filename format [`da83de27`](https://github.com/Veloera/Veloera/commit/da83de27) +- incorrect whisper audio usage [`2abf05b3`](https://github.com/Veloera/Veloera/commit/2abf05b3) +- fix the issue of fetching model list failure in batch add channel [`2ec4d284`](https://github.com/Veloera/Veloera/commit/2ec4d284) +- update linux-release workflow to install gcc-aarch64-linux-gnu non-interactively [`055a238e`](https://github.com/Veloera/Veloera/commit/055a238e) +- update iframe styling and permissions in ChatPage component [`af0b9325`](https://github.com/Veloera/Veloera/commit/af0b9325) +- retry prompt tokens [`ef4c1a2e`](https://github.com/Veloera/Veloera/commit/ef4c1a2e) +- try to fix pgsql #685 [`42bf95bd`](https://github.com/Veloera/Veloera/commit/42bf95bd) +- error page size opts [`e6ea5e59`](https://github.com/Veloera/Veloera/commit/e6ea5e59) +- try to fix pgsql #682 [`a7e5f1e5`](https://github.com/Veloera/Veloera/commit/a7e5f1e5) +- try to fix pgsql #682 [`87d5e286`](https://github.com/Veloera/Veloera/commit/87d5e286) +- Gemini 其他文件类型的支持(Base64URL) [`2a15dfcc`](https://github.com/Veloera/Veloera/commit/2a15dfcc) +- Gemini 函数调用的文本转义 [`9e5a7ed5`](https://github.com/Veloera/Veloera/commit/9e5a7ed5) +- playground request_start_time [`65d1cde8`](https://github.com/Veloera/Veloera/commit/65d1cde8) +- prevent setting models to null in PersonalSetting component [`8f4a2df5`](https://github.com/Veloera/Veloera/commit/8f4a2df5) +- 转义 Gemini 工具调用中的反斜杠 [`72dc5430`](https://github.com/Veloera/Veloera/commit/72dc5430) +- add index in the tool calls when chat by stream (gemini) [`38cff317`](https://github.com/Veloera/Veloera/commit/38cff317) +- prevent duplicate models in user group retrieval [`1cef91a7`](https://github.com/Veloera/Veloera/commit/1cef91a7) +- oauth bind [`0fd0e5d3`](https://github.com/Veloera/Veloera/commit/0fd0e5d3) +- update render function for quota display in Detail page [`f92d96e2`](https://github.com/Veloera/Veloera/commit/f92d96e2) +- validate number input in renderQuotaNumberWithDigit and improve data handling in Detail page [`bfba4866`](https://github.com/Veloera/Veloera/commit/bfba4866) +- update MaxCompletionTokens for model prefix handling in buildTestRequest function [`ed4d26fc`](https://github.com/Veloera/Veloera/commit/ed4d26fc) +- correct user retrieval in GetPricing function [`ba56e2e8`](https://github.com/Veloera/Veloera/commit/ba56e2e8) +- resolve pricing calculation issue (#659) [`7c20e6d0`](https://github.com/Veloera/Veloera/commit/7c20e6d0) +- update searchUsers function to include searchKeyword and searchGroup parameters [`f2c93881`](https://github.com/Veloera/Veloera/commit/f2c93881) +- #657 [`fe2165ac`](https://github.com/Veloera/Veloera/commit/fe2165ac) +- get upstream models [`3003d12a`](https://github.com/Veloera/Veloera/commit/3003d12a) +- gemini func call [`d40e6ec2`](https://github.com/Veloera/Veloera/commit/d40e6ec2) +- mutil func call in gemini [`a4795737`](https://github.com/Veloera/Veloera/commit/a4795737) +- Fix the issue where Gemini loses content when converting OpenAI format in the stream. [`158edab9`](https://github.com/Veloera/Veloera/commit/158edab9) +- 修复添加模型切换模式时,初始化空值导致的判断问题 [`e3a01622`](https://github.com/Veloera/Veloera/commit/e3a01622) +- Correct JSON field name for SystemInstructions in GeminiChatRequest [`76acfdc9`](https://github.com/Veloera/Veloera/commit/76acfdc9) +- Refine sider visibility logic in HeaderBar component [`a485db50`](https://github.com/Veloera/Veloera/commit/a485db50) +- Adjust inner padding style in PageLayout component [`2a6f3ad2`](https://github.com/Veloera/Veloera/commit/2a6f3ad2) +- Refine sider visibility and inner padding logic in StyleProvider component [`16599a90`](https://github.com/Veloera/Veloera/commit/16599a90) +- Update label truncation logic in Playground and adjust sider visibility in HeaderBar [`d241e4fe`](https://github.com/Veloera/Veloera/commit/d241e4fe) +- Correct inner padding and sider visibility logic in HeaderBar, PageLayout, and SiderBar components [`e17f36e7`](https://github.com/Veloera/Veloera/commit/e17f36e7) +- 编辑标签文字错误 [`5d1087a6`](https://github.com/Veloera/Veloera/commit/5d1087a6) +- edit channel weight and priority [`cf8b30ed`](https://github.com/Veloera/Veloera/commit/cf8b30ed) +- 渠道标签开启下使用ID排序出错 [`56ccb30a`](https://github.com/Veloera/Veloera/commit/56ccb30a) +- telegram register [`dd293f80`](https://github.com/Veloera/Veloera/commit/dd293f80) +- 关键词搜索时无法展开测试模型 [`3aa59178`](https://github.com/Veloera/Veloera/commit/3aa59178) +- 标签分组功能开启时无法展开测试模型 [`b5d273b6`](https://github.com/Veloera/Veloera/commit/b5d273b6) +- email panic [`d9b622c8`](https://github.com/Veloera/Veloera/commit/d9b622c8) +- 360智能接口地址更新 [`28885fee`](https://github.com/Veloera/Veloera/commit/28885fee) +- realtime [`6d4edc1f`](https://github.com/Veloera/Veloera/commit/6d4edc1f) +- tag channel copy [`bb0c5047`](https://github.com/Veloera/Veloera/commit/bb0c5047) +- xAI missing finish_reason #572 [`1774be85`](https://github.com/Veloera/Veloera/commit/1774be85) +- stt模型计费 [`7ebc1cfb`](https://github.com/Veloera/Veloera/commit/7ebc1cfb) +- search channel #442 [`807385d3`](https://github.com/Veloera/Veloera/commit/807385d3) +- oauth aff [`334a2424`](https://github.com/Veloera/Veloera/commit/334a2424) +- oauth aff [`7db70337`](https://github.com/Veloera/Veloera/commit/7db70337) +- aws claude [`320e6ec5`](https://github.com/Veloera/Veloera/commit/320e6ec5) +- LinuxDo OAuth [`85b90e89`](https://github.com/Veloera/Veloera/commit/85b90e89) +- 非root日志展开bug [`34998f79`](https://github.com/Veloera/Veloera/commit/34998f79) +- returnPreConsumedQuota [`8fc49f98`](https://github.com/Veloera/Veloera/commit/8fc49f98) +- log table unknown ws prop error [`afc1e92e`](https://github.com/Veloera/Veloera/commit/afc1e92e) +- mistral adaptor [`7b1ff41e`](https://github.com/Veloera/Veloera/commit/7b1ff41e) +- realtime计费 [`4e0c522c`](https://github.com/Veloera/Veloera/commit/4e0c522c) +- channel test [`f08f7ae9`](https://github.com/Veloera/Veloera/commit/f08f7ae9) +- 修复ws 握手失败、计费问题 [`be64408a`](https://github.com/Veloera/Veloera/commit/be64408a) +- 部分情况缺少返回预扣 [`f0907bf6`](https://github.com/Veloera/Veloera/commit/f0907bf6) +- 修复聊天环境变量替换不完全 (close #542) [`3e2ae29b`](https://github.com/Veloera/Veloera/commit/3e2ae29b) +- 修复Playground分组无用户分组 (close #529) [`ade6d0f5`](https://github.com/Veloera/Veloera/commit/ade6d0f5) +- 修复用户可选分组不能选择用户分组 (close #528) [`f599c659`](https://github.com/Veloera/Veloera/commit/f599c659) +- 修复自定义聊天bug [`40baa636`](https://github.com/Veloera/Veloera/commit/40baa636) +- playground group [`6a8a4bcf`](https://github.com/Veloera/Veloera/commit/6a8a4bcf) +- playground气泡溢出 #511 [`74631124`](https://github.com/Veloera/Veloera/commit/74631124) +- playground max_tokens #512 #511 [`f9ba10f1`](https://github.com/Veloera/Veloera/commit/f9ba10f1) +- 第三方登录注销 #500 [`9fe1f35f`](https://github.com/Veloera/Veloera/commit/9fe1f35f) +- 第三方登录注销 #500 [`972ac1ee`](https://github.com/Veloera/Veloera/commit/972ac1ee) +- error user role [`221894d9`](https://github.com/Veloera/Veloera/commit/221894d9) +- 使用令牌分组时 "/v1/models" 返回模型不正确 #481 [`af7fecbf`](https://github.com/Veloera/Veloera/commit/af7fecbf) +- token group #477 [`3fbdd502`](https://github.com/Veloera/Veloera/commit/3fbdd502) +- 初始令牌 [`9032b5cf`](https://github.com/Veloera/Veloera/commit/9032b5cf) +- '/v1/models' #474 [`a03cd155`](https://github.com/Veloera/Veloera/commit/a03cd155) +- '/v1/models' #474 [`02f51377`](https://github.com/Veloera/Veloera/commit/02f51377) +- '/vi/models' #474 [`e6df0ed2`](https://github.com/Veloera/Veloera/commit/e6df0ed2) +- cohere SafetyMode [`d168a685`](https://github.com/Veloera/Veloera/commit/d168a685) +- tool use in claude [`0ada2371`](https://github.com/Veloera/Veloera/commit/0ada2371) +- email [`8bc1e956`](https://github.com/Veloera/Veloera/commit/8bc1e956) +- channel auto ban [`46e03683`](https://github.com/Veloera/Veloera/commit/46e03683) +- channel auto ban #443 [`ff0985f0`](https://github.com/Veloera/Veloera/commit/ff0985f0) +- 修复 dall-e-2 请求报错 [`967ccabb`](https://github.com/Veloera/Veloera/commit/967ccabb) +- 多地区outlook邮箱和ofb邮箱Auth [`484a8595`](https://github.com/Veloera/Veloera/commit/484a8595) +- add email missing Message-ID [`a5ec11e4`](https://github.com/Veloera/Veloera/commit/a5ec11e4) +- lobechat #430 [`b3d8e3e9`](https://github.com/Veloera/Veloera/commit/b3d8e3e9) +- log page 'Cannot read properties of undefined (reading 'length')' [`8cd8cc29`](https://github.com/Veloera/Veloera/commit/8cd8cc29) +- log pagination [`9edb7c4a`](https://github.com/Veloera/Veloera/commit/9edb7c4a) +- log分页问题 [`a2af637e`](https://github.com/Veloera/Veloera/commit/a2af637e) +- close #422 [`9e45dbe9`](https://github.com/Veloera/Veloera/commit/9e45dbe9) +- sqlite group 查询兼容 [`9452be51`](https://github.com/Veloera/Veloera/commit/9452be51) +- 修复mysql兼容问题 [`04f0084d`](https://github.com/Veloera/Veloera/commit/04f0084d) +- response format [`2e3c266b`](https://github.com/Veloera/Veloera/commit/2e3c266b) +- optionList bug [`e614ca37`](https://github.com/Veloera/Veloera/commit/e614ca37) +- 渠道多分组,优化分组 like 查询 [`190316f6`](https://github.com/Veloera/Veloera/commit/190316f6) +- epay [`5d0d268c`](https://github.com/Veloera/Veloera/commit/5d0d268c) +- channel typ error [`0b4ef42d`](https://github.com/Veloera/Veloera/commit/0b4ef42d) +- 重试后request id不一致 [`0123ad4d`](https://github.com/Veloera/Veloera/commit/0123ad4d) +- rpm模糊查询 [`f8f15bd1`](https://github.com/Veloera/Veloera/commit/f8f15bd1) +- 日志模糊查询 [`b7690fe1`](https://github.com/Veloera/Veloera/commit/b7690fe1) +- error channel name on notify. #338 [`1501ccb9`](https://github.com/Veloera/Veloera/commit/1501ccb9) +- panic when get model ratio (close #392) [`b16e6bf4`](https://github.com/Veloera/Veloera/commit/b16e6bf4) +- image quota (close #382) [`9e610c94`](https://github.com/Veloera/Veloera/commit/9e610c94) +- gemini [`b8291dcd`](https://github.com/Veloera/Veloera/commit/b8291dcd) +- first login error (close #385) [`caaecb8d`](https://github.com/Veloera/Veloera/commit/caaecb8d) +- the base64 format image_url for gemini [`b9454c3f`](https://github.com/Veloera/Veloera/commit/b9454c3f) +- 修复aws claude渠道panic的问题 [`96bdf971`](https://github.com/Veloera/Veloera/commit/96bdf971) +- gemini stream finish reason (close #378) [`3875b141`](https://github.com/Veloera/Veloera/commit/3875b141) +- 修复nginx缓存导致串用户问题 [`ce815a98`](https://github.com/Veloera/Veloera/commit/ce815a98) +- image relay quota [`70491ea1`](https://github.com/Veloera/Veloera/commit/70491ea1) +- distribute panic [`7a0beb57`](https://github.com/Veloera/Veloera/commit/7a0beb57) +- try to fix panic #369 [`e3b83f88`](https://github.com/Veloera/Veloera/commit/e3b83f88) +- embedding model dimensions [`fd872602`](https://github.com/Veloera/Veloera/commit/fd872602) +- try to fix panic #369 [`4d0d1893`](https://github.com/Veloera/Veloera/commit/4d0d1893) +- fix bug [`86ca533f`](https://github.com/Veloera/Veloera/commit/86ca533f) +- try to fix mj [`ba27da9e`](https://github.com/Veloera/Veloera/commit/ba27da9e) +- 日志详情非消费类型显示错误 [`9bbe8e7d`](https://github.com/Veloera/Veloera/commit/9bbe8e7d) +- openai stream response [`e2b90616`](https://github.com/Veloera/Veloera/commit/e2b90616) +- openai response time [`220ab412`](https://github.com/Veloera/Veloera/commit/220ab412) +- azure stream options [`0f687aab`](https://github.com/Veloera/Veloera/commit/0f687aab) +- http code is not properly disabled [`d55cb35c`](https://github.com/Veloera/Veloera/commit/d55cb35c) +- channel timeout auto-ban and auto-enable [`e67aa370`](https://github.com/Veloera/Veloera/commit/e67aa370) +- gemini usage (close #354) [`4e7e2062`](https://github.com/Veloera/Veloera/commit/4e7e2062) +- dify (close #355) [`579fc812`](https://github.com/Veloera/Veloera/commit/579fc812) +- email login [`f55f63f4`](https://github.com/Veloera/Veloera/commit/f55f63f4) +- hunyuan [`b75134ec`](https://github.com/Veloera/Veloera/commit/b75134ec) +- stream options [`a0755987`](https://github.com/Veloera/Veloera/commit/a0755987) +- baidu max_output_tokens (#353) [`90abe7f2`](https://github.com/Veloera/Veloera/commit/90abe7f2) +- baidu max_output_tokens (close #353) [`02545e48`](https://github.com/Veloera/Veloera/commit/02545e48) +- channel default test model [`49cec509`](https://github.com/Veloera/Veloera/commit/49cec509) +- 修复渠道晒筛选后无法展开测试模型 (close #297 #302) [`4f6710e5`](https://github.com/Veloera/Veloera/commit/4f6710e5) +- 日志详情 [`e062cf32`](https://github.com/Veloera/Veloera/commit/e062cf32) +- streaming timeout [`4246c4cd`](https://github.com/Veloera/Veloera/commit/4246c4cd) +- streaming timeout [`1e536ee7`](https://github.com/Veloera/Veloera/commit/1e536ee7) +- fix rerank [`1735e093`](https://github.com/Veloera/Veloera/commit/1735e093) +- midjourney channel auto ban [`afe02c6a`](https://github.com/Veloera/Veloera/commit/afe02c6a) +- try to fix tencent hunyuan #336 [`6c5b3b51`](https://github.com/Veloera/Veloera/commit/6c5b3b51) +- channel auto ban [`d4578e28`](https://github.com/Veloera/Veloera/commit/d4578e28) +- sqlite too many SQL variables [`1e9d64fd`](https://github.com/Veloera/Veloera/commit/1e9d64fd) +- mj auto ban [`4d3b57e1`](https://github.com/Veloera/Veloera/commit/4d3b57e1) +- try to fix minimax (close #327) [`11be36da`](https://github.com/Veloera/Veloera/commit/11be36da) +- try to fix hunyuan (close #303) [`77d14561`](https://github.com/Veloera/Veloera/commit/77d14561) +- auto ban [`5abb0a9c`](https://github.com/Veloera/Veloera/commit/5abb0a9c) +- claude usage [`954fa879`](https://github.com/Veloera/Veloera/commit/954fa879) +- Available models could not be populated when adding a new channel [`6f1bef66`](https://github.com/Veloera/Veloera/commit/6f1bef66) +- 删除用户改为注销 [`692455ef`](https://github.com/Veloera/Veloera/commit/692455ef) +- typo [`c1040afe`](https://github.com/Veloera/Veloera/commit/c1040afe) +- GetLogByKey panic [`a31247ec`](https://github.com/Veloera/Veloera/commit/a31247ec) +- pricing page group ratio (close #275) [`3cd25c7e`](https://github.com/Veloera/Veloera/commit/3cd25c7e) +- log page error [`f07ae813`](https://github.com/Veloera/Veloera/commit/f07ae813) +- 模型价格 [`cf8fe63f`](https://github.com/Veloera/Veloera/commit/cf8fe63f) +- 模型价格 [`1568d648`](https://github.com/Veloera/Veloera/commit/1568d648) +- log page error (close #270) [`51e0754a`](https://github.com/Veloera/Veloera/commit/51e0754a) +- try to fix sqlite database migration (#231) [`7003a4ed`](https://github.com/Veloera/Veloera/commit/7003a4ed) +- gpt-4-gizmo-* model ratio [`9f18641d`](https://github.com/Veloera/Veloera/commit/9f18641d) +- 修复自定义渠道出错 #243 [`fd19798c`](https://github.com/Veloera/Veloera/commit/fd19798c) +- 修复"/v1/models"不显示自定义模型 (close #235) [`db575a1c`](https://github.com/Veloera/Veloera/commit/db575a1c) +- update user (#230) [`2bf40450`](https://github.com/Veloera/Veloera/commit/2bf40450) +- aws claude system [`7fb6420e`](https://github.com/Veloera/Veloera/commit/7fb6420e) +- aws claude [`5425b5bf`](https://github.com/Veloera/Veloera/commit/5425b5bf) +- 规范claude返回格式 [`1c6fd879`](https://github.com/Veloera/Veloera/commit/1c6fd879) +- 规范claude返回格式 [`d1c89478`](https://github.com/Veloera/Veloera/commit/d1c89478) +- claude流模式缺失role [`7d2d5250`](https://github.com/Veloera/Veloera/commit/7d2d5250) +- claude [`e2edd5e7`](https://github.com/Veloera/Veloera/commit/e2edd5e7) +- update user [`08b53364`](https://github.com/Veloera/Veloera/commit/08b53364) +- audio预扣费未返还 [`b1b38a6b`](https://github.com/Veloera/Veloera/commit/b1b38a6b) +- close #218 [`a3ccc92f`](https://github.com/Veloera/Veloera/commit/a3ccc92f) +- fix update payment setting [`77e7d111`](https://github.com/Veloera/Veloera/commit/77e7d111) +- claude max_tokens [`53e87900`](https://github.com/Veloera/Veloera/commit/53e87900) +- test all channel error (close #206) [`a4defe6a`](https://github.com/Veloera/Veloera/commit/a4defe6a) +- the dark mode does not work for the `OperationSetting` and `SystemSetting` panels [`848358d8`](https://github.com/Veloera/Veloera/commit/848358d8) +- Gemini 1.5 name error [`d7e117ac`](https://github.com/Veloera/Veloera/commit/d7e117ac) +- rename the latest Gemini model name [`3b6ea510`](https://github.com/Veloera/Veloera/commit/3b6ea510) +- 修复渠道一次性添加很多model失败 [`320da09f`](https://github.com/Veloera/Veloera/commit/320da09f) +- 307本地重试 [`2d849e0d`](https://github.com/Veloera/Veloera/commit/2d849e0d) +- distributor panic [`60d7ed3f`](https://github.com/Veloera/Veloera/commit/60d7ed3f) +- select channel [`34bf8f89`](https://github.com/Veloera/Veloera/commit/34bf8f89) +- email whitelist check [`257cfc23`](https://github.com/Veloera/Veloera/commit/257cfc23) +- add group tag 'unknown' [`fc9f8c8e`](https://github.com/Veloera/Veloera/commit/fc9f8c8e) +- GetRandomSatisfiedChannel [`aaf3a1f0`](https://github.com/Veloera/Veloera/commit/aaf3a1f0) +- email whitelist check [`9025756b`](https://github.com/Veloera/Veloera/commit/9025756b) +- user update error [`2ea60099`](https://github.com/Veloera/Veloera/commit/2ea60099) +- log page type error (close #154) [`a33f685f`](https://github.com/Veloera/Veloera/commit/a33f685f) +- update user quote (close #161) [`5ce8e6da`](https://github.com/Veloera/Veloera/commit/5ce8e6da) +- add key prop to Tag components [`d6e373fb`](https://github.com/Veloera/Veloera/commit/d6e373fb) +- SearchUsers (close #160) [`36d164be`](https://github.com/Veloera/Veloera/commit/36d164be) +- remove sensitive check on completion (close #157) [`44a8ade4`](https://github.com/Veloera/Veloera/commit/44a8ade4) +- 支持 /mj-{mode} 路径 [`3065bf92`](https://github.com/Veloera/Veloera/commit/3065bf92) +- 支持 /mj-{mode} 路径 [`2e595bda`](https://github.com/Veloera/Veloera/commit/2e595bda) +- ollama channel test [`319e97d6`](https://github.com/Veloera/Veloera/commit/319e97d6) +- CountTokenInput [`6114c9bb`](https://github.com/Veloera/Veloera/commit/6114c9bb) +- CountTokenInput [`3cf2f0d5`](https://github.com/Veloera/Veloera/commit/3cf2f0d5) +- try to fix 307 [`cc8cc8b3`](https://github.com/Veloera/Veloera/commit/cc8cc8b3) +- 无法复制弹窗过小 [`b2a280c1`](https://github.com/Veloera/Veloera/commit/b2a280c1) +- Cannot read properties of undefined (reading 'map') (close #148) [`3800dc21`](https://github.com/Veloera/Veloera/commit/3800dc21) +- error in console under dev mode [`f671176d`](https://github.com/Veloera/Veloera/commit/f671176d) +- 流模式网络错误导致0补 [`2d36dee1`](https://github.com/Veloera/Veloera/commit/2d36dee1) +- 模型倍率和价格无法设置 [`6eb30ec3`](https://github.com/Veloera/Veloera/commit/6eb30ec3) +- 修复默认模型倍率未显示 [`0b3520e3`](https://github.com/Veloera/Veloera/commit/0b3520e3) +- ci [`66e30f41`](https://github.com/Veloera/Veloera/commit/66e30f41) +- embed [`21f48b55`](https://github.com/Veloera/Veloera/commit/21f48b55) +- 模型固定价格为空时错误使用默认价格 [`67332bc8`](https://github.com/Veloera/Veloera/commit/67332bc8) +- GLM-4V 的 Vision 兼容问题 (close #136) [`d0acecb2`](https://github.com/Veloera/Veloera/commit/d0acecb2) +- mj [`a70ca534`](https://github.com/Veloera/Veloera/commit/a70ca534) +- 充值并发导致订单号相同 [`c33b1522`](https://github.com/Veloera/Veloera/commit/c33b1522) +- add missing created [`ff7da08b`](https://github.com/Veloera/Veloera/commit/ff7da08b) +- add missing id,object,created [`3e03c5a7`](https://github.com/Veloera/Veloera/commit/3e03c5a7) +- try to fix curl: (18) [`d9344d79`](https://github.com/Veloera/Veloera/commit/d9344d79) +- fix embedding [`c4b3d3a9`](https://github.com/Veloera/Veloera/commit/c4b3d3a9) +- add missing version [`3f808be2`](https://github.com/Veloera/Veloera/commit/3f808be2) +- fix mj panic [`9b64f4a3`](https://github.com/Veloera/Veloera/commit/9b64f4a3) +- fix SensitiveWords error [`222a5538`](https://github.com/Veloera/Veloera/commit/222a5538) +- fix SensitiveWords load error [`d7e25e16`](https://github.com/Veloera/Veloera/commit/d7e25e16) +- 敏感词库为空时,不检测 [`c7658b70`](https://github.com/Veloera/Veloera/commit/c7658b70) +- midjourneys table [`d5e93e78`](https://github.com/Veloera/Veloera/commit/d5e93e78) +- claude panic [`dd719460`](https://github.com/Veloera/Veloera/commit/dd719460) +- claude panic [`b736de71`](https://github.com/Veloera/Veloera/commit/b736de71) +- fix SensitiveWordContains not working [`eb6257a8`](https://github.com/Veloera/Veloera/commit/eb6257a8) +- fix error [`47b9f485`](https://github.com/Veloera/Veloera/commit/47b9f485) +- api type error [`4c4e0870`](https://github.com/Veloera/Veloera/commit/4c4e0870) +- make the 'openai_organization' parameter actually work. [`f63ad9c0`](https://github.com/Veloera/Veloera/commit/f63ad9c0) +- make the 'openai_organization' parameter actually work. [`0fb98e44`](https://github.com/Veloera/Veloera/commit/0fb98e44) +- chatglm top_p error (close #124) [`b8c053c3`](https://github.com/Veloera/Veloera/commit/b8c053c3) +- 侧边栏聚焦问题 (close #123) [`2581b373`](https://github.com/Veloera/Veloera/commit/2581b373) +- 修复日志翻页问题 (close #122) [`f65477d0`](https://github.com/Veloera/Veloera/commit/f65477d0) +- fix image-seed error [`6ab1b3a5`](https://github.com/Veloera/Veloera/commit/6ab1b3a5) +- 修复epay可能重复回调的问题 [`299911d4`](https://github.com/Veloera/Veloera/commit/299911d4) +- reroll action error [`a77fbc0f`](https://github.com/Veloera/Veloera/commit/a77fbc0f) +- "Inpaint" code error [`44361d75`](https://github.com/Veloera/Veloera/commit/44361d75) +- fix typo [`d3399d68`](https://github.com/Veloera/Veloera/commit/d3399d68) +- layout issues [`30d48ea4`](https://github.com/Veloera/Veloera/commit/30d48ea4) +- 修复模型映射功能失效 (close #105) [`d4df1960`](https://github.com/Veloera/Veloera/commit/d4df1960) +- 请求阿里通义千问异常 (close #108) [`97dd8054`](https://github.com/Veloera/Veloera/commit/97dd8054) +- 删除用户改为软删除 (close #107) [`bf241b21`](https://github.com/Veloera/Veloera/commit/bf241b21) +- 修复claude渠道流模式计费可能异常 [`7ab6c6c3`](https://github.com/Veloera/Veloera/commit/7ab6c6c3) +- fix gemini channel test [`8fe8340b`](https://github.com/Veloera/Veloera/commit/8fe8340b) +- fix claude channel test [`26ef906c`](https://github.com/Veloera/Veloera/commit/26ef906c) +- fix claude 3 request missing the 'max_token' field [`f43b2685`](https://github.com/Veloera/Veloera/commit/f43b2685) +- fix gemini [`3c3c5305`](https://github.com/Veloera/Veloera/commit/3c3c5305) +- 隐藏无用按钮 [`6a83c8ad`](https://github.com/Veloera/Veloera/commit/6a83c8ad) +- fix Azure channel test (close #101) [`0640dd81`](https://github.com/Veloera/Veloera/commit/0640dd81) +- fix bug [`0e06be8c`](https://github.com/Veloera/Veloera/commit/0e06be8c) +- 修复流模式错误扣费的问题 (close #95) [`626217fb`](https://github.com/Veloera/Veloera/commit/626217fb) +- 恢复微信公众号扫码功能 (close #7) [`9de2d21e`](https://github.com/Veloera/Veloera/commit/9de2d21e) +- Fix the issue of 'unknown relay mode' when making an embedding request (close #93) [`fb39b6b3`](https://github.com/Veloera/Veloera/commit/fb39b6b3) +- model names must not contain spaces at both ends [`d160736a`](https://github.com/Veloera/Veloera/commit/d160736a) +- restore the set of StatusContext [`5cb933a2`](https://github.com/Veloera/Veloera/commit/5cb933a2) +- 修复保存telegram设置后会将telegram登陆禁用的问题 [`1f0a48a8`](https://github.com/Veloera/Veloera/commit/1f0a48a8) +- 修复预扣费判定无效导致用户可无限欠费问题 [`37b307a7`](https://github.com/Veloera/Veloera/commit/37b307a7) +- 修复添加和编辑渠道无可选择模型 [`54088bc6`](https://github.com/Veloera/Veloera/commit/54088bc6) +- remove useless code [`06b746a7`](https://github.com/Veloera/Veloera/commit/06b746a7) +- Improve handling of small weights in channel selection logic [`4c43012e`](https://github.com/Veloera/Veloera/commit/4c43012e) +- add missing UpstreamModelName [`cbe0a1fa`](https://github.com/Veloera/Veloera/commit/cbe0a1fa) +- add missing upstreamModelName [`7297c626`](https://github.com/Veloera/Veloera/commit/7297c626) +- the content displayed on the homepage is incorrect [`ac64fd26`](https://github.com/Veloera/Veloera/commit/ac64fd26) +- fix typo [`2b076eae`](https://github.com/Veloera/Veloera/commit/2b076eae) +- fix preConsumeQuota error [`a3687b72`](https://github.com/Veloera/Veloera/commit/a3687b72) +- fix typo [`9b421478`](https://github.com/Veloera/Veloera/commit/9b421478) +- 修复测试渠道指定模型时实际使用gpt-3.5-turbo的问题 (close #67) [`569751f5`](https://github.com/Veloera/Veloera/commit/569751f5) +- 修复redis缓存用户额度不过期的问题 [`5482fff6`](https://github.com/Veloera/Veloera/commit/5482fff6) +- 修正 Unicode 转义序列解析问题,close #63 [`1cde58c0`](https://github.com/Veloera/Veloera/commit/1cde58c0) +- fix test channel [`f19b11be`](https://github.com/Veloera/Veloera/commit/f19b11be) +- support AI Gateway, close #52 [`0004c102`](https://github.com/Veloera/Veloera/commit/0004c102) +- support AI Gateway, close #52 [`4274b925`](https://github.com/Veloera/Veloera/commit/4274b925) +- 修复是否自动禁用选项不生效 [`166770ee`](https://github.com/Veloera/Veloera/commit/166770ee) +- fix tool calls [`6d047963`](https://github.com/Veloera/Veloera/commit/6d047963) +- fix redis error [`49209229`](https://github.com/Veloera/Veloera/commit/49209229) +- fix redis error [`70bdb8ca`](https://github.com/Veloera/Veloera/commit/70bdb8ca) +- mj 错误处理 [`d3e070d9`](https://github.com/Veloera/Veloera/commit/d3e070d9) +- 修复数据看板渲染错误 [`a8715c61`](https://github.com/Veloera/Veloera/commit/a8715c61) +- 修复渠道一致性问题 [`2b3539fc`](https://github.com/Veloera/Veloera/commit/2b3539fc) +- 修复渠道一致性问题 [`e2a1caba`](https://github.com/Veloera/Veloera/commit/e2a1caba) +- delete RelayPanicRecover [`075b1ac1`](https://github.com/Veloera/Veloera/commit/075b1ac1) +- 修复mj错误返还费用问题 [`2a9c3ac6`](https://github.com/Veloera/Veloera/commit/2a9c3ac6) +- the Redis problem in the CacheGetUsername function [`6aadcf0c`](https://github.com/Veloera/Veloera/commit/6aadcf0c) +- user group size [`ad857928`](https://github.com/Veloera/Veloera/commit/ad857928) +- token model limit [`521ef5e2`](https://github.com/Veloera/Veloera/commit/521ef5e2) +- 完善令牌预扣费逻辑 [`64e9e9cc`](https://github.com/Veloera/Veloera/commit/64e9e9cc) +- fix relay openai panic [`701a28d0`](https://github.com/Veloera/Veloera/commit/701a28d0) +- 修复高并发下,高额度用户使用低额度令牌没有预扣费的问题 [`a3b726dd`](https://github.com/Veloera/Veloera/commit/a3b726dd) +- fix response choice json [`042d55cf`](https://github.com/Veloera/Veloera/commit/042d55cf) +- do not consume user quota if failed [`3432d9e0`](https://github.com/Veloera/Veloera/commit/3432d9e0) +- fix channel panic [`3e13810c`](https://github.com/Veloera/Veloera/commit/3e13810c) +- add Tools support [`db29c963`](https://github.com/Veloera/Veloera/commit/db29c963) +- 修复数据看板筛选用户的时候不能指定时间的问题 [`6ed365c2`](https://github.com/Veloera/Veloera/commit/6ed365c2) +- fix JSON tag in Log struct [`daf0be49`](https://github.com/Veloera/Veloera/commit/daf0be49) +- 减少username和model_name字段长度为64 [`f3124e72`](https://github.com/Veloera/Veloera/commit/f3124e72) +- 减少group和model字段长度为64 [`ebedf2cb`](https://github.com/Veloera/Veloera/commit/ebedf2cb) +- 修复侧边导航栏需要刷新才出现选项的问题 [`18e9d370`](https://github.com/Veloera/Veloera/commit/18e9d370) +- 修复mj固定价格设置无效的问题 [`e2d994d7`](https://github.com/Veloera/Veloera/commit/e2d994d7) +- 数据面板次数统计错误 [`29dbdf01`](https://github.com/Veloera/Veloera/commit/29dbdf01) +- recover panic [`64862cd6`](https://github.com/Veloera/Veloera/commit/64862cd6) +- 索引长度 [`a5e0f86c`](https://github.com/Veloera/Veloera/commit/a5e0f86c) +- 索引名称长度 [`2a995a5d`](https://github.com/Veloera/Veloera/commit/2a995a5d) +- fix gemini panic [`bba61747`](https://github.com/Veloera/Veloera/commit/bba61747) +- 修复数据看板饼图显示错误 [`b91a269d`](https://github.com/Veloera/Veloera/commit/b91a269d) +- 优化数据看板更新逻辑 [`ce05e7dd`](https://github.com/Veloera/Veloera/commit/ce05e7dd) +- 修复兑换码复制带sk- [`79b1201f`](https://github.com/Veloera/Veloera/commit/79b1201f) +- 优化 mj 获取进度 [`fd4ef086`](https://github.com/Veloera/Veloera/commit/fd4ef086) +- add Date header for email [`cdcbebce`](https://github.com/Veloera/Veloera/commit/cdcbebce) +- 如果还有数据,等待一会 [`f712b73c`](https://github.com/Veloera/Veloera/commit/f712b73c) +- 如果还有数据,等待一会 [`ed22a202`](https://github.com/Veloera/Veloera/commit/ed22a202) +- 修复客户端中断请求,计算补全阻塞问题 [`6680f6d8`](https://github.com/Veloera/Veloera/commit/6680f6d8) +- fix postgresql support (#606) [`a398f359`](https://github.com/Veloera/Veloera/commit/a398f359) +- set Accept header if not given (#615) [`57aa637c`](https://github.com/Veloera/Veloera/commit/57aa637c) +- docker compose healthcheck failed (#593) [`64cdb7ea`](https://github.com/Veloera/Veloera/commit/64cdb7ea) +- fix array index not checked (close #588) [`a27a5bcc`](https://github.com/Veloera/Veloera/commit/a27a5bcc) +- fix request count not updated correctly when using batch update [`f073592d`](https://github.com/Veloera/Veloera/commit/f073592d) +- fix url not passing (#562) [`fa41ca98`](https://github.com/Veloera/Veloera/commit/fa41ca98) +- 404 Component is missing (#592) [`e338de45`](https://github.com/Veloera/Veloera/commit/e338de45) +- check user quota when pre-consume quota [`47c08c72`](https://github.com/Veloera/Veloera/commit/47c08c72) +- fix midjourney task update bug [`f7870b1b`](https://github.com/Veloera/Veloera/commit/f7870b1b) +- sum null to 0 (#541) [`8651451e`](https://github.com/Veloera/Veloera/commit/8651451e) +- fix gorm expression [`1c5bb97a`](https://github.com/Veloera/Veloera/commit/1c5bb97a) +- fix gorm expression [`de868e4e`](https://github.com/Veloera/Veloera/commit/de868e4e) +- add default value for base url [`1d258cc8`](https://github.com/Veloera/Veloera/commit/1d258cc8) +- fix random selection is not working when directly using database [`37e09d76`](https://github.com/Veloera/Veloera/commit/37e09d76) +- fix unable to set zero value for base url & model mapping [`159b9e33`](https://github.com/Veloera/Veloera/commit/159b9e33) +- fix priority not updated & random choice not working [`a5647b1e`](https://github.com/Veloera/Veloera/commit/a5647b1e) +- enable cors for token routers and dashboard routers [`946eed4d`](https://github.com/Veloera/Veloera/commit/946eed4d) +- fix url not passing when using custom chat_link [`12ef9679`](https://github.com/Veloera/Veloera/commit/12ef9679) +- only enable cors for relay routers to avoid csrf attack [`25c4c111`](https://github.com/Veloera/Veloera/commit/25c4c111) +- fix oauth2 state not checking [`39ae8075`](https://github.com/Veloera/Veloera/commit/39ae8075) +- fix quota not return when error occurred (close #518) [`01863d3e`](https://github.com/Veloera/Veloera/commit/01863d3e) +- support ali's embedding model (#481, close #469) [`d0a0e871`](https://github.com/Veloera/Veloera/commit/d0a0e871) +- press enter to submit custom model name [`276163af`](https://github.com/Veloera/Veloera/commit/276163af) +- fix log table use created_at as key instead of id [`f0d5e102`](https://github.com/Veloera/Veloera/commit/f0d5e102) +- fix the issue of function_call not working when using model mapping (#462) [`4f2f911e`](https://github.com/Veloera/Veloera/commit/4f2f911e) +- fix error response (close #468) [`a3e267df`](https://github.com/Veloera/Veloera/commit/a3e267df) +- disable channel when 401 received (close #467) [`ac7c0f3a`](https://github.com/Veloera/Veloera/commit/ac7c0f3a) +- fix xunfei crash (#451) [`efeb9a16`](https://github.com/Veloera/Veloera/commit/efeb9a16) +- empty completion issue caused by bad status code from upstream channel (#422) [`05e4f2b4`](https://github.com/Veloera/Veloera/commit/05e4f2b4) +- fix baidu & ali's quota calculation (#444) [`dfaa0183`](https://github.com/Veloera/Veloera/commit/dfaa0183) +- claude model ratio (#449) [`23b1c635`](https://github.com/Veloera/Veloera/commit/23b1c635) +- update cache immediately after cache get [`86c2627c`](https://github.com/Veloera/Veloera/commit/86c2627c) +- fix baidu's embedding api (#398) [`4ef5e202`](https://github.com/Veloera/Veloera/commit/4ef5e202) +- add lock when update quota (close #399) [`eae9b6e6`](https://github.com/Veloera/Veloera/commit/eae9b6e6) +- fix token name too long [`2a527ee4`](https://github.com/Veloera/Veloera/commit/2a527ee4) +- fix finish_reason fileld not fully compatible with OpenAI (close #372, #373) [`476a46ad`](https://github.com/Veloera/Veloera/commit/476a46ad) +- set connection limits for database [`466005de`](https://github.com/Veloera/Veloera/commit/466005de) +- disable eslint when building (close #371, close #376) [`2b088a16`](https://github.com/Veloera/Veloera/commit/2b088a16) +- update tiktoken-go's version to fix resource consumption problem (#392, close #161) [`3a18cebe`](https://github.com/Veloera/Veloera/commit/3a18cebe) +- update no route handler [`3b36608b`](https://github.com/Veloera/Veloera/commit/3b36608b) +- no need to check turnstile when process deletion [`463b0b3c`](https://github.com/Veloera/Veloera/commit/463b0b3c) +- fix stream mode determine related logic (close #360) [`d96cf2e8`](https://github.com/Veloera/Veloera/commit/d96cf2e8) +- calculate usage if not given in non-stream mode (#352) [`446337c3`](https://github.com/Veloera/Veloera/commit/446337c3) +- fix SparkDesk not billed (#344) [`2d49ca6a`](https://github.com/Veloera/Veloera/commit/2d49ca6a) +- fix zhipu streaming (#349) [`c2c455c9`](https://github.com/Veloera/Veloera/commit/c2c455c9) +- fix sse not ending properly in some case [`c9d2e42a`](https://github.com/Veloera/Veloera/commit/c9d2e42a) +- ignore data if not have proper prefix [`8cbbeb78`](https://github.com/Veloera/Veloera/commit/8cbbeb78) +- prompt user that channel test is unavailable [`ec88c0c2`](https://github.com/Veloera/Veloera/commit/ec88c0c2) +- close connection when response ended [`065147b4`](https://github.com/Veloera/Veloera/commit/065147b4) +- fix model mapping cannot be deleted [`f45d5864`](https://github.com/Veloera/Veloera/commit/f45d5864) +- fix model mapping cannot be deleted [`50dec03f`](https://github.com/Veloera/Veloera/commit/50dec03f) +- convert system message to user message for zhipu [`d1b6f492`](https://github.com/Veloera/Veloera/commit/d1b6f492) +- convert system message to user message for claude [`b9f6461d`](https://github.com/Veloera/Veloera/commit/b9f6461d) +- convert system message to user message (close #328) [`0a39521a`](https://github.com/Veloera/Veloera/commit/0a39521a) +- use channel type to determine api type (close #321) [`c134604c`](https://github.com/Veloera/Veloera/commit/c134604c) +- baseURL not working in APITypePaLM (#317) [`929e43ef`](https://github.com/Veloera/Veloera/commit/929e43ef) +- relay router typo for List models (#320) [`dce8bbe1`](https://github.com/Veloera/Veloera/commit/dce8bbe1) +- fix redemption can be used multiple times in some cases [`889af8b2`](https://github.com/Veloera/Veloera/commit/889af8b2) +- fix blank screen after refresh [`4ab3211c`](https://github.com/Veloera/Veloera/commit/4ab3211c) +- fix PaLM2 not billed [`e6938bd2`](https://github.com/Veloera/Veloera/commit/e6938bd2) +- fix model editing now working properly (close #288) [`3c940113`](https://github.com/Veloera/Veloera/commit/3c940113) +- using whitelist when disabling channels (close #292) [`0495b9a0`](https://github.com/Veloera/Veloera/commit/0495b9a0) +- restore display_name/username that deleted before (#268) [`ea73201b`](https://github.com/Veloera/Veloera/commit/ea73201b) +- use transaction to protect redeem process [`d17bdc40`](https://github.com/Veloera/Veloera/commit/d17bdc40) +- fix http2: invalid Connection request header: [\"upgrade\"]" [`991f5bf4`](https://github.com/Veloera/Veloera/commit/991f5bf4) +- fix refresh not working properly (close #229) [`9ec6506c`](https://github.com/Veloera/Veloera/commit/9ec6506c) +- fix cannot enable token if set to unlimited time (close #230) [`f0c40a6c`](https://github.com/Veloera/Veloera/commit/f0c40a6c) +- fix channel search is not working with MySQL [`0cea9e6a`](https://github.com/Veloera/Veloera/commit/0cea9e6a) +- fix AutomaticDisableChannelEnabled option not ignored (close #217) [`bddbf571`](https://github.com/Veloera/Veloera/commit/bddbf571) +- fix the wrong message when channel is deleted [`aabc5466`](https://github.com/Veloera/Veloera/commit/aabc5466) +- fix wrong env var name [`51f19470`](https://github.com/Veloera/Veloera/commit/51f19470) +- update cached user quota after post-consuming (close #204) [`737672fb`](https://github.com/Veloera/Veloera/commit/737672fb) +- InitChannelCache does not filter disabled channels (#201) [`f0dc7f3f`](https://github.com/Veloera/Veloera/commit/f0dc7f3f) +- check if token is nil before using it [`6b1a24d6`](https://github.com/Veloera/Veloera/commit/6b1a24d6) +- do not record if used quota is zero [`57bd907f`](https://github.com/Veloera/Veloera/commit/57bd907f) +- do not charge the user if the amount of tokens used was zero [`dd8e8d5e`](https://github.com/Veloera/Veloera/commit/dd8e8d5e) +- fix usage is not correct [`1ca1aa0c`](https://github.com/Veloera/Veloera/commit/1ca1aa0c) +- fix log sorting (#195) [`f2ba0c03`](https://github.com/Veloera/Veloera/commit/f2ba0c03) +- do not reuse state variable directly [`f5c1fcd3`](https://github.com/Veloera/Veloera/commit/f5c1fcd3) +- reset page number after query [`5fdf670a`](https://github.com/Veloera/Veloera/commit/5fdf670a) +- remove tqdm dependency [`f5564727`](https://github.com/Veloera/Veloera/commit/f5564727) +- fix http status code (close #193) [`8a4cd403`](https://github.com/Veloera/Veloera/commit/8a4cd403) +- disable redis on master node [`28fb4d76`](https://github.com/Veloera/Veloera/commit/28fb4d76) +- fix time_test.sh (#191) [`ca779e4f`](https://github.com/Veloera/Veloera/commit/ca779e4f) +- update time_test.sh [`f51c9824`](https://github.com/Veloera/Veloera/commit/f51c9824) +- only master node can migrate database [`567916bd`](https://github.com/Veloera/Veloera/commit/567916bd) +- fix channel table's sorting problem (#188) [`1f3b3ca7`](https://github.com/Veloera/Veloera/commit/1f3b3ca7) +- do not show dollar balance if not enabled [`6536a7be`](https://github.com/Veloera/Veloera/commit/6536a7be) +- cors allow all headers [`634099e5`](https://github.com/Veloera/Veloera/commit/634099e5) +- fix used amount not correct [`ced9f060`](https://github.com/Veloera/Veloera/commit/ced9f060) +- fix crash when Redis is enabled [`14b85318`](https://github.com/Veloera/Veloera/commit/14b85318) +- fix wrong field name in SQL [`aa0a9f22`](https://github.com/Veloera/Veloera/commit/aa0a9f22) +- do not print response to console [`dc94765d`](https://github.com/Veloera/Veloera/commit/dc94765d) +- fix the shared field is modified [`1cb1f727`](https://github.com/Veloera/Veloera/commit/1cb1f727) +- reduce the table size (close #174) [`bcbfacc0`](https://github.com/Veloera/Veloera/commit/bcbfacc0) +- limit the length of email [`a9099723`](https://github.com/Veloera/Veloera/commit/a9099723) +- fix footer not updated asap [`d79289cc`](https://github.com/Veloera/Veloera/commit/d79289cc) +- do not record completion ratio anymore [`58fb18aa`](https://github.com/Veloera/Veloera/commit/58fb18aa) +- the prompt field can be array type now (close #166, #167) [`630156dc`](https://github.com/Veloera/Veloera/commit/630156dc) +- fix file not committed [`d6dbaff3`](https://github.com/Veloera/Veloera/commit/d6dbaff3) +- now the input field can be array type now (close #149) [`7c7eb6b7`](https://github.com/Veloera/Veloera/commit/7c7eb6b7) +- fix OpenAI-SB balance not correct [`8b2ef666`](https://github.com/Veloera/Veloera/commit/8b2ef666) +- fix group list not correct (close #147) [`955d5f87`](https://github.com/Veloera/Veloera/commit/955d5f87) +- correct OpenAI error code's type [`f97a9ce5`](https://github.com/Veloera/Veloera/commit/f97a9ce5) +- fix not using proxy when update balance [`145bb14c`](https://github.com/Veloera/Veloera/commit/145bb14c) +- fix redemption code's quota not updated [`e0d0674f`](https://github.com/Veloera/Veloera/commit/e0d0674f) +- able to manage root user now [`521ede24`](https://github.com/Veloera/Veloera/commit/521ede24) +- prompt user the feat is not implemented (#125) [`1e1c6a82`](https://github.com/Veloera/Veloera/commit/1e1c6a82) +- fix balance query (close #138) [`98f1a627`](https://github.com/Veloera/Veloera/commit/98f1a627) +- fix balance query (close #138) [`333e4216`](https://github.com/Veloera/Veloera/commit/333e4216) +- add a blank VERSION file (#135) [`7e80e2da`](https://github.com/Veloera/Veloera/commit/7e80e2da) +- update common.RootUserEmail when root's email changed (#132) [`0f6958c5`](https://github.com/Veloera/Veloera/commit/0f6958c5) +- update docker-compose.yml (#131) [`5f045f8c`](https://github.com/Veloera/Veloera/commit/5f045f8c) +- fetch root user's email if blank (#129) [`f19ee053`](https://github.com/Veloera/Veloera/commit/f19ee053) +- fix wrong implementation for /v1/models (close #128) [`fa71daa8`](https://github.com/Veloera/Veloera/commit/fa71daa8) +- add no-cache for index.html [`8b43e0dd`](https://github.com/Veloera/Veloera/commit/8b43e0dd) +- remove no-store for index.html [`92c88fa2`](https://github.com/Veloera/Veloera/commit/92c88fa2) +- do not cache index.html [`38191d55`](https://github.com/Veloera/Veloera/commit/38191d55) +- disable channel with a whitelist [`d9e39f59`](https://github.com/Veloera/Veloera/commit/d9e39f59) +- fix unable to update custom channel's balance [`17b7646c`](https://github.com/Veloera/Veloera/commit/17b7646c) +- show bind options only available (close #65) [`b92ec5e5`](https://github.com/Veloera/Veloera/commit/b92ec5e5) +- use gpt-3.5's encoder if not found (close #110) [`fa79e8b7`](https://github.com/Veloera/Veloera/commit/fa79e8b7) +- make the token number calculation more accurate (#101) [`7c6bf3e9`](https://github.com/Veloera/Veloera/commit/7c6bf3e9) +- add X-Accel-Buffering header on SSE response [`2779d662`](https://github.com/Veloera/Veloera/commit/2779d662) +- fix channel test error checking [`6da34108`](https://github.com/Veloera/Veloera/commit/6da34108) +- handel error response from server correctly (close #90) [`ceb289cb`](https://github.com/Veloera/Veloera/commit/ceb289cb) +- fix error log not recorded (close #83) [`ad01e1f3`](https://github.com/Veloera/Veloera/commit/ad01e1f3) +- fix stream mode checking (#83) [`cc1ef2ff`](https://github.com/Veloera/Veloera/commit/cc1ef2ff) +- update api2d's base url (#83) [`7201bd1c`](https://github.com/Veloera/Veloera/commit/7201bd1c) +- limit the shown text's length (close #80) [`e8da9813`](https://github.com/Veloera/Veloera/commit/e8da9813) +- fix quota not consuming [`8afdc56b`](https://github.com/Veloera/Veloera/commit/8afdc56b) +- fix topup page now showing [`a9ea1d9d`](https://github.com/Veloera/Veloera/commit/a9ea1d9d) +- fix token quota not updated [`ea8e7c51`](https://github.com/Veloera/Veloera/commit/ea8e7c51) +- fix option update logic not working properly [`c48327ff`](https://github.com/Veloera/Veloera/commit/c48327ff) +- fix tab icon & title not changed (close #69) [`a5406c69`](https://github.com/Veloera/Veloera/commit/a5406c69) +- fix lock is not working [`aae92683`](https://github.com/Veloera/Veloera/commit/aae92683) +- remove version suffix for Azure (close #67) [`cc3072c4`](https://github.com/Veloera/Veloera/commit/cc3072c4) +- fix /v1/models not working (close #66) [`bffee4e9`](https://github.com/Veloera/Veloera/commit/bffee4e9) +- fix quota not consumed [`4a0e81fe`](https://github.com/Veloera/Veloera/commit/4a0e81fe) +- fix "[DONE is not valid JSON" (#57) [`246b981e`](https://github.com/Veloera/Veloera/commit/246b981e) +- fix Azure channel not working in stream mode (#57) [`2edd52e8`](https://github.com/Veloera/Veloera/commit/2edd52e8) +- fix SMTPFrom not updated in some cases (close #34) [`e123c66b`](https://github.com/Veloera/Veloera/commit/e123c66b) +- fix garbled email subject (#34) [`9edc82bd`](https://github.com/Veloera/Veloera/commit/9edc82bd) +- support smtp server with port 465 [`46e77389`](https://github.com/Veloera/Veloera/commit/46e77389) +- remove the dot in model name (#57) [`73aa53f5`](https://github.com/Veloera/Veloera/commit/73aa53f5) +- provide a default value for api-version if not given (#57) [`44729da2`](https://github.com/Veloera/Veloera/commit/44729da2) +- handle errors when update option map [`fd19d7d2`](https://github.com/Veloera/Veloera/commit/fd19d7d2) +- fix the default ratio for text-embedding-ada-002 [`9edc54ca`](https://github.com/Veloera/Veloera/commit/9edc54ca) +- the initial quota for new token now calculated correctly (#51) [`e6af636f`](https://github.com/Veloera/Veloera/commit/e6af636f) +- delete a token with a negative quota will now update the account's quota correctly (close #51) [`241ade2f`](https://github.com/Veloera/Veloera/commit/241ade2f) +- return quota to user when delete token (close #37) [`331177d9`](https://github.com/Veloera/Veloera/commit/331177d9) +- shouldn't close c.Request.Body too soon (close #35) [`7c66fc6c`](https://github.com/Veloera/Veloera/commit/7c66fc6c) +- relay bug fix [`aea6c859`](https://github.com/Veloera/Veloera/commit/aea6c859) +- check user's role when manage user (#30) [`7a5057f0`](https://github.com/Veloera/Veloera/commit/7a5057f0) +- check user status when validating token (#23) [`54b1e4ad`](https://github.com/Veloera/Veloera/commit/54b1e4ad) +- root user cannot demote itself now (close #30) [`92728843`](https://github.com/Veloera/Veloera/commit/92728843) +- fix MySQL syntax error (#54) [`195e94a7`](https://github.com/Veloera/Veloera/commit/195e94a7) +- specify type for token (close #23) [`5bfc2246`](https://github.com/Veloera/Veloera/commit/5bfc2246) +- remove rate limit for relay api [`fd149c24`](https://github.com/Veloera/Veloera/commit/fd149c24) +- allow all origins (close #20) [`e7a809b0`](https://github.com/Veloera/Veloera/commit/e7a809b0) +- prevent common user from specifying channel id (#12) [`4f8cbd64`](https://github.com/Veloera/Veloera/commit/4f8cbd64) +- allow all origins (close #20) [`1dd92a3f`](https://github.com/Veloera/Veloera/commit/1dd92a3f) +- only reduce remain times when request `/v1/chat/completions` (close #15) [`109736cc`](https://github.com/Veloera/Veloera/commit/109736cc) +- fully support stream mode now (close #3) [`69ee87c5`](https://github.com/Veloera/Veloera/commit/69ee87c5) +- improve the implementation of sse [`b74a17c9`](https://github.com/Veloera/Veloera/commit/b74a17c9) +- fixing SSE support [`5d602e9b`](https://github.com/Veloera/Veloera/commit/5d602e9b) +- update error message to make it more clear (#13) [`01c1b906`](https://github.com/Veloera/Veloera/commit/01c1b906) +- relay more headers [`16271e78`](https://github.com/Veloera/Veloera/commit/16271e78) +- only keep header Authorization & Content-Type [`0198df59`](https://github.com/Veloera/Veloera/commit/0198df59) +- fix http2: invalid Connection request header: ["upgrade"] [`4c0dc50a`](https://github.com/Veloera/Veloera/commit/4c0dc50a) +- copy token to search input if clipboard.writeText is not available (close #6) [`423978ba`](https://github.com/Veloera/Veloera/commit/423978ba) + +### Continuous Integration +- Let CI only push to ghcr.io [`1b510d19`](https://github.com/Veloera/Veloera/commit/1b510d19) +- Correct the filename in linux_release [`acf20b08`](https://github.com/Veloera/Veloera/commit/acf20b08) +- Update CI [`c643523f`](https://github.com/Veloera/Veloera/commit/c643523f) +- update ci [`bb313eb2`](https://github.com/Veloera/Veloera/commit/bb313eb2) +- update ci [`2a345ae0`](https://github.com/Veloera/Veloera/commit/2a345ae0) +- update workflows [`1bfc46aa`](https://github.com/Veloera/Veloera/commit/1bfc46aa) +- add stage caching to Dockerfile (#408) [`e4bacc45`](https://github.com/Veloera/Veloera/commit/e4bacc45) +- fix GitHub Actions config [`25017219`](https://github.com/Veloera/Veloera/commit/25017219) +- rename GitHub Actions file [`61dc117d`](https://github.com/Veloera/Veloera/commit/61dc117d) +- ignore alpha version [`1932c56e`](https://github.com/Veloera/Veloera/commit/1932c56e) +- build arm version [`79dc53ff`](https://github.com/Veloera/Veloera/commit/79dc53ff) +- remove arm64 image builder [`570b3bc7`](https://github.com/Veloera/Veloera/commit/570b3bc7) +- remove useless action [`4e8dc8d0`](https://github.com/Veloera/Veloera/commit/4e8dc8d0) + +### Performance Improvements +- 运营设置-提示文案 [`470f3a1d`](https://github.com/Veloera/Veloera/commit/470f3a1d) +- 运营设置-数据刷新逻辑 [`65ae7091`](https://github.com/Veloera/Veloera/commit/65ae7091) +- 移除不生效的参数 [`a9d9877b`](https://github.com/Veloera/Veloera/commit/a9d9877b) +- add useEffect and useNavigate hooks to Setting component [`290dcf75`](https://github.com/Veloera/Veloera/commit/290dcf75) +- add tabActiveKey state to Setting component [`aa23c51a`](https://github.com/Veloera/Veloera/commit/aa23c51a) +- 优化其他设置显示效果 [`37113c0e`](https://github.com/Veloera/Veloera/commit/37113c0e) +- code logic [`931d22c9`](https://github.com/Veloera/Veloera/commit/931d22c9) +- prompt when the name of the custom model input already exists [`92c1ed7f`](https://github.com/Veloera/Veloera/commit/92c1ed7f) +- 美化绘画界面UI [`e688e413`](https://github.com/Veloera/Veloera/commit/e688e413) +- 数据看板支持选择时间粒度 [`00306aa1`](https://github.com/Veloera/Veloera/commit/00306aa1) +- UI美化 [`d04d2a6c`](https://github.com/Veloera/Veloera/commit/d04d2a6c) +- 美化数据看板 [`e2317524`](https://github.com/Veloera/Veloera/commit/e2317524) +- 优化数据看板性能 [`1618a8c9`](https://github.com/Veloera/Veloera/commit/1618a8c9) +- 优化数据看板性能和显示效果 [`95e84f2b`](https://github.com/Veloera/Veloera/commit/95e84f2b) +- 美化数据看板 [`8f36a995`](https://github.com/Veloera/Veloera/commit/8f36a995) +- lazy initialization for token encoders (close #566) [`594f06e7`](https://github.com/Veloera/Veloera/commit/594f06e7) +- only return quota when it's not zero [`420c3751`](https://github.com/Veloera/Veloera/commit/420c3751) +- preallocate array capacity [`abbf2fde`](https://github.com/Veloera/Veloera/commit/abbf2fde) +- initialize all token encoder when starting (close #459, close $460) [`fdb2cccf`](https://github.com/Veloera/Veloera/commit/fdb2cccf) +- use a goroutine to handle quota post consumption (#364) [`0e9ff882`](https://github.com/Veloera/Veloera/commit/0e9ff882) +- flush response after response handled (close #364) [`7bddc73b`](https://github.com/Veloera/Veloera/commit/7bddc73b) +- reuse http client to reduce delay [`3da119ef`](https://github.com/Veloera/Veloera/commit/3da119ef) +- validate the request first before send to OpenAI's server [`f6eb4e56`](https://github.com/Veloera/Veloera/commit/f6eb4e56) +- use max_tokens to reduce token consuming [`58fe923c`](https://github.com/Veloera/Veloera/commit/58fe923c) +- load cached about content first (#60) [`57cb1501`](https://github.com/Veloera/Veloera/commit/57cb1501) +- use slice to improve efficiency (#57) [`cf688377`](https://github.com/Veloera/Veloera/commit/cf688377) + +### Documentation +- Add whitespace between badges [`c52b112b`](https://github.com/Veloera/Veloera/commit/c52b112b) +- Add cool badges in README [`f0df9d66`](https://github.com/Veloera/Veloera/commit/f0df9d66) +- Add CLAUDE.md [`b5fac30a`](https://github.com/Veloera/Veloera/commit/b5fac30a) +- Sync features section with latest features [`8012db8b`](https://github.com/Veloera/Veloera/commit/8012db8b) +- Add features section at README [`c35392a6`](https://github.com/Veloera/Veloera/commit/c35392a6) +- Replace most new-api with Veloera [`4c03f368`](https://github.com/Veloera/Veloera/commit/4c03f368) +- Update README [`8d02adee`](https://github.com/Veloera/Veloera/commit/8d02adee) +- Change SQLite filename to veloera.db [`9562db63`](https://github.com/Veloera/Veloera/commit/9562db63) +- Rename to Veloera [`e0f83fa7`](https://github.com/Veloera/Veloera/commit/e0f83fa7) +- Put New Api's stuffs to a sepurate folder [`78d8bf29`](https://github.com/Veloera/Veloera/commit/78d8bf29) +- Replace most New API with Gating [`8fdd2c2e`](https://github.com/Veloera/Veloera/commit/8fdd2c2e) +- fix a typo [`be6ffd3c`](https://github.com/Veloera/Veloera/commit/be6ffd3c) +- Update README [`3de5b96c`](https://github.com/Veloera/Veloera/commit/3de5b96c) +- Add proxy usage information note in SystemSetting component [`585c19fc`](https://github.com/Veloera/Veloera/commit/585c19fc) +- Update README with detailed Docker deployment and update instructions [`20723766`](https://github.com/Veloera/Veloera/commit/20723766) +- update README [`1eb706de`](https://github.com/Veloera/Veloera/commit/1eb706de) +- update Midjourney.md [`4a4df758`](https://github.com/Veloera/Veloera/commit/4a4df758) +- update [`2c187fde`](https://github.com/Veloera/Veloera/commit/2c187fde) +- add description for TIKTOKEN_CACHE_DIR (#612) [`22980b4c`](https://github.com/Veloera/Veloera/commit/22980b4c) +- update readme [`fbdea91e`](https://github.com/Veloera/Veloera/commit/fbdea91e) +- update readme [`0f6c132a`](https://github.com/Veloera/Veloera/commit/0f6c132a) +- update readme [`3cac45dc`](https://github.com/Veloera/Veloera/commit/3cac45dc) +- update readme [`197d1d7a`](https://github.com/Veloera/Veloera/commit/197d1d7a) +- update readme (#502) [`215e54fc`](https://github.com/Veloera/Veloera/commit/215e54fc) +- update README [`fe26a144`](https://github.com/Veloera/Veloera/commit/fe26a144) +- update readme [`b57a0eca`](https://github.com/Veloera/Veloera/commit/b57a0eca) +- add QChatGPT (#522) [`1b4cc788`](https://github.com/Veloera/Veloera/commit/1b4cc788) +- update README (close #482) [`c55bb678`](https://github.com/Veloera/Veloera/commit/c55bb678) +- update README (close #482) [`0f949c37`](https://github.com/Veloera/Veloera/commit/0f949c37) +- update README [`9db93316`](https://github.com/Veloera/Veloera/commit/9db93316) +- update README [`ef2c5abb`](https://github.com/Veloera/Veloera/commit/ef2c5abb) +- update README [`49d1a634`](https://github.com/Veloera/Veloera/commit/49d1a634) +- update README [`2a7b8265`](https://github.com/Veloera/Veloera/commit/2a7b8265) +- update README [`8ea7b9aa`](https://github.com/Veloera/Veloera/commit/8ea7b9aa) +- update README [`5136b126`](https://github.com/Veloera/Veloera/commit/5136b126) +- update README [`80a49e01`](https://github.com/Veloera/Veloera/commit/80a49e01) +- update README [`8fb082ba`](https://github.com/Veloera/Veloera/commit/8fb082ba) +- add Japanese README (#425) [`90b4cac7`](https://github.com/Veloera/Veloera/commit/90b4cac7) +- update README [`da1d8199`](https://github.com/Veloera/Veloera/commit/da1d8199) +- update README [`821c559e`](https://github.com/Veloera/Veloera/commit/821c559e) +- update README [`7e2bca7e`](https://github.com/Veloera/Veloera/commit/7e2bca7e) +- update README [`be780462`](https://github.com/Veloera/Veloera/commit/be780462) +- update README (#374) [`f2159e10`](https://github.com/Veloera/Veloera/commit/f2159e10) +- update FastGPT's description (#388) [`29fa94e7`](https://github.com/Veloera/Veloera/commit/29fa94e7) +- update README [`9c436921`](https://github.com/Veloera/Veloera/commit/9c436921) +- update readme (#359) [`89bcaaf9`](https://github.com/Veloera/Veloera/commit/89bcaaf9) +- update deploy-on-sealos (#351) [`afcd1bd2`](https://github.com/Veloera/Veloera/commit/afcd1bd2) +- update README [`30a7f1a1`](https://github.com/Veloera/Veloera/commit/30a7f1a1) +- update README [`d1335ebc`](https://github.com/Veloera/Veloera/commit/d1335ebc) +- update README [`4ec42895`](https://github.com/Veloera/Veloera/commit/4ec42895) +- update README [`3dc5a0f9`](https://github.com/Veloera/Veloera/commit/3dc5a0f9) +- update README [`80a84667`](https://github.com/Veloera/Veloera/commit/80a84667) +- update README [`02da0b51`](https://github.com/Veloera/Veloera/commit/02da0b51) +- update README (close #257) [`6215d2e7`](https://github.com/Veloera/Veloera/commit/6215d2e7) +- update README [`280df277`](https://github.com/Veloera/Veloera/commit/280df277) +- update README [`04f40def`](https://github.com/Veloera/Veloera/commit/04f40def) +- update README [`c48b7bc0`](https://github.com/Veloera/Veloera/commit/c48b7bc0) +- update README [`1ab5fb7d`](https://github.com/Veloera/Veloera/commit/1ab5fb7d) +- update README [`f769711c`](https://github.com/Veloera/Veloera/commit/f769711c) +- update README [`f387cc5e`](https://github.com/Veloera/Veloera/commit/f387cc5e) +- update README [`569b68c4`](https://github.com/Veloera/Veloera/commit/569b68c4) +- update README [`8f6bd51f`](https://github.com/Veloera/Veloera/commit/8f6bd51f) +- update README [`9a16b0f9`](https://github.com/Veloera/Veloera/commit/9a16b0f9) +- update issue templates [`3530309a`](https://github.com/Veloera/Veloera/commit/3530309a) +- update issue templates [`733ebc06`](https://github.com/Veloera/Veloera/commit/733ebc06) +- update issue templates [`6a8567ac`](https://github.com/Veloera/Veloera/commit/6a8567ac) +- update README [`1c82b06f`](https://github.com/Veloera/Veloera/commit/1c82b06f) +- update README [`9e410967`](https://github.com/Veloera/Veloera/commit/9e410967) +- update README [`64c35334`](https://github.com/Veloera/Veloera/commit/64c35334) +- update README (#210) [`0ce572b4`](https://github.com/Veloera/Veloera/commit/0ce572b4) +- update README [`05b0e778`](https://github.com/Veloera/Veloera/commit/05b0e778) +- update README [`f81f4c60`](https://github.com/Veloera/Veloera/commit/f81f4c60) +- update README [`c613d8b6`](https://github.com/Veloera/Veloera/commit/c613d8b6) +- update README [`77a16e64`](https://github.com/Veloera/Veloera/commit/77a16e64) +- update README [`827942c8`](https://github.com/Veloera/Veloera/commit/827942c8) +- update README [`aeb1cad6`](https://github.com/Veloera/Veloera/commit/aeb1cad6) +- update README [`7497f24d`](https://github.com/Veloera/Veloera/commit/7497f24d) +- update README [`97cdb616`](https://github.com/Veloera/Veloera/commit/97cdb616) +- update README [`70cffbc2`](https://github.com/Veloera/Veloera/commit/70cffbc2) +- update README [`868f0474`](https://github.com/Veloera/Veloera/commit/868f0474) +- update README [`42500642`](https://github.com/Veloera/Veloera/commit/42500642) +- update README [`e5ac80c1`](https://github.com/Veloera/Veloera/commit/e5ac80c1) +- update README [`9291b5fb`](https://github.com/Veloera/Veloera/commit/9291b5fb) +- update README [`d97f1df3`](https://github.com/Veloera/Veloera/commit/d97f1df3) +- update README [`f0434c81`](https://github.com/Veloera/Veloera/commit/f0434c81) +- update README [`0e35050b`](https://github.com/Veloera/Veloera/commit/0e35050b) +- update README [`4010164d`](https://github.com/Veloera/Veloera/commit/4010164d) +- update README [`ba89abed`](https://github.com/Veloera/Veloera/commit/ba89abed) +- update issue template [`a680b1b8`](https://github.com/Veloera/Veloera/commit/a680b1b8) +- update README [`b3b7d0a0`](https://github.com/Veloera/Veloera/commit/b3b7d0a0) +- update README (close #175) [`5531e215`](https://github.com/Veloera/Veloera/commit/5531e215) +- update README [`6dcffca0`](https://github.com/Veloera/Veloera/commit/6dcffca0) +- update README [`d754620e`](https://github.com/Veloera/Veloera/commit/d754620e) +- update README [`21111126`](https://github.com/Veloera/Veloera/commit/21111126) +- update README [`d91e7dcf`](https://github.com/Veloera/Veloera/commit/d91e7dcf) +- deploy to Zeabur (#170) [`f89f6c7f`](https://github.com/Veloera/Veloera/commit/f89f6c7f) +- update README [`57b213a0`](https://github.com/Veloera/Veloera/commit/57b213a0) +- update README [`549e944b`](https://github.com/Veloera/Veloera/commit/549e944b) +- update notice [`5f23f59d`](https://github.com/Veloera/Veloera/commit/5f23f59d) +- update issue template (#164) [`538a5d7a`](https://github.com/Veloera/Veloera/commit/538a5d7a) +- update issue template [`07cccdc8`](https://github.com/Veloera/Veloera/commit/07cccdc8) +- update issue template [`f71f0166`](https://github.com/Veloera/Veloera/commit/f71f0166) +- update issue template [`54d7a1c2`](https://github.com/Veloera/Veloera/commit/54d7a1c2) +- update issue template [`f426f31b`](https://github.com/Veloera/Veloera/commit/f426f31b) +- update issue template [`2930577c`](https://github.com/Veloera/Veloera/commit/2930577c) +- add issue templates [`e0951217`](https://github.com/Veloera/Veloera/commit/e0951217) +- update README [`0c34ed4c`](https://github.com/Veloera/Veloera/commit/0c34ed4c) +- update README [`69153e72`](https://github.com/Veloera/Veloera/commit/69153e72) +- update README [`cdef10ca`](https://github.com/Veloera/Veloera/commit/cdef10ca) +- update README [`e398e075`](https://github.com/Veloera/Veloera/commit/e398e075) +- update README [`0b4bf309`](https://github.com/Veloera/Veloera/commit/0b4bf309) +- update README [`c6edb78a`](https://github.com/Veloera/Veloera/commit/c6edb78a) +- update README [`502515bb`](https://github.com/Veloera/Veloera/commit/502515bb) +- update README [`b3839831`](https://github.com/Veloera/Veloera/commit/b3839831) +- update README (#103) [`741c0b9c`](https://github.com/Veloera/Veloera/commit/741c0b9c) +- update README [`481ba41f`](https://github.com/Veloera/Veloera/commit/481ba41f) +- update README (close #97) [`e509899d`](https://github.com/Veloera/Veloera/commit/e509899d) +- update README [`b53cdbaf`](https://github.com/Veloera/Veloera/commit/b53cdbaf) +- fix typo [`09c2e3bc`](https://github.com/Veloera/Veloera/commit/09c2e3bc) +- fix typo [`5cba800f`](https://github.com/Veloera/Veloera/commit/5cba800f) +- update README [`6f8cc712`](https://github.com/Veloera/Veloera/commit/6f8cc712) +- update README [`976c29ea`](https://github.com/Veloera/Veloera/commit/976c29ea) +- update README [`da9ccb52`](https://github.com/Veloera/Veloera/commit/da9ccb52) +- update README (#47) [`8b056bf4`](https://github.com/Veloera/Veloera/commit/8b056bf4) +- add funding link [`e5640857`](https://github.com/Veloera/Veloera/commit/e5640857) +- update LICENSE [`34a3329f`](https://github.com/Veloera/Veloera/commit/34a3329f) +- update README [`1e46b9d1`](https://github.com/Veloera/Veloera/commit/1e46b9d1) +- update Nginx configuration (#3) [`faf84d83`](https://github.com/Veloera/Veloera/commit/faf84d83) +- update Nginx config (close #3) [`eb8f43ac`](https://github.com/Veloera/Veloera/commit/eb8f43ac) +- add help in command line arguments (#14) [`f067f64a`](https://github.com/Veloera/Veloera/commit/f067f64a) +- update deploy tutorial [`f6194fa8`](https://github.com/Veloera/Veloera/commit/f6194fa8) +- fix typo [`abb2449b`](https://github.com/Veloera/Veloera/commit/abb2449b) +- update README [`cc5ef987`](https://github.com/Veloera/Veloera/commit/cc5ef987) +- update Docker start command (close #8) [`9e30524e`](https://github.com/Veloera/Veloera/commit/9e30524e) +- add missing manual deployment documentation (#7) [`336c03a1`](https://github.com/Veloera/Veloera/commit/336c03a1) + +### Chore +- Rename to Veloera [`5df1fc6a`](https://github.com/Veloera/Veloera/commit/5df1fc6a) +- Add aider files to gitignore [`56f6c089`](https://github.com/Veloera/Veloera/commit/56f6c089) +- Fix proxy path in Vite config [`fd7265c6`](https://github.com/Veloera/Veloera/commit/fd7265c6) +- Working on new interface [`664017a5`](https://github.com/Veloera/Veloera/commit/664017a5) +- Delete patch files [`4946af08`](https://github.com/Veloera/Veloera/commit/4946af08) +- Make session more stable [`3deccb02`](https://github.com/Veloera/Veloera/commit/3deccb02) +- update .gitignore and docker-compose.yml to include tiktoken_cache directory [`75570af9`](https://github.com/Veloera/Veloera/commit/75570af9) +- Update GitHub Actions workflows and refactor adaptor logic for Docker image builds [`6187656a`](https://github.com/Veloera/Veloera/commit/6187656a) +- Update terminology from "cache ratio" to "cache multiplier" in UI and add placeholder for default create cache ratio [`6d79d899`](https://github.com/Veloera/Veloera/commit/6d79d899) +- Update Azure OpenAI API version and embedding model detection [`1bcf7a3c`](https://github.com/Veloera/Veloera/commit/1bcf7a3c) +- update rerank.md [`c44a32ef`](https://github.com/Veloera/Veloera/commit/c44a32ef) +- update env name and README [`90191b8d`](https://github.com/Veloera/Veloera/commit/90191b8d) +- disable cgo [`cad8a832`](https://github.com/Veloera/Veloera/commit/cad8a832) +- disable cgo [`40d878e8`](https://github.com/Veloera/Veloera/commit/40d878e8) +- replace sqlite lib with prue go lib [`3a2e2244`](https://github.com/Veloera/Veloera/commit/3a2e2244) +- update CI [`13d1b820`](https://github.com/Veloera/Veloera/commit/13d1b820) +- update CI [`1b4fe860`](https://github.com/Veloera/Veloera/commit/1b4fe860) +- update CI [`d10b4700`](https://github.com/Veloera/Veloera/commit/d10b4700) +- 同步deepseek价格 [`34fdac38`](https://github.com/Veloera/Veloera/commit/34fdac38) +- sync gemini aistudio model [`efdd6fb6`](https://github.com/Veloera/Veloera/commit/efdd6fb6) +- update Node.js version in CI workflows from 16 to 18 [`a652ac61`](https://github.com/Veloera/Veloera/commit/a652ac61) +- add ffmpeg-tools to Dockerfile for enhanced multimedia processing [`e9256760`](https://github.com/Veloera/Veloera/commit/e9256760) +- change workflow runners to self-hosted for Docker and release jobs [`82ae6e4e`](https://github.com/Veloera/Veloera/commit/82ae6e4e) +- update language in index.html to Chinese [`d75ecfc6`](https://github.com/Veloera/Veloera/commit/d75ecfc6) +- 更新gemini模型 [`f3a71197`](https://github.com/Veloera/Veloera/commit/f3a71197) +- 更新gemini模型 [`3fa1d93b`](https://github.com/Veloera/Veloera/commit/3fa1d93b) +- Update dependencies and refactor JSON handling #614 [`79de02b0`](https://github.com/Veloera/Veloera/commit/79de02b0) +- update default STREAMING_TIMEOUT [`7b3394d8`](https://github.com/Veloera/Veloera/commit/7b3394d8) +- Update docker-compose.yml [`9e087097`](https://github.com/Veloera/Veloera/commit/9e087097) +- go.mod [`e8286e47`](https://github.com/Veloera/Veloera/commit/e8286e47) +- update .env.example [`b012505f`](https://github.com/Veloera/Veloera/commit/b012505f) +- update .env.example [`c7c870d4`](https://github.com/Veloera/Veloera/commit/c7c870d4) +- 删除无用日志 [`0a80231e`](https://github.com/Veloera/Veloera/commit/0a80231e) +- update model ratio [`fe0ed128`](https://github.com/Veloera/Veloera/commit/fe0ed128) +- 更新令牌分组描述 [`50eab6b4`](https://github.com/Veloera/Veloera/commit/50eab6b4) +- 令牌分组描述歧义 [`2e734e0c`](https://github.com/Veloera/Veloera/commit/2e734e0c) +- update footer [`be54369c`](https://github.com/Veloera/Veloera/commit/be54369c) +- remove useless code [`0c46d0c7`](https://github.com/Veloera/Veloera/commit/0c46d0c7) +- update @so1ve/prettier-config to version 3.1.0 [`d6f6403f`](https://github.com/Veloera/Veloera/commit/d6f6403f) +- indent recovery [`c152b4de`](https://github.com/Veloera/Veloera/commit/c152b4de) +- epay [`a0a3807b`](https://github.com/Veloera/Veloera/commit/a0a3807b) +- 优化自动禁用代码 [`5acf0745`](https://github.com/Veloera/Veloera/commit/5acf0745) +- 优化relay代码 [`dd12a005`](https://github.com/Veloera/Veloera/commit/dd12a005) +- 优化relay代码 [`fbe6cd75`](https://github.com/Veloera/Veloera/commit/fbe6cd75) +- 优化relay代码 [`8a9ff36f`](https://github.com/Veloera/Veloera/commit/8a9ff36f) +- remove useless code [`a6b6bcfe`](https://github.com/Veloera/Veloera/commit/a6b6bcfe) +- update token page [`07e55cc9`](https://github.com/Veloera/Veloera/commit/07e55cc9) +- gopool [`e84300f4`](https://github.com/Veloera/Veloera/commit/e84300f4) +- log format [`fae918c0`](https://github.com/Veloera/Veloera/commit/fae918c0) +- update model radio [`963985e7`](https://github.com/Veloera/Veloera/commit/963985e7) +- mj [`a3880d55`](https://github.com/Veloera/Veloera/commit/a3880d55) +- openai stream [`e262a9bd`](https://github.com/Veloera/Veloera/commit/e262a9bd) +- remove useless code [`bd9bf4b7`](https://github.com/Veloera/Veloera/commit/bd9bf4b7) +- 重构 [`d767ae04`](https://github.com/Veloera/Veloera/commit/d767ae04) +- Add Anthropic claude-3-5-sonnet-20240620 to model list [`6b07e6fb`](https://github.com/Veloera/Veloera/commit/6b07e6fb) +- 添加注释 [`d8602896`](https://github.com/Veloera/Veloera/commit/d8602896) +- 删除无用代码 [`d05a786b`](https://github.com/Veloera/Veloera/commit/d05a786b) +- 删除无用代码 [`01160658`](https://github.com/Veloera/Veloera/commit/01160658) +- update minimax url [`98a99130`](https://github.com/Veloera/Veloera/commit/98a99130) +- token counter [`a3de3091`](https://github.com/Veloera/Veloera/commit/a3de3091) +- update model list [`09512167`](https://github.com/Veloera/Veloera/commit/09512167) +- update InitTokenEncoders (#255) [`a3b3e6cc`](https://github.com/Veloera/Veloera/commit/a3b3e6cc) +- update tiktoken (#254) [`9dcec277`](https://github.com/Veloera/Veloera/commit/9dcec277) +- 删除无用代码 [`21839ed1`](https://github.com/Veloera/Veloera/commit/21839ed1) +- update model ratio [`86b17fcc`](https://github.com/Veloera/Veloera/commit/86b17fcc) +- 移除无用代码 [`408c2bdd`](https://github.com/Veloera/Veloera/commit/408c2bdd) +- 优化按次计费的数据库查询次数 [`f3f36daf`](https://github.com/Veloera/Veloera/commit/f3f36daf) +- update model ratio [`f1fb7b32`](https://github.com/Veloera/Veloera/commit/f1fb7b32) +- lint fix [`962dc984`](https://github.com/Veloera/Veloera/commit/962dc984) +- delete useless file [`951383c3`](https://github.com/Veloera/Veloera/commit/951383c3) +- delete useless dir [`87b62100`](https://github.com/Veloera/Veloera/commit/87b62100) +- Chunking Strategy [`06c86397`](https://github.com/Veloera/Veloera/commit/06c86397) +- update dependency [`2d2fec24`](https://github.com/Veloera/Veloera/commit/2d2fec24) +- reformat code [`d34b55c1`](https://github.com/Veloera/Veloera/commit/d34b55c1) +- drop idx_channels_key on start [`c3a01dec`](https://github.com/Veloera/Veloera/commit/c3a01dec) +- update README.md [`d2de6755`](https://github.com/Veloera/Veloera/commit/d2de6755) +- check before get userGroup [`ccaf64bb`](https://github.com/Veloera/Veloera/commit/ccaf64bb) +- change use_time to int [`705171e4`](https://github.com/Veloera/Veloera/commit/705171e4) +- remove unused import [`e485bc76`](https://github.com/Veloera/Veloera/commit/e485bc76) +- 数据看板限制用户只能查询跨度一个月的数据 [`e41fcd56`](https://github.com/Veloera/Veloera/commit/e41fcd56) +- UpdateMidjourneyTaskBulk with SafeGoroutine [`d30b9321`](https://github.com/Veloera/Veloera/commit/d30b9321) +- add SafeGoroutine [`febcadb4`](https://github.com/Veloera/Veloera/commit/febcadb4) +- cache username [`f07b9f8a`](https://github.com/Veloera/Veloera/commit/f07b9f8a) +- delete model price log [`fb95216b`](https://github.com/Veloera/Veloera/commit/fb95216b) +- show prompt to let the user know [`fbe9985f`](https://github.com/Veloera/Veloera/commit/fbe9985f) +- bump golang.org/x/net from 0.10.0 to 0.17.0 (#591) [`114587b4`](https://github.com/Veloera/Veloera/commit/114587b4) +- delete 360's 360GPT_S2_V9.4 [`d663de3e`](https://github.com/Veloera/Veloera/commit/d663de3e) +- delete 360's 360GPT_S2_V9.4 [`a85ecace`](https://github.com/Veloera/Veloera/commit/a85ecace) +- add database migration prompt [`cbd62011`](https://github.com/Veloera/Veloera/commit/cbd62011) +- sync database indices with pro version [`4701897e`](https://github.com/Veloera/Veloera/commit/4701897e) +- sync model schema [`53b2cace`](https://github.com/Veloera/Veloera/commit/53b2cace) +- remind user to change default password (close #516) [`f0fc991b`](https://github.com/Veloera/Veloera/commit/f0fc991b) +- add MEMORY_CACHE_ENABLED env variable [`f9b748c2`](https://github.com/Veloera/Veloera/commit/f9b748c2) +- update ali's model name [`fd984636`](https://github.com/Veloera/Veloera/commit/fd984636) +- update channel test prompt [`0d50ad4b`](https://github.com/Veloera/Veloera/commit/0d50ad4b) +- update error code [`959bcdef`](https://github.com/Veloera/Veloera/commit/959bcdef) +- add error prompt for Azure [`a721a5b6`](https://github.com/Veloera/Veloera/commit/a721a5b6) +- pass through error out [`621eb91b`](https://github.com/Veloera/Veloera/commit/621eb91b) +- update variable name [`3da12e99`](https://github.com/Veloera/Veloera/commit/3da12e99) +- use unknown as placeholder [`e42119b7`](https://github.com/Veloera/Veloera/commit/e42119b7) +- resolve conflicts [`1e16ef3e`](https://github.com/Veloera/Veloera/commit/1e16ef3e) +- update prompt [`150d068e`](https://github.com/Veloera/Veloera/commit/150d068e) +- update i18n [`c3d85a28`](https://github.com/Veloera/Veloera/commit/c3d85a28) +- update prompt [`7422b0d0`](https://github.com/Veloera/Veloera/commit/7422b0d0) +- update domain [`b464e290`](https://github.com/Veloera/Veloera/commit/b464e290) +- update title for xunfei [`b7d0616a`](https://github.com/Veloera/Veloera/commit/b7d0616a) +- update prompt for xunfei [`ce9c8024`](https://github.com/Veloera/Veloera/commit/ce9c8024) +- update i18n [`3e81d8af`](https://github.com/Veloera/Veloera/commit/3e81d8af) +- adjust ui [`b8cb86c2`](https://github.com/Veloera/Veloera/commit/b8cb86c2) +- automatically add related models when switch type [`f31d400b`](https://github.com/Veloera/Veloera/commit/f31d400b) +- send a notice to user indicating that the current channel type does not support testing [`bc2f48b1`](https://github.com/Veloera/Veloera/commit/bc2f48b1) +- do not hardcode cache time (close #302) [`4eea0966`](https://github.com/Veloera/Veloera/commit/4eea0966) +- optimize frontend (#293) [`9b4d1964`](https://github.com/Veloera/Veloera/commit/9b4d1964) +- update prompts of channel config page [`806bf824`](https://github.com/Veloera/Veloera/commit/806bf824) +- adjust channel config page [`ce93c9b6`](https://github.com/Veloera/Veloera/commit/ce93c9b6) +- add more context in error message [`c87e05bf`](https://github.com/Veloera/Veloera/commit/c87e05bf) +- add notice for baidu [`fcc1e2d5`](https://github.com/Veloera/Veloera/commit/fcc1e2d5) +- make subscription api compatible with official api [`4139a703`](https://github.com/Veloera/Veloera/commit/4139a703) +- update i18n (#262) [`2b17bb8d`](https://github.com/Veloera/Veloera/commit/2b17bb8d) +- remove email in UsersTable to make room for other fields (#246) [`3bab5b48`](https://github.com/Veloera/Veloera/commit/3bab5b48) +- update channel add & edit page [`f3bccee3`](https://github.com/Veloera/Veloera/commit/f3bccee3) +- add model parameter to the time_test script (#245) [`d84b0b0f`](https://github.com/Veloera/Veloera/commit/d84b0b0f) +- update countTokenMessages (#238) [`b09daf5e`](https://github.com/Veloera/Veloera/commit/b09daf5e) +- set the default token quota to 1$ [`c90c0ece`](https://github.com/Veloera/Veloera/commit/c90c0ece) +- ignore FRONTEND_BASE_URL on master node [`edc51566`](https://github.com/Veloera/Veloera/commit/edc51566) +- more hints in model mapping textarea (#205) [`a326ac4b`](https://github.com/Veloera/Veloera/commit/a326ac4b) +- show the HTTP status code in the test_time script to determine the success or failure of the request (#200) [`99fed1f8`](https://github.com/Veloera/Veloera/commit/99fed1f8) +- do not show completion ratio anymore [`4dc5388a`](https://github.com/Veloera/Veloera/commit/4dc5388a) +- update default ratio for text-embedding-ada-002 [`7adac1c0`](https://github.com/Veloera/Veloera/commit/7adac1c0) +- show equivalent amount next to remaining quota in the user editing page (#198) [`6f051283`](https://github.com/Veloera/Veloera/commit/6f051283) +- update the number that representing the unlimited quota [`4a6a7f46`](https://github.com/Veloera/Veloera/commit/4a6a7f46) +- billing api now will return a large number if unlimited quota is set [`94ba3dd0`](https://github.com/Veloera/Veloera/commit/94ba3dd0) +- adjust table width [`a515f928`](https://github.com/Veloera/Veloera/commit/a515f928) +- update en.json [`b0bfb9c9`](https://github.com/Veloera/Veloera/commit/b0bfb9c9) +- update completion ratio for GPT-3 [`3aff61a9`](https://github.com/Veloera/Veloera/commit/3aff61a9) +- update translations [`0fd1ff4d`](https://github.com/Veloera/Veloera/commit/0fd1ff4d) +- update translations [`e2777bf7`](https://github.com/Veloera/Veloera/commit/e2777bf7) +- fix translation [`604ff205`](https://github.com/Veloera/Veloera/commit/604ff205) +- update en.json [`2dd4ad0e`](https://github.com/Veloera/Veloera/commit/2dd4ad0e) +- use notice to show password (#107) [`d4869dfa`](https://github.com/Veloera/Veloera/commit/d4869dfa) +- able to clear all models now [`d0c454c7`](https://github.com/Veloera/Veloera/commit/d0c454c7) +- update base url setting [`fe135fd5`](https://github.com/Veloera/Veloera/commit/fe135fd5) +- use NODE_TYPE to determine node type [`b090e50f`](https://github.com/Veloera/Veloera/commit/b090e50f) +- update time_test.sh [`36e681e8`](https://github.com/Veloera/Veloera/commit/36e681e8) +- add time_test.sh [`75cd522c`](https://github.com/Veloera/Veloera/commit/75cd522c) +- update docker-compose.yml (#189) [`c893d046`](https://github.com/Veloera/Veloera/commit/c893d046) +- update one-api.service [`c6717307`](https://github.com/Veloera/Veloera/commit/c6717307) +- update docker-compose.yml [`76a39131`](https://github.com/Veloera/Veloera/commit/76a39131) +- format logs [`00151a01`](https://github.com/Veloera/Veloera/commit/00151a01) +- print more logs [`b86de464`](https://github.com/Veloera/Veloera/commit/b86de464) +- update api message [`dc7bb78c`](https://github.com/Veloera/Veloera/commit/dc7bb78c) +- update api message [`853a2880`](https://github.com/Veloera/Veloera/commit/853a2880) +- update prompt [`1b5c628e`](https://github.com/Veloera/Veloera/commit/1b5c628e) +- update description [`868d9a87`](https://github.com/Veloera/Veloera/commit/868d9a87) +- make the user unable to search token by id [`5c18c559`](https://github.com/Veloera/Veloera/commit/5c18c559) +- hide token id for user [`72ea805f`](https://github.com/Veloera/Veloera/commit/72ea805f) +- set the fallback model ratio to 30 [`24a4b323`](https://github.com/Veloera/Veloera/commit/24a4b323) +- update prompt [`8e805e23`](https://github.com/Veloera/Veloera/commit/8e805e23) +- update api endpoint for CloseAI [`eb70b846`](https://github.com/Veloera/Veloera/commit/eb70b846) +- add x-requested-with header in CORS setting [`6855d0dc`](https://github.com/Veloera/Veloera/commit/6855d0dc) +- remove -0613 suffix for Azure (#163) [`e87ad1f4`](https://github.com/Veloera/Veloera/commit/e87ad1f4) +- update gpt3.5 completion ratio [`38668e73`](https://github.com/Veloera/Veloera/commit/38668e73) +- add trailing slash for API calling [`39481eb6`](https://github.com/Veloera/Veloera/commit/39481eb6) +- record ratio detail in log [`07785341`](https://github.com/Veloera/Veloera/commit/07785341) +- update prompt [`9d0bec83`](https://github.com/Veloera/Veloera/commit/9d0bec83) +- add more log types [`d29c2730`](https://github.com/Veloera/Veloera/commit/d29c2730) +- update test logic [`9301b3fe`](https://github.com/Veloera/Veloera/commit/9301b3fe) +- bump github.com/gin-gonic/gin from 1.9.0 to 1.9.1 (#134) [`139624b8`](https://github.com/Veloera/Veloera/commit/139624b8) +- update config prompt (close #133) [`2f44aaa6`](https://github.com/Veloera/Veloera/commit/2f44aaa6) +- make channel test related code separated [`54215dc3`](https://github.com/Veloera/Veloera/commit/54215dc3) +- only check OpenAI channel & custom channel [`f9f42997`](https://github.com/Veloera/Veloera/commit/f9f42997) +- prompt user if redemption code not input [`1cc7c201`](https://github.com/Veloera/Veloera/commit/1cc7c201) +- set initial quota for root user [`a3a1b612`](https://github.com/Veloera/Veloera/commit/a3a1b612) +- set default value for Azure's api version if not set [`ef9dca28`](https://github.com/Veloera/Veloera/commit/ef9dca28) +- rewrite 429 prompt text (close #96) [`ced89398`](https://github.com/Veloera/Veloera/commit/ced89398) +- update input label [`519cb030`](https://github.com/Veloera/Veloera/commit/519cb030) +- update prompt [`d1e9b86f`](https://github.com/Veloera/Veloera/commit/d1e9b86f) +- only show two digits [`68e53d3e`](https://github.com/Veloera/Veloera/commit/68e53d3e) +- fix typo [`8835d830`](https://github.com/Veloera/Veloera/commit/8835d830) +- update prompt for Azure channel configuration (#57) [`74c1ba7c`](https://github.com/Veloera/Veloera/commit/74c1ba7c) +- update placeholder text (#36) [`4fed003f`](https://github.com/Veloera/Veloera/commit/4fed003f) +- update placeholder text (#36) [`a1ea1bf6`](https://github.com/Veloera/Veloera/commit/a1ea1bf6) +- update gitignore [`4fb07b6d`](https://github.com/Veloera/Veloera/commit/4fb07b6d) +- update Dockerfile [`8be7c9ae`](https://github.com/Veloera/Veloera/commit/8be7c9ae) +- update dependency [`b8747840`](https://github.com/Veloera/Veloera/commit/b8747840) + +### Style +- Format code using gofmt [`270a37f4`](https://github.com/Veloera/Veloera/commit/270a37f4) +- format code [`6b79b89d`](https://github.com/Veloera/Veloera/commit/6b79b89d) +- Enhance LogsTable header tags with improved styling and visual hierarchy [`3ed50787`](https://github.com/Veloera/Veloera/commit/3ed50787) +- fix UI related problems [`25eab0b2`](https://github.com/Veloera/Veloera/commit/25eab0b2) +- add positive attribute to submit buttons (close #113) [`34bce5b4`](https://github.com/Veloera/Veloera/commit/34bce5b4) +- add comma to quota stat [`2eee97e9`](https://github.com/Veloera/Veloera/commit/2eee97e9) +- hide scroll bar [`6167e20b`](https://github.com/Veloera/Veloera/commit/6167e20b) +- add bottom margin for unlimited times button [`c76027a2`](https://github.com/Veloera/Veloera/commit/c76027a2) + +### Refactor +- remove unused mutex from RelayInfo struct [`27266208`](https://github.com/Veloera/Veloera/commit/27266208) +- move maxFileSize variable inside GetFileBase64FromUrl function [`8a233207`](https://github.com/Veloera/Veloera/commit/8a233207) +- 把common/instants.go里的从Getenv获取的参数,放到init.go的LoadEnv函数里获取 把constant/env.go里的从Getenv获取的参数,放到env.go的InitEnv函数里获取。以避免.env文件配置参数不起作用的情况 [`86f6bb7a`](https://github.com/Veloera/Veloera/commit/86f6bb7a) +- Remove duplicate model settings initialization in main function [`f89d8a0f`](https://github.com/Veloera/Veloera/commit/f89d8a0f) +- Update localization keys for API address in English translations and adjust related UI labels [`3d6d1990`](https://github.com/Veloera/Veloera/commit/3d6d1990) +- systemSetting component to enhance UI structure and add new configuration options [`09adc6f2`](https://github.com/Veloera/Veloera/commit/09adc6f2) +- use handleFieldChange function on change event [`af7f886c`](https://github.com/Veloera/Veloera/commit/af7f886c) +- Improve token quota consumption logic [`dd82618c`](https://github.com/Veloera/Veloera/commit/dd82618c) +- Update ClaudeResponse error handling to use pointer for ClaudeError and improve nil checks in response processing [`8c7c3955`](https://github.com/Veloera/Veloera/commit/8c7c3955) +- Enhance Claude response handling [`53b35998`](https://github.com/Veloera/Veloera/commit/53b35998) +- Enhance error handling in AWS and Claude response processing by updating function signatures and improving error propagation [`ee302c06`](https://github.com/Veloera/Veloera/commit/ee302c06) +- Streamline AWS and Claude response handling by consolidating logic and improving error management [`2c81a5f0`](https://github.com/Veloera/Veloera/commit/2c81a5f0) +- Replace direct access to ImageUrl with GetImageMedia method across multiple relay channels [`c183c123`](https://github.com/Veloera/Veloera/commit/c183c123) +- Change ClaudeError field type to non-pointer and enhance response handling with reasoning content [`19bfa158`](https://github.com/Veloera/Veloera/commit/19bfa158) +- Simplify OpenAI handler function signature and remove unused TextResponseWithError struct; introduce common_handler for rerank functionality [`69e44a03`](https://github.com/Veloera/Veloera/commit/69e44a03) +- Update token usage calculation in FormatClaudeResponseInfo #865 [`1644dbc8`](https://github.com/Veloera/Veloera/commit/1644dbc8) +- Update OIDC status check to use oidc_enabled flag [`0033f5ba`](https://github.com/Veloera/Veloera/commit/0033f5ba) +- Remove OIDC configuration from option initialization [`e52ac52e`](https://github.com/Veloera/Veloera/commit/e52ac52e) +- Migrate OIDC configuration to system settings [`66682584`](https://github.com/Veloera/Veloera/commit/66682584) +- Improve responsive design across multiple setting pages [`94549f96`](https://github.com/Veloera/Veloera/commit/94549f96) +- Remove unnecessary transition styles and simplify sidebar state management [`627f95b0`](https://github.com/Veloera/Veloera/commit/627f95b0) +- Improve sidebar state management and layout responsiveness [`8b99eec4`](https://github.com/Veloera/Veloera/commit/8b99eec4) +- Improve mobile responsiveness and scrolling behavior in UI layout [`b2938ffe`](https://github.com/Veloera/Veloera/commit/b2938ffe) +- Enhance UI layout and styling with responsive design improvements [`d9cf0885`](https://github.com/Veloera/Veloera/commit/d9cf0885) +- Make Channel Setting nullable and improve setting handling #836 [`97d948cd`](https://github.com/Veloera/Veloera/commit/97d948cd) +- Improve price rendering with clearer token and price calculations [`db01994c`](https://github.com/Veloera/Veloera/commit/db01994c) +- Update topup amount type from int to int64 for improved precision [`5a10ebd3`](https://github.com/Veloera/Veloera/commit/5a10ebd3) +- Simplify chat menu items rendering in SiderBar [`867187ab`](https://github.com/Veloera/Veloera/commit/867187ab) +- Improve quota calculation precision using floating-point arithmetic [`bb848b2f`](https://github.com/Veloera/Veloera/commit/bb848b2f) +- Remove redundant user quota retrieval in audio relay [`81137e05`](https://github.com/Veloera/Veloera/commit/81137e05) +- Reorganize sidebar navigation and add personal settings route [`fd22948e`](https://github.com/Veloera/Veloera/commit/fd22948e) +- Improve model request rate limit middleware execution [`b841ce00`](https://github.com/Veloera/Veloera/commit/b841ce00) +- Centralize stream handling and helper functions in relay package [`37a83ecc`](https://github.com/Veloera/Veloera/commit/37a83ecc) +- Extract operation-related settings into a separate package [`98b27a17`](https://github.com/Veloera/Veloera/commit/98b27a17) +- Add index to Username column in Log model [`7208a65e`](https://github.com/Veloera/Veloera/commit/7208a65e) +- Update rate limit configuration to use dynamic expiration duration [`4084b180`](https://github.com/Veloera/Veloera/commit/4084b180) +- Improve channel testing and model price handling [`d042a1bd`](https://github.com/Veloera/Veloera/commit/d042a1bd) +- Reorganize Claude MaxTokens configuration UI layout [`ae5b874a`](https://github.com/Veloera/Veloera/commit/ae5b874a) +- Enhance user context and quota management [`069f2672`](https://github.com/Veloera/Veloera/commit/069f2672) +- Improve message content parsing with robust type handling [`cc5066c5`](https://github.com/Veloera/Veloera/commit/cc5066c5) +- Improve message content handling and quota error responses [`b9b69b01`](https://github.com/Veloera/Veloera/commit/b9b69b01) +- Optimize sensitive word detection and text processing [`1f4f9123`](https://github.com/Veloera/Veloera/commit/1f4f9123) +- Improve quota error messages with formatted quota display [`2d42145b`](https://github.com/Veloera/Veloera/commit/2d42145b) +- Simplify model mapping and pricing logic across relay modules [`06da65a9`](https://github.com/Veloera/Veloera/commit/06da65a9) +- Replace manual goroutine creation with gopool.Go [`5937d850`](https://github.com/Veloera/Veloera/commit/5937d850) +- Optimize user caching and token retrieval methods [`b1847509`](https://github.com/Veloera/Veloera/commit/b1847509) +- Simplify root user notification and remove global email variable [`0907a078`](https://github.com/Veloera/Veloera/commit/0907a078) +- Improve CompletionRatio handling with thread-safe access and initialization [`9d9c461c`](https://github.com/Veloera/Veloera/commit/9d9c461c) +- Optimize channel testing and model menu generation (fix #761) [`3239c605`](https://github.com/Veloera/Veloera/commit/3239c605) +- Improve channel property update mechanism (fix #761) [`e6f4587f`](https://github.com/Veloera/Veloera/commit/e6f4587f) +- Optimize log retrieval with separate channel name fetching (fix #751) [`bc62d1bb`](https://github.com/Veloera/Veloera/commit/bc62d1bb) +- Optimize Dockerfile for Go build process [`81591f20`](https://github.com/Veloera/Veloera/commit/81591f20) +- improve SSE response handling in Playground [`39aacf5f`](https://github.com/Veloera/Veloera/commit/39aacf5f) +- update log queries to explicitly reference 'logs' table for clarity and consistency [`1c5740d5`](https://github.com/Veloera/Veloera/commit/1c5740d5) +- access_token auth [`006bc372`](https://github.com/Veloera/Veloera/commit/006bc372) +- realtime log render [`f1e3cd6f`](https://github.com/Veloera/Veloera/commit/f1e3cd6f) +- realtime i18n [`f417a109`](https://github.com/Veloera/Veloera/commit/f417a109) +- realtime quota [`99245e4c`](https://github.com/Veloera/Veloera/commit/99245e4c) +- update group handling and rendering logic [`d13d81ba`](https://github.com/Veloera/Veloera/commit/d13d81ba) +- token cache logic [`bb5e032d`](https://github.com/Veloera/Veloera/commit/bb5e032d) +- remove redundant group column handling in user queries [`ca8b7ed1`](https://github.com/Veloera/Veloera/commit/ca8b7ed1) +- user cache logic [`ed435e5c`](https://github.com/Veloera/Veloera/commit/ed435e5c) +- Playground controller [`c8614f98`](https://github.com/Veloera/Veloera/commit/c8614f98) +- streamline log processing by introducing formatUserLogs function [`10d896aa`](https://github.com/Veloera/Veloera/commit/10d896aa) +- enhance log retrieval and user interaction in LogsTable component [`118eb362`](https://github.com/Veloera/Veloera/commit/118eb362) +- improve user group handling and add GetUserUsableGroups function [`77861e64`](https://github.com/Veloera/Veloera/commit/77861e64) +- migrate group ratio and user usable groups logic to new setting package [`4fc1fe31`](https://github.com/Veloera/Veloera/commit/4fc1fe31) +- Remove unused context and logging in CovertGemini2OpenAI function [`241c9389`](https://github.com/Veloera/Veloera/commit/241c9389) +- Update Message methods to use pointer receivers [`c4e256e6`](https://github.com/Veloera/Veloera/commit/c4e256e6) +- Update SetToolCalls method to use pointer receiver [`87a5e40d`](https://github.com/Veloera/Veloera/commit/87a5e40d) +- Update OpenAI request and message handling [`0c326556`](https://github.com/Veloera/Veloera/commit/0c326556) +- Simplify Gemini function parameter handling [`656e8092`](https://github.com/Veloera/Veloera/commit/656e8092) +- Enhance error handling in Gemini request conversion [`03256dbd`](https://github.com/Veloera/Veloera/commit/03256dbd) +- Improve channel status update logic and clean up code [`2d865eb7`](https://github.com/Veloera/Veloera/commit/2d865eb7) +- Update SystemInstructions type in GeminiChatRequest and adjust handling in CovertGemini2OpenAI [`41637137`](https://github.com/Veloera/Veloera/commit/41637137) +- Remove unused translation function calls in LogsTable component [`68b7e0e9`](https://github.com/Veloera/Veloera/commit/68b7e0e9) +- Simplify average calculations in Detail component [`263547eb`](https://github.com/Veloera/Veloera/commit/263547eb) +- Simplify PersonalSetting component layout [`77b8d918`](https://github.com/Veloera/Veloera/commit/77b8d918) +- improve validation logic and error handling in relay-text.go [`a9f739a7`](https://github.com/Veloera/Veloera/commit/a9f739a7) +- realtime log [`d5966992`](https://github.com/Veloera/Veloera/commit/d5966992) +- 调整配置文件,优化注释和变量命名 [`2e18d5f9`](https://github.com/Veloera/Veloera/commit/2e18d5f9) +- image relay [`b0d5491a`](https://github.com/Veloera/Veloera/commit/b0d5491a) +- audio relay [`bcc7f3ed`](https://github.com/Veloera/Veloera/commit/bcc7f3ed) +- 重构流模式逻辑 [`70290658`](https://github.com/Veloera/Veloera/commit/70290658) +- 移除已废弃模型 [`7e262382`](https://github.com/Veloera/Veloera/commit/7e262382) +- 重构敏感词 [`bfbbe67f`](https://github.com/Veloera/Veloera/commit/bfbbe67f) +- 运营设置-数据刷新 [`98c347e0`](https://github.com/Veloera/Veloera/commit/98c347e0) +- 运营设置-倍率设置 [`b283365e`](https://github.com/Veloera/Veloera/commit/b283365e) +- 运营设置-额度设置 [`968ef1e5`](https://github.com/Veloera/Veloera/commit/968ef1e5) +- 运营设置-监控设置 [`88bc2958`](https://github.com/Veloera/Veloera/commit/88bc2958) +- 运营设置-数据看板设置 [`76f6b41b`](https://github.com/Veloera/Veloera/commit/76f6b41b) +- 运营设置-日志设置 [`003745ab`](https://github.com/Veloera/Veloera/commit/003745ab) +- 运营设置-屏蔽词过滤设置 [`96468ce6`](https://github.com/Veloera/Veloera/commit/96468ce6) +- 运营设置-绘图设置 [`9886cdd5`](https://github.com/Veloera/Veloera/commit/9886cdd5) +- 运营设置-通用设置 [`83dd6298`](https://github.com/Veloera/Veloera/commit/83dd6298) +- 重构计费代码 [`783e8fd7`](https://github.com/Veloera/Veloera/commit/783e8fd7) +- dark mode [`e9abe5b7`](https://github.com/Veloera/Veloera/commit/e9abe5b7) +- renderGroup function [`3e90b6d5`](https://github.com/Veloera/Veloera/commit/3e90b6d5) +- 代码结构优化 [`03195771`](https://github.com/Veloera/Veloera/commit/03195771) +- 修改超时时间 [`84e05446`](https://github.com/Veloera/Veloera/commit/84e05446) +- remove consumeQuota [`9b2e5c29`](https://github.com/Veloera/Veloera/commit/9b2e5c29) +- change UI, improve interaction [`6413bf34`](https://github.com/Veloera/Veloera/commit/6413bf34) +- update logging related logic [`42451d9d`](https://github.com/Veloera/Veloera/commit/42451d9d) +- add set event stream headers func (#402) [`ca512f6a`](https://github.com/Veloera/Veloera/commit/ca512f6a) +- update billing related code [`fe8f216d`](https://github.com/Veloera/Veloera/commit/fe8f216d) +- refactor openai related code [`12a0e710`](https://github.com/Veloera/Veloera/commit/12a0e710) +- refactor claude related code [`e628b643`](https://github.com/Veloera/Veloera/commit/e628b643) +- refactor claude related code [`675847bf`](https://github.com/Veloera/Veloera/commit/675847bf) +- do not use redis to store session [`431d505f`](https://github.com/Veloera/Veloera/commit/431d505f) +- rename function [`33846ce4`](https://github.com/Veloera/Veloera/commit/33846ce4) +- split the relay controller [`f6fe3467`](https://github.com/Veloera/Veloera/commit/f6fe3467) +- make operation settings separated from system settings [`75545a1f`](https://github.com/Veloera/Veloera/commit/75545a1f) +- enable model configuration on default group (close #143) [`813bf0bd`](https://github.com/Veloera/Veloera/commit/813bf0bd) +- bind quota to account instead of token (close #64, #31) [`01abed0a`](https://github.com/Veloera/Veloera/commit/01abed0a) +- rename file to conform to standards [`5798fdac`](https://github.com/Veloera/Veloera/commit/5798fdac) +- use built in smtp library (close #34) [`3710688e`](https://github.com/Veloera/Veloera/commit/3710688e) +- use tiktoken-go to calculate token number [`b08cd7e1`](https://github.com/Veloera/Veloera/commit/b08cd7e1) +- improve relay's implementation [`23ec541b`](https://github.com/Veloera/Veloera/commit/23ec541b) +- use quota instead of times [`601fa5ce`](https://github.com/Veloera/Veloera/commit/601fa5ce) + +### Other +- format: Format code use gofmt [`70915028`](https://github.com/Veloera/Veloera/commit/70915028) +- Fix workflow badge path in README [`123fc658`](https://github.com/Veloera/Veloera/commit/123fc658) +- Simplify CI [`e030c702`](https://github.com/Veloera/Veloera/commit/e030c702) +- format: Format code [`125396b2`](https://github.com/Veloera/Veloera/commit/125396b2) +- revert: 9872cd86 [`7a0604f4`](https://github.com/Veloera/Veloera/commit/7a0604f4) +- revert: Revert changes in commit f258225 [`cf60bb1d`](https://github.com/Veloera/Veloera/commit/cf60bb1d) +- feature: Allow user to use multiple api keys in one channel. [`0be85ebf`](https://github.com/Veloera/Veloera/commit/0be85ebf) +- feature: Add gift code [`e6b906b8`](https://github.com/Veloera/Veloera/commit/e6b906b8) +- 🐛fix: Fix the issue where new whitelist email domain names cannot be added in the system settings [`995c19a9`](https://github.com/Veloera/Veloera/commit/995c19a9) +- Merge pull request #927 from QuentinHsu/refactor-system-setting [`71d0d759`](https://github.com/Veloera/Veloera/commit/71d0d759) +- Merge pull request #967 from neotf/fix-01 [`d283f6b3`](https://github.com/Veloera/Veloera/commit/d283f6b3) +- Merge pull request #959 from Praying/main [`909c5eb2`](https://github.com/Veloera/Veloera/commit/909c5eb2) +- Merge pull request #953 from wkxu/main [`8efa12b9`](https://github.com/Veloera/Veloera/commit/8efa12b9) +- Merge pull request #956 from HynoR/feat/xai [`7b997b3a`](https://github.com/Veloera/Veloera/commit/7b997b3a) +- Merge pull request #944 from lamcodes/main [`c4c1099a`](https://github.com/Veloera/Veloera/commit/c4c1099a) +- Update: Gemini channel fetch_models [`524d4a65`](https://github.com/Veloera/Veloera/commit/524d4a65) +- Merge pull request #930 from Yiffyi/main [`67cbbc22`](https://github.com/Veloera/Veloera/commit/67cbbc22) +- Update MaxTokens for gemini model to 300 in test request [`79b35e38`](https://github.com/Veloera/Veloera/commit/79b35e38) +- Merge pull request #936 from lamcodes/main [`03e8ab41`](https://github.com/Veloera/Veloera/commit/03e8ab41) +- Set MaxTokens to 50 for gemini [`30f32c6a`](https://github.com/Veloera/Veloera/commit/30f32c6a) +- Update model-ratio.go [`f6e88874`](https://github.com/Veloera/Veloera/commit/f6e88874) +- Update model-ratio.go [`a29f4d88`](https://github.com/Veloera/Veloera/commit/a29f4d88) +- Update model-ratio.go [`424424c1`](https://github.com/Veloera/Veloera/commit/424424c1) +- Merge remote-tracking branch 'origin/main' [`42a2418d`](https://github.com/Veloera/Veloera/commit/42a2418d) +- Update README.md [`5cb317bd`](https://github.com/Veloera/Veloera/commit/5cb317bd) +- Merge pull request #925 from Calcium-Ion/setup [`37dd1ef0`](https://github.com/Veloera/Veloera/commit/37dd1ef0) +- Merge remote-tracking branch 'origin/main' [`552e2850`](https://github.com/Veloera/Veloera/commit/552e2850) +- Merge pull request #909 from jasinliu/feature/fix-dify-thinking [`1dc2284d`](https://github.com/Veloera/Veloera/commit/1dc2284d) +- Merge pull request #893 from wizcas/replace-linux-do-icon [`f4cc90c8`](https://github.com/Veloera/Veloera/commit/f4cc90c8) +- Merge pull request #895 from Feiyuyu0503/main [`140d3a97`](https://github.com/Veloera/Veloera/commit/140d3a97) +- Merge pull request #912 from OrdinarySF/main [`2ecb742e`](https://github.com/Veloera/Veloera/commit/2ecb742e) +- Merge pull request #914 from JoeyLearnsToCode/main [`9066cfa8`](https://github.com/Veloera/Veloera/commit/9066cfa8) +- Merge pull request #916 from xifan2333/fix/systemSettingsUI [`4f437f30`](https://github.com/Veloera/Veloera/commit/4f437f30) +- fix dify thinking [`dc132655`](https://github.com/Veloera/Veloera/commit/dc132655) +- update model ratio [`3516aad3`](https://github.com/Veloera/Veloera/commit/3516aad3) +- update model ratio [`1df39e5a`](https://github.com/Veloera/Veloera/commit/1df39e5a) +- replace the linuxdo icon in the login form [`a9522075`](https://github.com/Veloera/Veloera/commit/a9522075) +- Merge pull request #886 from seefs001/main [`983d31bf`](https://github.com/Veloera/Veloera/commit/983d31bf) +- Merge pull request #872 from neotf/main [`b5aa3c12`](https://github.com/Veloera/Veloera/commit/b5aa3c12) +- Update README [`962e803d`](https://github.com/Veloera/Veloera/commit/962e803d) +- Update README [`ff57ced2`](https://github.com/Veloera/Veloera/commit/ff57ced2) +- Update README [`2223806c`](https://github.com/Veloera/Veloera/commit/2223806c) +- Merge remote-tracking branch 'origin/main' [`a4a40c49`](https://github.com/Veloera/Veloera/commit/a4a40c49) +- Merge pull request #851 from HynoR/main [`5a67bdf1`](https://github.com/Veloera/Veloera/commit/5a67bdf1) +- Merge pull request #874 from HynoR/feat/gemini2 [`b84b6aff`](https://github.com/Veloera/Veloera/commit/b84b6aff) +- Sync Cohere Latest Model [`e9884904`](https://github.com/Veloera/Veloera/commit/e9884904) +- Merge branch 'main' into main [`8e68bcce`](https://github.com/Veloera/Veloera/commit/8e68bcce) +- Merge pull request #867 from Sh1n3zZ/wrong-think-label-fix [`a3811634`](https://github.com/Veloera/Veloera/commit/a3811634) +- Merge pull request #857 from asjfoajs/main [`e5b6aa6e`](https://github.com/Veloera/Veloera/commit/e5b6aa6e) +- Refactor: Optimize the ImageHandler under the Alibaba large model to retrieve the key from the header. Reason: The info parameter already includes the key, so there is no need to retrieve it again from the header. Solution: Delete the code for obtaining the key and directly use info.ApiKey. [`23596d22`](https://github.com/Veloera/Veloera/commit/23596d22) +- Update README.md [`c25d4d8d`](https://github.com/Veloera/Veloera/commit/c25d4d8d) +- Update README.md [`b291fbff`](https://github.com/Veloera/Veloera/commit/b291fbff) +- Update README.md [`e68edf81`](https://github.com/Veloera/Veloera/commit/e68edf81) +- Merge pull request #854 from seefs001/main [`5ff16f9b`](https://github.com/Veloera/Veloera/commit/5ff16f9b) +- Merge pull request #855 from Calcium-Ion/claude [`f614cfa5`](https://github.com/Veloera/Veloera/commit/f614cfa5) +- fix panic [`2048b451`](https://github.com/Veloera/Veloera/commit/2048b451) +- Merge remote-tracking branch 'origin/main' [`5059cbdb`](https://github.com/Veloera/Veloera/commit/5059cbdb) +- Fix Deepseek Cache Ratio [`f7852ada`](https://github.com/Veloera/Veloera/commit/f7852ada) +- Merge pull request #848 from wzxjohn/feature/oidc [`495bbcb6`](https://github.com/Veloera/Veloera/commit/495bbcb6) +- Merge remote-tracking branch 'origin/main' [`8de29fbb`](https://github.com/Veloera/Veloera/commit/8de29fbb) +- Merge pull request #849 from OrdinarySF/main [`f2163acf`](https://github.com/Veloera/Veloera/commit/f2163acf) +- Merge remote-tracking branch 'origin/main' [`bbe7223a`](https://github.com/Veloera/Veloera/commit/bbe7223a) +- Merge pull request #845 from Sh1n3zZ/gemini-embedding [`ecb5b563`](https://github.com/Veloera/Veloera/commit/ecb5b563) +- Merge remote-tracking branch 'origin/main' [`69db1f14`](https://github.com/Veloera/Veloera/commit/69db1f14) +- Merge pull request #842 from asjfoajs/dev [`c7e1bab1`](https://github.com/Veloera/Veloera/commit/c7e1bab1) +- Fix: Under Ali's large model, the task ID result for image retrieval is incorrect. Reason: The URL is incomplete, missing baseurl. Solution: Add baseurl. url := fmt.Sprintf("%s/api/v1/tasks/%s", info.BaseUrl, taskID). [`434e9d76`](https://github.com/Veloera/Veloera/commit/434e9d76) +- Merge pull request #830 from Calcium-Ion/decimal [`a0ca3eff`](https://github.com/Veloera/Veloera/commit/a0ca3eff) +- Merge pull request #828 from Calcium-Ion/ui [`3352bacd`](https://github.com/Veloera/Veloera/commit/3352bacd) +- Merge pull request #826 from Calcium-Ion/cache [`d9390ff4`](https://github.com/Veloera/Veloera/commit/d9390ff4) +- Merge pull request #821 from Calcium-Ion/cache [`618908f6`](https://github.com/Veloera/Veloera/commit/618908f6) +- Merge pull request #820 from Calcium-Ion/cache [`7c03ad71`](https://github.com/Veloera/Veloera/commit/7c03ad71) +- Merge pull request #815 from Sh1n3zZ/openrouter-adapter [`b9b66dda`](https://github.com/Veloera/Veloera/commit/b9b66dda) +- Update README.md [`37bb34b4`](https://github.com/Veloera/Veloera/commit/37bb34b4) +- Update README.md [`7855f83e`](https://github.com/Veloera/Veloera/commit/7855f83e) +- Merge remote-tracking branch 'origin/main' [`a42c3b62`](https://github.com/Veloera/Veloera/commit/a42c3b62) +- Merge pull request #805 from PaperPlaneDeemo/main [`be228ccd`](https://github.com/Veloera/Veloera/commit/be228ccd) +- Merge branch 'Calcium-Ion:main' into main [`14848ff7`](https://github.com/Veloera/Veloera/commit/14848ff7) +- fix [`15295049`](https://github.com/Veloera/Veloera/commit/15295049) +- init openrouter adaptor [`19a318c9`](https://github.com/Veloera/Veloera/commit/19a318c9) +- Merge pull request #788 from MartialBE/main [`5b9e2756`](https://github.com/Veloera/Veloera/commit/5b9e2756) +- Merge branch 'main' into thinking [`607e3206`](https://github.com/Veloera/Veloera/commit/607e3206) +- Merge pull request #781 from zeyugao/main [`dc36fded`](https://github.com/Veloera/Veloera/commit/dc36fded) +- Merge pull request #783 from Calcium-Ion/rate-limit [`3017882f`](https://github.com/Veloera/Veloera/commit/3017882f) +- Pass extra_body to the backend [`af00f7b3`](https://github.com/Veloera/Veloera/commit/af00f7b3) +- fix typo [`6c7a8c81`](https://github.com/Veloera/Veloera/commit/6c7a8c81) +- Merge pull request #778 from utopeadia/main [`4b6101b3`](https://github.com/Veloera/Veloera/commit/4b6101b3) +- 进一步美化刷新图标 [`c541d6c9`](https://github.com/Veloera/Veloera/commit/c541d6c9) +- 优化日志刷新图标显示 [`7dfcd135`](https://github.com/Veloera/Veloera/commit/7dfcd135) +- Merge pull request #775 from Calcium-Ion/model_mappping [`8dd4ce98`](https://github.com/Veloera/Veloera/commit/8dd4ce98) +- Merge pull request #773 from wellcoming/patch-1 [`6e0046f7`](https://github.com/Veloera/Veloera/commit/6e0046f7) +- Merge pull request #770 from Calcium-Ion/refactor_notify [`2b743550`](https://github.com/Veloera/Veloera/commit/2b743550) +- Merge pull request #768 from lgphone/main [`63f34123`](https://github.com/Veloera/Veloera/commit/63f34123) +- Update .env.example [`a13bea5f`](https://github.com/Veloera/Veloera/commit/a13bea5f) +- Merge pull request #763 from Sh1n3zZ/support-imagen-3.0-generate-002 [`2e3b920a`](https://github.com/Veloera/Veloera/commit/2e3b920a) +- Merge remote-tracking branch 'origin/main' [`995b3a24`](https://github.com/Veloera/Veloera/commit/995b3a24) +- Merge pull request #735 from jyc001/main [`78f19d46`](https://github.com/Veloera/Veloera/commit/78f19d46) +- Merge pull request #759 from nightcoffee/patch-1 [`814be845`](https://github.com/Veloera/Veloera/commit/814be845) +- Merge pull request #714 from NitroRCr/main [`81d11e5d`](https://github.com/Veloera/Veloera/commit/81d11e5d) +- Merge pull request #723 from kuwork/main [`88bdedd2`](https://github.com/Veloera/Veloera/commit/88bdedd2) +- update CI [`7fce084a`](https://github.com/Veloera/Veloera/commit/7fce084a) +- Merge pull request #746 from zjjxwhh/main [`0e89939a`](https://github.com/Veloera/Veloera/commit/0e89939a) +- Merge remote-tracking branch 'origin/main' [`2c2d1da2`](https://github.com/Veloera/Veloera/commit/2c2d1da2) +- Merge pull request #736 from xy3xy3/main [`ec50f665`](https://github.com/Veloera/Veloera/commit/ec50f665) +- Merge pull request #742 from HynoR/chore/ds [`1a09b1ae`](https://github.com/Veloera/Veloera/commit/1a09b1ae) +- 更正硅基流动的SenseVoiceSmall模型名字 [`8910efb1`](https://github.com/Veloera/Veloera/commit/8910efb1) +- Merge pull request #2 from jyc001/dev [`206dbfa4`](https://github.com/Veloera/Veloera/commit/206dbfa4) +- Merge pull request #1 from jyc001/dev [`68bd7f70`](https://github.com/Veloera/Veloera/commit/68bd7f70) +- feat add FIM support for siliconflow [`ce426995`](https://github.com/Veloera/Veloera/commit/ce426995) +- Merge pull request #727 from HynoR/feat/autogemini [`562c6633`](https://github.com/Veloera/Veloera/commit/562c6633) +- Merge branch 'main' into main [`89d48a66`](https://github.com/Veloera/Veloera/commit/89d48a66) +- f*** o3-mini [`b80c1ee3`](https://github.com/Veloera/Veloera/commit/b80c1ee3) +- Merge remote-tracking branch 'origin/main' [`030ffd5f`](https://github.com/Veloera/Veloera/commit/030ffd5f) +- Merge pull request #694 from yinuan-i/main [`fec448eb`](https://github.com/Veloera/Veloera/commit/fec448eb) +- Merge pull request #715 from seefs001/main [`dd593e1a`](https://github.com/Veloera/Veloera/commit/dd593e1a) +- Merge remote-tracking branch 'origin/main' [`ced9c6e5`](https://github.com/Veloera/Veloera/commit/ced9c6e5) +- Merge branch 'Calcium-Ion:main' into main [`344d8046`](https://github.com/Veloera/Veloera/commit/344d8046) +- Merge pull request #713 from seefs001/main [`5a4319b9`](https://github.com/Veloera/Veloera/commit/5a4319b9) +- Fix M3E not working [`7588c42b`](https://github.com/Veloera/Veloera/commit/7588c42b) +- Merge pull request #710 from hubutui/main [`6cc9c36a`](https://github.com/Veloera/Veloera/commit/6cc9c36a) +- fix : chanel test did not refresh [`8a2d220c`](https://github.com/Veloera/Veloera/commit/8a2d220c) +- Merge branch 'Calcium-Ion:main' into main [`0b3a0064`](https://github.com/Veloera/Veloera/commit/0b3a0064) +- Support for MokaAI M3E [`126f04e0`](https://github.com/Veloera/Veloera/commit/126f04e0) +- CI: update workflows [`53a941a6`](https://github.com/Veloera/Veloera/commit/53a941a6) +- Fix temperature not being set to 0 due to json omitempty [`eda7ef50`](https://github.com/Veloera/Veloera/commit/eda7ef50) +- Merge pull request #705 from maranello-o/main [`7f8112a3`](https://github.com/Veloera/Veloera/commit/7f8112a3) +- Merge pull request #699 from detecti1/feat/show-log-with-channel-name [`055e77e4`](https://github.com/Veloera/Veloera/commit/055e77e4) +- Merge pull request #709 from HynoR/feat/update-ratio [`a2872dec`](https://github.com/Veloera/Veloera/commit/a2872dec) +- Merge branch 'Calcium-Ion:main' into main [`ad051fc2`](https://github.com/Veloera/Veloera/commit/ad051fc2) +- Fix JSON parsing error when record.other is empty string [`f7277933`](https://github.com/Veloera/Veloera/commit/f7277933) +- Add channel name (tooltip / detail) to logs [`94fa2810`](https://github.com/Veloera/Veloera/commit/94fa2810) +- Update IP restriction messages for clarity in English localization and placeholder text in EditToken component. Enhanced user guidance by specifying that leaving the IP field blank means no restrictions. [`5387d7f4`](https://github.com/Veloera/Veloera/commit/5387d7f4) +- Merge pull request #693 from Calcium-Ion/refactor-auth [`b85a07e5`](https://github.com/Veloera/Veloera/commit/b85a07e5) +- Adjust streaming timeout for OpenAI models in OaiStreamHandler [`8518ca65`](https://github.com/Veloera/Veloera/commit/8518ca65) +- Update Dockerfile to use Bun for package management and build process [`cd192e27`](https://github.com/Veloera/Veloera/commit/cd192e27) +- Enhance user search functionality to support ID and keyword searches. Updated query conditions to allow searching by user ID alongside username, email, and display name. Improved handling of numeric and string keywords in search queries. [`80fcd4e9`](https://github.com/Veloera/Veloera/commit/80fcd4e9) +- Merge pull request #692 from Calcium-Ion/fix-channel-model-length [`3f8c12c1`](https://github.com/Veloera/Veloera/commit/3f8c12c1) +- revert cache.go [`08a89a50`](https://github.com/Veloera/Veloera/commit/08a89a50) +- Fix model name length validation limit [`4cf9d078`](https://github.com/Veloera/Veloera/commit/4cf9d078) +- 4fa7fefe [`4fa7fefe`](https://github.com/Veloera/Veloera/commit/4fa7fefe) +- Fix channel model length issue [`239bc469`](https://github.com/Veloera/Veloera/commit/239bc469) +- Merge pull request #689 from iszcz/new512 [`b5de003e`](https://github.com/Veloera/Veloera/commit/b5de003e) +- Update model-ratio.go [`8ede1bf1`](https://github.com/Veloera/Veloera/commit/8ede1bf1) +- Merge pull request #686 from delph1s/main [`ba1aad8a`](https://github.com/Veloera/Veloera/commit/ba1aad8a) +- Update README.md [`bf9a492f`](https://github.com/Veloera/Veloera/commit/bf9a492f) +- Merge pull request #683 from iszcz/new512 [`16725d12`](https://github.com/Veloera/Veloera/commit/16725d12) +- Update channel-test.go [`687f07bc`](https://github.com/Veloera/Veloera/commit/687f07bc) +- fix redis [`b4f17543`](https://github.com/Veloera/Veloera/commit/b4f17543) +- Merge pull request #679 from kingxjs/main [`65af1a4d`](https://github.com/Veloera/Veloera/commit/65af1a4d) +- Merge pull request #677 from mageia/master [`1ae0a3fb`](https://github.com/Veloera/Veloera/commit/1ae0a3fb) +- Merge branch 'main' [`fe2e8f1a`](https://github.com/Veloera/Veloera/commit/fe2e8f1a) +- Merge pull request #680 from Calcium-Ion/refactor_redis [`a5f7f8af`](https://github.com/Veloera/Veloera/commit/a5f7f8af) +- 使用原生document构建input再次尝试复制命令 [`e4f9787c`](https://github.com/Veloera/Veloera/commit/e4f9787c) +- 修复 PostgreSQL 中用户组查询错误 [`304c92ce`](https://github.com/Veloera/Veloera/commit/304c92ce) +- Merge pull request #676 from Calcium-Ion/refactor_redis [`05874dcc`](https://github.com/Veloera/Veloera/commit/05874dcc) +- Merge pull request #674 from Yan-Zero/main [`a1b864bc`](https://github.com/Veloera/Veloera/commit/a1b864bc) +- Merge pull request #673 from Yan-Zero/main [`91b777f3`](https://github.com/Veloera/Veloera/commit/91b777f3) +- Merge pull request #672 from Yan-Zero/main [`458dd1bd`](https://github.com/Veloera/Veloera/commit/458dd1bd) +- fix #663 [`52c023a1`](https://github.com/Veloera/Veloera/commit/52c023a1) +- update dockerignore [`5f082d72`](https://github.com/Veloera/Veloera/commit/5f082d72) +- update model ratio [`0b1354ed`](https://github.com/Veloera/Veloera/commit/0b1354ed) +- Merge pull request #661 from tenacioustommy/fix-title-schema [`132c7139`](https://github.com/Veloera/Veloera/commit/132c7139) +- Merge pull request #662 from xqx333/main [`bb3deb7b`](https://github.com/Veloera/Veloera/commit/bb3deb7b) +- Update relay-text.go [`c86762b6`](https://github.com/Veloera/Veloera/commit/c86762b6) +- fix delete title schema [`3409d7a6`](https://github.com/Veloera/Veloera/commit/3409d7a6) +- fix typo [`b3576f24`](https://github.com/Veloera/Veloera/commit/b3576f24) +- Merge pull request #656 from Yan-Zero/main [`aaf5cece`](https://github.com/Veloera/Veloera/commit/aaf5cece) +- Merge branch 'Calcium-Ion:main' into main [`a8a2195a`](https://github.com/Veloera/Veloera/commit/a8a2195a) +- Merge remote-tracking branch 'origin/main' [`be0c240e`](https://github.com/Veloera/Veloera/commit/be0c240e) +- Merge pull request #652 from Yan-Zero/main [`61495a46`](https://github.com/Veloera/Veloera/commit/61495a46) +- Merge remote-tracking branch 'origin/main' [`cf3287a1`](https://github.com/Veloera/Veloera/commit/cf3287a1) +- Merge pull request #651 from tenacioustommy/fix-gemini-json [`eec8f523`](https://github.com/Veloera/Veloera/commit/eec8f523) +- fix-gemini-json [`cce2990d`](https://github.com/Veloera/Veloera/commit/cce2990d) +- Merge pull request #648 from palboss/main [`794f6a6e`](https://github.com/Veloera/Veloera/commit/794f6a6e) +- Update user.go [`a02bc334`](https://github.com/Veloera/Veloera/commit/a02bc334) +- Update user.go [`f54d0cb3`](https://github.com/Veloera/Veloera/commit/f54d0cb3) +- Merge branch 'feat/o1' [`865b98a4`](https://github.com/Veloera/Veloera/commit/865b98a4) +- Merge pull request #645 from MartialBE/gemini_res_format [`5bdbf3a6`](https://github.com/Veloera/Veloera/commit/5bdbf3a6) +- Merge remote-tracking branch 'origin/feat/o1' into feat/o1 [`eac34634`](https://github.com/Veloera/Veloera/commit/eac34634) +- Merge branch 'Calcium-Ion:main' into feat/o1 [`3b58b498`](https://github.com/Veloera/Veloera/commit/3b58b498) +- Merge pull request #636 from HynoR/fix/smfix [`c289694f`](https://github.com/Veloera/Veloera/commit/c289694f) +- Merge pull request #641 from HynoR/main [`c37319cb`](https://github.com/Veloera/Veloera/commit/c37319cb) +- Merge pull request #642 from MartialBE/fix_gemini_thinking [`57f03674`](https://github.com/Veloera/Veloera/commit/57f03674) +- Merge branch 'Calcium-Ion:main' into fix/smfix [`5e3ba457`](https://github.com/Veloera/Veloera/commit/5e3ba457) +- Merge pull request #631 from xqx333/main [`70e78608`](https://github.com/Veloera/Veloera/commit/70e78608) +- Update channel.go [`1f4fc2d5`](https://github.com/Veloera/Veloera/commit/1f4fc2d5) +- Merge pull request #630 from xqx333/main [`c76021e9`](https://github.com/Veloera/Veloera/commit/c76021e9) +- Update channel.go [`173c9bc6`](https://github.com/Veloera/Veloera/commit/173c9bc6) +- Update cache.go [`739c0cc3`](https://github.com/Veloera/Veloera/commit/739c0cc3) +- Merge pull request #628 from Calcium-Ion/pr482-merge [`1f70904f`](https://github.com/Veloera/Veloera/commit/1f70904f) +- Merge remote-tracking branch 'guoruqiang/main' into pr482-merge [`c8f437c1`](https://github.com/Veloera/Veloera/commit/c8f437c1) +- Update README.md [`aab3887d`](https://github.com/Veloera/Veloera/commit/aab3887d) +- Merge pull request #625 from QAbot-zh/fix/bindwechat [`25ab1d40`](https://github.com/Veloera/Veloera/commit/25ab1d40) +- fix bindWeChat tips [`6c995d4b`](https://github.com/Veloera/Veloera/commit/6c995d4b) +- Merge pull request #624 from Calcium-Ion/channel-setting [`fb9f50ce`](https://github.com/Veloera/Veloera/commit/fb9f50ce) +- Merge pull request #623 from Calcium-Ion/channel-setting [`c1a5b0e1`](https://github.com/Veloera/Veloera/commit/c1a5b0e1) +- Merge pull request #618 from HynoR/feat/modeledit [`6ca68651`](https://github.com/Veloera/Veloera/commit/6ca68651) +- Merge pull request #617 from kingxjs/main [`e2f6444b`](https://github.com/Veloera/Veloera/commit/e2f6444b) +- Merge pull request #622 from Calcium-Ion/i18n-fix [`8e2b6d0a`](https://github.com/Veloera/Veloera/commit/8e2b6d0a) +- Update README [`cfdf6e48`](https://github.com/Veloera/Veloera/commit/cfdf6e48) +- Update README.en.md [`84130cdf`](https://github.com/Veloera/Veloera/commit/84130cdf) +- Add README.en.md [`d54cab2a`](https://github.com/Veloera/Veloera/commit/d54cab2a) +- Update README.md [`7aa8b88c`](https://github.com/Veloera/Veloera/commit/7aa8b88c) +- Merge branch 'Calcium-Ion:main' into feat/modeledit [`2eddb934`](https://github.com/Veloera/Veloera/commit/2eddb934) +- Update EditChannel.js [`36bf4b39`](https://github.com/Veloera/Veloera/commit/36bf4b39) +- Merge pull request #616 from Calcium-Ion/panel [`430d5fcd`](https://github.com/Veloera/Veloera/commit/430d5fcd) +- Merge pull request #613 from Calcium-Ion/mobile [`0455f30d`](https://github.com/Veloera/Veloera/commit/0455f30d) +- Merge pull request #612 from Calcium-Ion/mobile [`21d4dcad`](https://github.com/Veloera/Veloera/commit/21d4dcad) +- Merge pull request #611 from Calcium-Ion/mobile [`db84b26e`](https://github.com/Veloera/Veloera/commit/db84b26e) +- Merge pull request #610 from Calcium-Ion/mobile [`69f57728`](https://github.com/Veloera/Veloera/commit/69f57728) +- Merge pull request #609 from Calcium-Ion/mobile [`e5dc21d5`](https://github.com/Veloera/Veloera/commit/e5dc21d5) +- Merge pull request #605 from jochne/patch-1 [`1e1a22e7`](https://github.com/Veloera/Veloera/commit/1e1a22e7) +- Update relay-xunfei.go [`70b5a7fd`](https://github.com/Veloera/Veloera/commit/70b5a7fd) +- Merge pull request #600 from wzxjohn/upstream [`904a1858`](https://github.com/Veloera/Veloera/commit/904a1858) +- Merge pull request #597 from daggeryu/patch-3 [`3eca5809`](https://github.com/Veloera/Veloera/commit/3eca5809) +- fix 关键词搜索加标签聚合时,大于1个空标签渠道无法展开的问题 [`b9007ced`](https://github.com/Veloera/Veloera/commit/b9007ced) +- Merge pull request #596 from daggeryu/patch-2 [`8c42ea19`](https://github.com/Veloera/Veloera/commit/8c42ea19) +- 删除无用代码 [`9ce8940d`](https://github.com/Veloera/Veloera/commit/9ce8940d) +- Update README.md [`a7b5d684`](https://github.com/Veloera/Veloera/commit/a7b5d684) +- Update go.mod [`45bf4961`](https://github.com/Veloera/Veloera/commit/45bf4961) +- Merge pull request #594 from Calcium-Ion/gzip [`c645bf7e`](https://github.com/Veloera/Veloera/commit/c645bf7e) +- Merge pull request #593 from Calcium-Ion/gzip [`6b2f6753`](https://github.com/Veloera/Veloera/commit/6b2f6753) +- Merge pull request #592 from Calcium-Ion/gzip [`c75bc956`](https://github.com/Veloera/Veloera/commit/c75bc956) +- Merge pull request #591 from Calcium-Ion/no-cache [`4937a6d1`](https://github.com/Veloera/Veloera/commit/4937a6d1) +- Merge pull request #590 from iszcz/new512 [`ee990419`](https://github.com/Veloera/Veloera/commit/ee990419) +- 1 [`c8a29251`](https://github.com/Veloera/Veloera/commit/c8a29251) +- Update docker-compose.yml [`07b1c9a4`](https://github.com/Veloera/Veloera/commit/07b1c9a4) +- Merge pull request #589 from mrhaoji/main [`5d8de46e`](https://github.com/Veloera/Veloera/commit/5d8de46e) +- Merge pull request #588 from iszcz/new512 [`f693c13c`](https://github.com/Veloera/Veloera/commit/f693c13c) +- Update EditTagModal.js [`89cd0db2`](https://github.com/Veloera/Veloera/commit/89cd0db2) +- Update README.md [`ae57dd7b`](https://github.com/Veloera/Veloera/commit/ae57dd7b) +- Merge remote-tracking branch 'origin/main' [`87d763e6`](https://github.com/Veloera/Veloera/commit/87d763e6) +- Merge pull request #587 from Calcium-Ion/channel-tag [`08f3562e`](https://github.com/Veloera/Veloera/commit/08f3562e) +- Update docker-compose.yml [`2d1b2676`](https://github.com/Veloera/Veloera/commit/2d1b2676) +- Update README.md [`1035a8e0`](https://github.com/Veloera/Veloera/commit/1035a8e0) +- Merge pull request #586 from Calcium-Ion/channel-tag [`ea433b2e`](https://github.com/Veloera/Veloera/commit/ea433b2e) +- Merge pull request #574 from Calcium-Ion/channel-tag [`48abfd05`](https://github.com/Veloera/Veloera/commit/48abfd05) +- Merge pull request #582 from prnake/patch-1 [`821f3a75`](https://github.com/Veloera/Veloera/commit/821f3a75) +- Update README.md [`b71e33b0`](https://github.com/Veloera/Veloera/commit/b71e33b0) +- Update README.md [`e5778837`](https://github.com/Veloera/Veloera/commit/e5778837) +- Update README.md [`78cac708`](https://github.com/Veloera/Veloera/commit/78cac708) +- Merge pull request #579 from HynoR/main [`76f74746`](https://github.com/Veloera/Veloera/commit/76f74746) +- Update BT.md [`0dd1953c`](https://github.com/Veloera/Veloera/commit/0dd1953c) +- Update BT.md [`019361a7`](https://github.com/Veloera/Veloera/commit/019361a7) +- Update README.md [`9b9d73e7`](https://github.com/Veloera/Veloera/commit/9b9d73e7) +- Update README.md [`05b5d6f2`](https://github.com/Veloera/Veloera/commit/05b5d6f2) +- Chore: support gpt-4o-2024-11-20 [`79b6c0a7`](https://github.com/Veloera/Veloera/commit/79b6c0a7) +- Merge pull request #493 from xixingya/feature/bug-fix [`462c2cc1`](https://github.com/Veloera/Veloera/commit/462c2cc1) +- Merge pull request #569 from utopeadia/main [`6a42ccf0`](https://github.com/Veloera/Veloera/commit/6a42ccf0) +- Merge branch 'main' into main [`7aa7114b`](https://github.com/Veloera/Veloera/commit/7aa7114b) +- Merge pull request #570 from leezhuuuuu/main [`c3e6b240`](https://github.com/Veloera/Veloera/commit/c3e6b240) +- 增加对于gemini-exp-1114模型的支持,映射到v1beta [`5d96f7b2`](https://github.com/Veloera/Veloera/commit/5d96f7b2) +- Modify the default gemini API to v1beta [`8eb32e9b`](https://github.com/Veloera/Veloera/commit/8eb32e9b) +- Merge pull request #564 from Licoy/main [`8baeece3`](https://github.com/Veloera/Veloera/commit/8baeece3) +- Merge pull request #563 from Licoy/main [`0b0bcbab`](https://github.com/Veloera/Veloera/commit/0b0bcbab) +- 修复独立日志数据库查询令牌日志时错误问题 [`450bea8f`](https://github.com/Veloera/Veloera/commit/450bea8f) +- 优化设置页面的模块间距与部分数据获取提示 [`bf75df8f`](https://github.com/Veloera/Veloera/commit/bf75df8f) +- 封装OAuth2授权回调页面 [`c6dae4b8`](https://github.com/Veloera/Veloera/commit/c6dae4b8) +- Merge pull request #505 from OiAnthony/f_dotenv [`a5abd40f`](https://github.com/Veloera/Veloera/commit/a5abd40f) +- Merge pull request #562 from seefs001/main [`6d47b2c5`](https://github.com/Veloera/Veloera/commit/6d47b2c5) +- Merge pull request #555 from utopeadia/main [`be652fa3`](https://github.com/Veloera/Veloera/commit/be652fa3) +- Continue fixing Ollama embedding return issue [`2ffa4268`](https://github.com/Veloera/Veloera/commit/2ffa4268) +- Merge pull request #552 from utopeadia/main [`3037dfab`](https://github.com/Veloera/Veloera/commit/3037dfab) +- Modify ollama embed return fields [`5253a0e7`](https://github.com/Veloera/Veloera/commit/5253a0e7) +- Update README.md [`e5588fc1`](https://github.com/Veloera/Veloera/commit/e5588fc1) +- Merge pull request #551 from Calcium-Ion/realtime [`a859ff59`](https://github.com/Veloera/Veloera/commit/a859ff59) +- Update dto [`e3c85572`](https://github.com/Veloera/Veloera/commit/e3c85572) +- Merge pull request #549 from HynoR/main [`3785e9d7`](https://github.com/Veloera/Veloera/commit/3785e9d7) +- Sync Latest Claude Model [`902a66b6`](https://github.com/Veloera/Veloera/commit/902a66b6) +- Merge pull request #548 from utopeadia/main [`aaf3f09e`](https://github.com/Veloera/Veloera/commit/aaf3f09e) +- /api/embeddings is deprecated, use /api/embed. [`e5235558`](https://github.com/Veloera/Veloera/commit/e5235558) +- Merge remote-tracking branch 'origin/main' [`8b8abfad`](https://github.com/Veloera/Veloera/commit/8b8abfad) +- Merge pull request #533 from HynoR/main [`62e321fe`](https://github.com/Veloera/Veloera/commit/62e321fe) +- Update model-ratio.go [`a2678a25`](https://github.com/Veloera/Veloera/commit/a2678a25) +- Update README.md [`334a6f82`](https://github.com/Veloera/Veloera/commit/334a6f82) +- Merge pull request #509 from Calcium-Ion/playground [`af02cdc5`](https://github.com/Veloera/Veloera/commit/af02cdc5) +- Merge branch 'Calcium-Ion:main' into main [`b0d655e2`](https://github.com/Veloera/Veloera/commit/b0d655e2) +- 撤回修改 [`e4ccaddf`](https://github.com/Veloera/Veloera/commit/e4ccaddf) +- 更新LoginForm 提示 [`b139588a`](https://github.com/Veloera/Veloera/commit/b139588a) +- Update README.md [`ee17e307`](https://github.com/Veloera/Veloera/commit/ee17e307) +- 使用postMessage向iframe传参theme-mode,实现切换子页面主题的功能 子页面的js示例 ``` + + + + +
+ + + diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000000000000000000000000000000000000..14d68a8e1f39b847806ab96b64b0382cf01eed9f --- /dev/null +++ b/web/package.json @@ -0,0 +1,72 @@ +{ + "name": "react-template", + "version": "0.1.0", + "private": true, + "type": "module", + "dependencies": { + "@douyinfe/semi-icons": "^2.63.1", + "@douyinfe/semi-ui": "^2.69.1", + "@semi-bot/semi-theme-veloera-theme": "^1.0.0", + "@visactor/react-vchart": "~1.13.11", + "@visactor/vchart": "~1.13.11", + "@visactor/vchart-semi-theme": "~1.8.8", + "axios": "^1.12.0", + "dayjs": "^1.11.11", + "decimal.js": "^10.6.0", + "history": "^5.3.0", + "i18next": "^23.16.8", + "i18next-browser-languagedetector": "^7.2.0", + "marked": "^4.1.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", + "react-fireworks": "^1.0.4", + "react-i18next": "^13.0.0", + "react-router-dom": "^6.3.0", + "react-telegram-login": "^1.1.2", + "react-toastify": "^9.0.8", + "react-turnstile": "^1.0.5", + "semantic-ui-offline": "^2.5.0", + "semantic-ui-react": "^2.1.3", + "sse": "https://github.com/mpetazzoni/sse.js", + "vite-plugin-semi-theme": "^0.6.0" + }, + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "prettier . --check", + "lint:fix": "prettier . --write", + "preview": "vite preview" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@so1ve/prettier-config": "^3.1.0", + "@vitejs/plugin-react": "^4.2.1", + "prettier": "^3.0.0", + "sass-embedded": "^1.89.0", + "typescript": "4.4.2", + "vite": "^5.2.0" + }, + "prettier": { + "singleQuote": true, + "jsxSingleQuote": true + }, + "proxy": "http://localhost:3000" +} diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9332dada998fdfb35b0cdb42b6743c0ac3817ba0 --- /dev/null +++ b/web/pnpm-lock.yaml @@ -0,0 +1,4770 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@douyinfe/semi-icons': + specifier: ^2.63.1 + version: 2.77.1(react@18.3.1) + '@douyinfe/semi-ui': + specifier: ^2.69.1 + version: 2.77.1(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@semi-bot/semi-theme-veloera-theme': + specifier: ^1.0.0 + version: 1.0.0 + '@visactor/react-vchart': + specifier: ~1.13.11 + version: 1.13.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@visactor/vchart': + specifier: ~1.13.11 + version: 1.13.13 + '@visactor/vchart-semi-theme': + specifier: ~1.8.8 + version: 1.8.8(@visactor/vchart@1.13.13) + axios: + specifier: ^1.12.0 + version: 1.12.0 + dayjs: + specifier: ^1.11.11 + version: 1.11.13 + decimal.js: + specifier: ^10.6.0 + version: 10.6.0 + history: + specifier: ^5.3.0 + version: 5.3.0 + i18next: + specifier: ^23.16.8 + version: 23.16.8 + i18next-browser-languagedetector: + specifier: ^7.2.0 + version: 7.2.2 + marked: + specifier: ^4.1.1 + version: 4.3.0 + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + react-dropzone: + specifier: ^14.2.3 + version: 14.3.8(react@18.3.1) + react-fireworks: + specifier: ^1.0.4 + version: 1.0.4 + react-i18next: + specifier: ^13.0.0 + version: 13.5.0(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router-dom: + specifier: ^6.3.0 + version: 6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-telegram-login: + specifier: ^1.1.2 + version: 1.1.2(react@18.3.1) + react-toastify: + specifier: ^9.0.8 + version: 9.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-turnstile: + specifier: ^1.0.5 + version: 1.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + semantic-ui-offline: + specifier: ^2.5.0 + version: 2.5.0 + semantic-ui-react: + specifier: ^2.1.3 + version: 2.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + sse: + specifier: https://github.com/mpetazzoni/sse.js + version: sse.js@https://codeload.github.com/mpetazzoni/sse.js/tar.gz/72cea4849a6da65f526b693b4145fdaec866720f + vite-plugin-semi-theme: + specifier: ^0.6.0 + version: 0.6.0 + devDependencies: + '@so1ve/prettier-config': + specifier: ^3.1.0 + version: 3.1.0(prettier@3.5.3) + '@vitejs/plugin-react': + specifier: ^4.2.1 + version: 4.3.4(vite@5.4.16(sass-embedded@1.89.0)(sass@1.89.2)) + prettier: + specifier: ^3.0.0 + version: 3.5.3 + sass-embedded: + specifier: ^1.89.0 + version: 1.89.0 + typescript: + specifier: 4.4.2 + version: 4.4.2 + vite: + specifier: ^5.2.0 + version: 5.4.16(sass-embedded@1.89.0)(sass@1.89.2) + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@astrojs/compiler@2.11.0': + resolution: {integrity: sha512-zZOO7i+JhojO8qmlyR/URui6LyfHJY6m+L9nwyX5GiKD78YoRaZ5tzz6X0fkl+5bD3uwlDHayf6Oe8Fu36RKNg==} + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.8': + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.10': + resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.27.0': + resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.0': + resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.26.5': + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.27.0': + resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.0': + resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.25.9': + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.25.9': + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.27.0': + resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.0': + resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.27.0': + resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.27.0': + resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} + engines: {node: '>=6.9.0'} + + '@bufbuild/protobuf@2.4.0': + resolution: {integrity: sha512-RN9M76x7N11QRihKovEglEjjVCQEA9PRBVnDgk9xw8JHLrcUrp4FpAVSPSH91cNbcTft3u2vpLN4GMbiKY9PJw==} + + '@dnd-kit/accessibility@3.1.1': + resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} + peerDependencies: + react: '>=16.8.0' + + '@dnd-kit/core@6.3.1': + resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/sortable@7.0.2': + resolution: {integrity: sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==} + peerDependencies: + '@dnd-kit/core': ^6.0.7 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + + '@douyinfe/semi-animation-react@2.77.1': + resolution: {integrity: sha512-imELR02pufgGFkZURfTd9oBUtZPYhHvXv9WsYoRvEoBM9U7yzxrR6Fb/Lc3TH+WHVJ2oZHH2S0APS5t1MceEOw==} + + '@douyinfe/semi-animation-styled@2.77.1': + resolution: {integrity: sha512-FBRroqVJroel1CXmBgV58ulZHG2xUVInJF7k0FAag54noKKaToEobSxRjiTJ6JHne3ZDU1M6sBqpbzYJElFnPQ==} + + '@douyinfe/semi-animation@2.77.1': + resolution: {integrity: sha512-Q1D7whvQe0D+mPov8hXeH/e1uR/iBhpGGcW1LCTL2pSVMEZEYGJLf2KeXTTiCIgRVWm0PRH3Sux7auJ64zg7vw==} + + '@douyinfe/semi-foundation@2.77.1': + resolution: {integrity: sha512-DAXRy8ryLNzbKAiTAv+RrivGCoMU0asv2cO7PNV5aBq0ICB8XXn97FHyZo6Wb5NpqpyMhOaOr8Ro1bfpd0FeaA==} + + '@douyinfe/semi-icons@2.77.1': + resolution: {integrity: sha512-IbGqYzbjzCoSd+//HlO/Gn1c3XmbulQwGys+JgDfQhYIbPeGyhQfLk56Q7ku3vJGC8BGy7dUmR9MbeTf1UQGtw==} + peerDependencies: + react: '>=16.0.0' + + '@douyinfe/semi-illustrations@2.77.1': + resolution: {integrity: sha512-FlESLOPaY0SadiSIFcP4gqJUk+CYkd4rHK6YP9bfjmU26v7h1S02H7pGLLV1lS0WnY4j0ad4zqRV9tbXFvba9g==} + peerDependencies: + react: '>=16.0.0' + + '@douyinfe/semi-json-viewer-core@2.77.1': + resolution: {integrity: sha512-LOW+7ga2OzFIL9pGKftwHfl1kKLTV3x6Cs857iyvq9GIF/GHbAboiHcKUy2OZIHfy66zvP+Focs+yhfZG7IcZw==} + + '@douyinfe/semi-theme-default@2.77.1': + resolution: {integrity: sha512-Rug75C7jjSqmCP2L2tBI0K4dnXuo4GardzwSzdSjxDkiaIXwOwR5KE0K1FRbKWkQ7xmxbyRu4S6Pff+CDEJ/lA==} + + '@douyinfe/semi-ui@2.77.1': + resolution: {integrity: sha512-eIy7kr9OleCwlNRby3VICSGScHM23Zt2u7TJpID68qN3WrfQowGaB4wQ/0k5bvpLzv463HQnVWFk5aak+v46yw==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@fluentui/react-component-event-listener@0.63.1': + resolution: {integrity: sha512-gSMdOh6tI3IJKZFqxfQwbTpskpME0CvxdxGM2tdglmf6ZPVDi0L4+KKIm+2dN8nzb8Ya1A8ZT+Ddq0KmZtwVQg==} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + + '@fluentui/react-component-ref@0.63.1': + resolution: {integrity: sha512-8MkXX4+R3i80msdbD4rFpEB4WWq2UDvGwG386g3ckIWbekdvN9z2kWAd9OXhRGqB7QeOsoAGWocp6gAMCivRlw==} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@mdx-js/mdx@3.1.0': + resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==} + + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + + '@remix-run/router@1.23.0': + resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==} + engines: {node: '>=14.0.0'} + + '@resvg/resvg-js-android-arm-eabi@2.4.1': + resolution: {integrity: sha512-AA6f7hS0FAPpvQMhBCf6f1oD1LdlqNXKCxAAPpKh6tR11kqV0YIB9zOlIYgITM14mq2YooLFl6XIbbvmY+jwUw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@resvg/resvg-js-android-arm64@2.4.1': + resolution: {integrity: sha512-/QleoRdPfsEuH9jUjilYcDtKK/BkmWcK+1LXM8L2nsnf/CI8EnFyv7ZzCj4xAIvZGAy9dTYr/5NZBcTwxG2HQg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@resvg/resvg-js-darwin-arm64@2.4.1': + resolution: {integrity: sha512-U1oMNhea+kAXgiEXgzo7EbFGCD1Edq5aSlQoe6LMly6UjHzgx2W3N5kEXCwU/CgN5FiQhZr7PlSJSlcr7mdhfg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@resvg/resvg-js-darwin-x64@2.4.1': + resolution: {integrity: sha512-avyVh6DpebBfHHtTQTZYSr6NG1Ur6TEilk1+H0n7V+g4F7x7WPOo8zL00ZhQCeRQ5H4f8WXNWIEKL8fwqcOkYw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@resvg/resvg-js-linux-arm-gnueabihf@2.4.1': + resolution: {integrity: sha512-isY/mdKoBWH4VB5v621co+8l101jxxYjuTkwOLsbW+5RK9EbLciPlCB02M99ThAHzI2MYxIUjXNmNgOW8btXvw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@resvg/resvg-js-linux-arm64-gnu@2.4.1': + resolution: {integrity: sha512-uY5voSCrFI8TH95vIYBm5blpkOtltLxLRODyhKJhGfskOI7XkRw5/t1u0sWAGYD8rRSNX+CA+np86otKjubrNg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@resvg/resvg-js-linux-arm64-musl@2.4.1': + resolution: {integrity: sha512-6mT0+JBCsermKMdi/O2mMk3m7SqOjwi9TKAwSngRZ/nQoL3Z0Z5zV+572ztgbWr0GODB422uD8e9R9zzz38dRQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@resvg/resvg-js-linux-x64-gnu@2.4.1': + resolution: {integrity: sha512-60KnrscLj6VGhkYOJEmmzPlqqfcw1keDh6U+vMcNDjPhV3B5vRSkpP/D/a8sfokyeh4VEacPSYkWGezvzS2/mg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@resvg/resvg-js-linux-x64-musl@2.4.1': + resolution: {integrity: sha512-0AMyZSICC1D7ge115cOZQW8Pcad6PjWuZkBFF3FJuSxC6Dgok0MQnLTs2MfMdKBlAcwO9dXsf3bv9tJZj8pATA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@resvg/resvg-js-win32-arm64-msvc@2.4.1': + resolution: {integrity: sha512-76XDFOFSa3d0QotmcNyChh2xHwk+JTFiEQBVxMlHpHMeq7hNrQJ1IpE1zcHSQvrckvkdfLboKRrlGB86B10Qjw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@resvg/resvg-js-win32-ia32-msvc@2.4.1': + resolution: {integrity: sha512-odyVFGrEWZIzzJ89KdaFtiYWaIJh9hJRW/frcEcG3agJ464VXkN/2oEVF5ulD+5mpGlug9qJg7htzHcKxDN8sg==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@resvg/resvg-js-win32-x64-msvc@2.4.1': + resolution: {integrity: sha512-vY4kTLH2S3bP+puU5x7hlAxHv+ulFgcK6Zn3efKSr0M0KnZ9A3qeAjZteIpkowEFfUeMPNg2dvvoFRJA9zqxSw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@resvg/resvg-js@2.4.1': + resolution: {integrity: sha512-wTOf1zerZX8qYcMmLZw3czR4paI4hXqPjShNwJRh5DeHxvgffUS5KM7XwxtbIheUW6LVYT5fhT2AJiP6mU7U4A==} + engines: {node: '>= 10'} + + '@rollup/rollup-android-arm-eabi@4.39.0': + resolution: {integrity: sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.39.0': + resolution: {integrity: sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.39.0': + resolution: {integrity: sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.39.0': + resolution: {integrity: sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.39.0': + resolution: {integrity: sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.39.0': + resolution: {integrity: sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.39.0': + resolution: {integrity: sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.39.0': + resolution: {integrity: sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.39.0': + resolution: {integrity: sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.39.0': + resolution: {integrity: sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.39.0': + resolution: {integrity: sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': + resolution: {integrity: sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.39.0': + resolution: {integrity: sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.39.0': + resolution: {integrity: sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.39.0': + resolution: {integrity: sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.39.0': + resolution: {integrity: sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.39.0': + resolution: {integrity: sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.39.0': + resolution: {integrity: sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.39.0': + resolution: {integrity: sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.39.0': + resolution: {integrity: sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==} + cpu: [x64] + os: [win32] + + '@semantic-ui-react/event-stack@3.1.3': + resolution: {integrity: sha512-FdTmJyWvJaYinHrKRsMLDrz4tTMGdFfds299Qory53hBugiDvGC0tEJf+cHsi5igDwWb/CLOgOiChInHwq8URQ==} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + + '@semi-bot/semi-theme-veloera-theme@1.0.0': + resolution: {integrity: sha512-pWPjV0yK851lYaKhGkN3lxMlvNHEEsxAksomBcUPV2USo7+jnv3ZhDBsSf8klntUcVzD1GC63ZfmhyqT2vLuPg==} + + '@so1ve/prettier-config@3.1.0': + resolution: {integrity: sha512-9GJ1yXKBC4DzqCTTaZoBf8zw7WWkVuXcccZt1Aqk4lj6ab/GiNUnjPGajUVYLjaqAEOKqM7jUSUfTjk2JTjCAg==} + peerDependencies: + prettier: ^3.0.0 + + '@so1ve/prettier-plugin-toml@3.1.0': + resolution: {integrity: sha512-8WZAGjAVNIJlkfWL6wHKxlUuEBY45fdd5qY5bR/Z6r/txgzKXk/r9qi1DTwc17gi/WcNuRrcRugecRT+mWbIYg==} + peerDependencies: + prettier: ^3.0.0 + + '@turf/boolean-clockwise@6.5.0': + resolution: {integrity: sha512-45+C7LC5RMbRWrxh3Z0Eihsc8db1VGBO5d9BLTOAwU4jR6SgsunTfRWR16X7JUwIDYlCVEmnjcXJNi/kIU3VIw==} + + '@turf/clone@6.5.0': + resolution: {integrity: sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw==} + + '@turf/flatten@6.5.0': + resolution: {integrity: sha512-IBZVwoNLVNT6U/bcUUllubgElzpMsNoCw8tLqBw6dfYg9ObGmpEjf9BIYLr7a2Yn5ZR4l7YIj2T7kD5uJjZADQ==} + + '@turf/helpers@6.5.0': + resolution: {integrity: sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==} + + '@turf/invariant@6.5.0': + resolution: {integrity: sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==} + + '@turf/meta@3.14.0': + resolution: {integrity: sha512-OtXqLQuR9hlQ/HkAF/OdzRea7E0eZK1ay8y8CBXkoO2R6v34CsDrWYLMSo0ZzMsaQDpKo76NPP2GGo+PyG1cSg==} + + '@turf/meta@6.5.0': + resolution: {integrity: sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==} + + '@turf/rewind@6.5.0': + resolution: {integrity: sha512-IoUAMcHWotBWYwSYuYypw/LlqZmO+wcBpn8ysrBNbazkFNkLf3btSDZMkKJO/bvOzl55imr/Xj4fi3DdsLsbzQ==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.7': + resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/parse-author@2.0.3': + resolution: {integrity: sha512-pgRW2K/GVQoogylrGJXDl7PBLW9A6T4OOc9Hy9MLT5f7vgufK2GQ8FcfAbjFHR5HjcN9ByzuCczAORk49REqoA==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@visactor/react-vchart@1.13.13': + resolution: {integrity: sha512-wER298ChiGh5EEApkZbXuPgSc0uwwjbFmumX9LPcRPdAMTz8dIL66m9vg1Ztm7YiHHpwuocBTl8bohFEYdIl6w==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@visactor/vchart-semi-theme@1.8.8': + resolution: {integrity: sha512-lm57CX3r6Bm7iGBYYyWhDY+1BvkyhNVLEckKx2PnlPKpJHikKSIK2ACyI5SmHuSOOdYzhY2QK6ZfYa2NShJ83w==} + peerDependencies: + '@visactor/vchart': ~1.8.8 + + '@visactor/vchart-theme-utils@1.8.8': + resolution: {integrity: sha512-RdCey3/t0+82EYyFZvx210rgJJWti9rsgcL3ROZS7o9CtRW1CMj9u9LKLDNIcPLNcLNACFC0aoT03jpdD1BCpA==} + peerDependencies: + '@visactor/vchart': ~1.8.8 + + '@visactor/vchart@1.13.13': + resolution: {integrity: sha512-36kQHpSfJdyiBsBgaD8LEl0ZZnHHDGWIu5vGyHJxJ84D1OCVNk9s/GvD2N0VcSbIEIEO9rfwnOypdOj+aQBqHA==} + + '@visactor/vdataset@0.19.7': + resolution: {integrity: sha512-M73x54v/BwOX3knnESVTk4+vpMUng13a4uxDNqBkJVIbcShp/cQxYvGb81mSxHFiIM3S9kn3LRQMYP/PuGlBOA==} + + '@visactor/vgrammar-coordinate@0.16.10': + resolution: {integrity: sha512-4yYc8QRuaSk2hWPRBWKk0UTzSySkfB4tmSxwHjsjcRlRPaV6XGqNT3eyoRr6pXv745XgVN1dgm5R0MHg3xEAeQ==} + + '@visactor/vgrammar-core@0.16.10': + resolution: {integrity: sha512-VxrE0ooIRGDbUO5Ck7cheaXQaAtLX4fDkrKixQ81RPoxdW0JNXOqVSFUTL0lYzmJfNUnXOBq0UQSefK5IKcURA==} + + '@visactor/vgrammar-hierarchy@0.16.10': + resolution: {integrity: sha512-Buzpoj42KY+yntUazt3RUYOmYMlc2QrvlYZyvvXr/juJaGhysQqCFt8BDbK+ahyXuxxa3C9Dbna1IP24RhyHug==} + + '@visactor/vgrammar-projection@0.16.10': + resolution: {integrity: sha512-C6S2YZLOOYY0XSpKaXKc0dbC2gYGBdGQtfPHyvh6Rp/98qAE6h7r9JBaAiyOZZFcjM3n08IKRpTYFZBEbQmfhg==} + + '@visactor/vgrammar-sankey@0.16.10': + resolution: {integrity: sha512-P40ptCDLkMt8+cJaLFg/u0EfRF/mJVg/r9WpLpIsMaT2qKyAzPFxa4RaHLYL6PQR3CWCEC7U5lHRaGAEsn/0gA==} + + '@visactor/vgrammar-util@0.16.10': + resolution: {integrity: sha512-471HM3KYD2gXwsAoWYjGL/4QhULkt66ragedcAJxa/JIrJBUWMY/i+lKJL4NEbYN7kkTMehbbNskBTqHxmOuSA==} + + '@visactor/vgrammar-venn@0.16.10': + resolution: {integrity: sha512-ee+ojbWOZxWpv9wK3F06CcWTqCGFiCuQ7K/pHO49xfV7KpNJTPzjJx2GOo4aNgMs25UcibatKAOhT5129XYhlg==} + + '@visactor/vgrammar-wordcloud-shape@0.16.10': + resolution: {integrity: sha512-6TV7EWB9gGsxuvvip9Y1KwC1LNbVHdJZKZZSwyM5j5JlGLnBPu4FBgpAEmM0mB0RAkIqwSL0GXdITC+/R5po/A==} + + '@visactor/vgrammar-wordcloud@0.16.10': + resolution: {integrity: sha512-a03ZVkszMk8ygaSQgwK2Xs6k2MAIRIGAUeFXlj2Mu1Akh/nk/xMe1VWBF1JR8iPiCuP+rUTOb7RUHd3HYGqbHw==} + + '@visactor/vrender-components@0.22.15': + resolution: {integrity: sha512-3N0PrZ+VIh2IJb8rOefMSORCmHOF9ABjuMkb7wJNnARUQ+HiembUxQzB69aCALCu9I/Jp8fweSOC+n9ougjKJA==} + + '@visactor/vrender-core@0.22.15': + resolution: {integrity: sha512-jCk+6Ej9J/K+CsTiaXE6wf/YiywZ2FSjk6AVVqrmSwgpK8bh/p4KtMdAJrhBU3gfC4LDzU8Ufpjx6X0Jl5XNCw==} + + '@visactor/vrender-kits@0.22.15': + resolution: {integrity: sha512-TXO+b+dkRgGiglMawaOrOTtGa+JGJZUGwTeHnUILW7o3FL33zCYPFssgTUS4kU+CiX9czHyq4WG/YLKw9hybyg==} + + '@visactor/vscale@0.19.7': + resolution: {integrity: sha512-hgRB/kUINR0Weq9bJ7kSXV+RBEiL1qVG2hYbIGNIAYBN/dXfr0p6YME7l86CYgYW5CMPQl5aTbr24qhj9vC51Q==} + + '@visactor/vutils-extension@1.13.13': + resolution: {integrity: sha512-Fuy0c/h7AnYlvPMRejIYUwsaGjwPhvT/Ba1Qywa0IDlgzaNnp9WzcVES3bB+dxhlPJJux+qy48qNiip1VVyksg==} + + '@visactor/vutils@0.19.7': + resolution: {integrity: sha512-1SSnkZgX1p/rSVIFEibrpN6rDdLfdETSI6lJI5JwV8I2paluM1mqz3jEeT3McmWygd/wyUVKAyoRxGXFKAsKEw==} + + '@vitejs/plugin-react@4.3.4': + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + + abs-svg-path@0.1.1: + resolution: {integrity: sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + + array-source@0.0.4: + resolution: {integrity: sha512-frNdc+zBn80vipY+GdcJkLEbMWj3xmzArYApmUGxoiV8uAu/ygcs9icPdsGdA26h0MkHUMW6EN2piIvVx+M5Mw==} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + async-validator@3.5.2: + resolution: {integrity: sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + attr-accept@2.2.5: + resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==} + engines: {node: '>=4'} + + author-regex@1.0.0: + resolution: {integrity: sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g==} + engines: {node: '>=0.8'} + + axios@1.12.0: + resolution: {integrity: sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bezier-easing@2.1.0: + resolution: {integrity: sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-builder@0.2.0: + resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001709: + resolution: {integrity: sha512-NgL3vUTnDrPCZ3zTahp4fsugQ4dc7EKTSzwQDPEel6DMoMnfH2jhry9n2Zm8onbSR+f/QtKHFOA+iAQu4kbtWA==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + clsx@1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorjs.io@0.5.2: + resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + compute-scroll-into-view@1.0.20: + resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-stream@1.4.11: + resolution: {integrity: sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==} + engines: {'0': node >= 0.8} + + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + copy-text-to-clipboard@2.2.0: + resolution: {integrity: sha512-WRvoIdnTs1rgPMkgA2pUOa/M4Enh2uzCwdKsOMYNAJiz/4ZvEJgmbF4OmninPmlFdAWisfeh0tH+Cpf7ni3RqQ==} + engines: {node: '>=6'} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + + d3-array@1.2.4: + resolution: {integrity: sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==} + + d3-dsv@2.0.0: + resolution: {integrity: sha512-E+Pn8UJYx9mViuIUkoc93gJGGYut6mSDKy2+XaPwccwkRGlR+LO97L2VCCRjQivTwLHkSnAJG7yo00BWY6QM+w==} + hasBin: true + + d3-geo@1.12.1: + resolution: {integrity: sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==} + + d3-hexbin@0.2.2: + resolution: {integrity: sha512-KS3fUT2ReD4RlGCjvCEm1RgMtp2NFZumdMu4DBzQK8AZv3fXRM6Xm8I4fSU07UXvH4xxg03NwWKWdvxfS/yc4w==} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + date-fns-tz@1.3.8: + resolution: {integrity: sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==} + peerDependencies: + date-fns: '>=2.0.0' + + date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + decode-named-character-reference@1.1.0: + resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.130: + resolution: {integrity: sha512-Ou2u7L9j2XLZbhqzyX0jWDj6gA8D3jIfVzt4rikLf3cGBa0VdReuFimBKS9tQJA4+XpeCxj1NoWlfBXzbMa9IA==} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + exenv@1.2.2: + resolution: {integrity: sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-copy@3.0.2: + resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} + + file-selector@2.1.2: + resolution: {integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==} + engines: {node: '>= 12'} + + file-source@0.6.1: + resolution: {integrity: sha512-1R1KneL7eTXmXfKxC10V/9NeGOdbsAXJ+lQ//fvvcHUgtaZcZDWNJNblxAoVOyV1cj45pOtUrR3vZTBwqcW8XA==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.4: + resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + engines: {node: '>= 6'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@4.0.3: + resolution: {integrity: sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + geobuf@3.0.2: + resolution: {integrity: sha512-ASgKwEAQQRnyNFHNvpd5uAwstbVYmiTW0Caw3fBb509tNTqXyAAPMyFs5NNihsLZhLxU1j/kjFhkhLWA9djuVg==} + hasBin: true + + geojson-dissolve@3.1.0: + resolution: {integrity: sha512-JXHfn+A3tU392HA703gJbjmuHaQOAE/C1KzbELCczFRFux+GdY6zt1nKb1VMBHp4LWeE7gUY2ql+g06vJqhiwQ==} + + geojson-flatten@0.2.4: + resolution: {integrity: sha512-LiX6Jmot8adiIdZ/fthbcKKPOfWjTQchX/ggHnwMZ2e4b0I243N1ANUos0LvnzepTEsj0+D4fIJ5bKhBrWnAHA==} + hasBin: true + + geojson-linestring-dissolve@0.0.1: + resolution: {integrity: sha512-Y8I2/Ea28R/Xeki7msBcpMvJL2TaPfaPKP8xqueJfQ9/jEhps+iOJxOR2XCBGgVb12Z6XnDb1CMbaPfLepsLaw==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stdin@6.0.0: + resolution: {integrity: sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==} + engines: {node: '>=4'} + + gifuct-js@2.1.2: + resolution: {integrity: sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg==} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + history@5.3.0: + resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==} + + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + + i18next-browser-languagedetector@7.2.2: + resolution: {integrity: sha512-6b7r75uIJDWCcCflmbof+sJ94k9UQO4X0YR62oUfqGI/GjCLVzlCwu8TFdRZIqVLzWbzNcmkmhfqKEr4TLz4HQ==} + + i18next@23.16.8: + resolution: {integrity: sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + immutable@5.1.2: + resolution: {integrity: sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + inline-style-parser@0.2.4: + resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + + jquery@3.7.1: + resolution: {integrity: sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==} + + js-binary-schema-parser@2.0.3: + resolution: {integrity: sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + keyboard-key@1.1.0: + resolution: {integrity: sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lottie-web@5.12.2: + resolution: {integrity: sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + marked@4.3.0: + resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} + engines: {node: '>= 12'} + hasBin: true + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimist@1.2.0: + resolution: {integrity: sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==} + + minimist@1.2.6: + resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-author@2.0.0: + resolution: {integrity: sha512-yx5DfvkN8JsHL2xk2Os9oTia467qnvRgey4ahSm2X8epehBLx/gWLcy5KI+Y36ful5DzGbCS6RazqZGgy1gHNw==} + engines: {node: '>=0.10.0'} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-svg-path@0.1.2: + resolution: {integrity: sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-source@0.1.3: + resolution: {integrity: sha512-dWRHm5mIw5kw0cs3QZLNmpUWty48f5+5v9nWD2dw3Y0Hf+s01Ag8iJEWV0Sm0kocE8kK27DrIowha03e1YR+Qw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pbf@3.3.0: + resolution: {integrity: sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==} + hasBin: true + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + point-at-length@1.1.0: + resolution: {integrity: sha512-nNHDk9rNEh/91o2Y8kHLzBLNpLf80RYd2gCun9ss+V0ytRSf6XhryBTx071fesktjbachRmGuUbId+JQmzhRXw==} + + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + + prettier-package-json@2.8.0: + resolution: {integrity: sha512-WxtodH/wWavfw3MR7yK/GrS4pASEQ+iSTkdtSxPJWvqzG55ir5nvbLt9rw5AOiEcqqPCRM92WCtR1rk3TG3JSQ==} + hasBin: true + + prettier-plugin-astro@0.14.1: + resolution: {integrity: sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==} + engines: {node: ^14.15.0 || >=16.0.0} + + prettier-plugin-curly-and-jsdoc@3.1.0: + resolution: {integrity: sha512-4QMOHnLlkP2jTRWS0MFH6j+cuOiXLvXOqCLKbtwwVd8PPyq8NenW5AAwfwqiTNHBQG/DmzViPphRrwgN0XkUVQ==} + peerDependencies: + prettier: ^3.0.0 + + prettier-plugin-pkgsort@0.2.1: + resolution: {integrity: sha512-/k5MIw84EhgoH7dmq4+6ozHjJ0VYbxbw17g4C+WPGHODkLivGwJoA6U1YPR/KObyRDMQJHXAfXKu++9smg7Jyw==} + peerDependencies: + prettier: ^3.0.0 + + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + engines: {node: '>=14'} + hasBin: true + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + property-information@7.0.0: + resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} + + protocol-buffers-schema@3.6.0: + resolution: {integrity: sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-draggable@4.4.6: + resolution: {integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==} + peerDependencies: + react: '>= 16.3.0' + react-dom: '>= 16.3.0' + + react-dropzone@14.3.8: + resolution: {integrity: sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==} + engines: {node: '>= 10.13'} + peerDependencies: + react: '>= 16.8 || 18.0.0' + + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + + react-fireworks@1.0.4: + resolution: {integrity: sha512-jj1a+HTicB4pR6g2lqhVyAox0GTE0TOrZK2XaJFRYOwltgQWeYErZxnvU9+zH/blY+Hpmu9IKyb39OD3KcCMJw==} + + react-i18next@13.5.0: + resolution: {integrity: sha512-CFJ5NDGJ2MUyBohEHxljOq/39NQ972rh1ajnadG9BjTk+UXbHLq4z5DKEbEQBDoIhUmmbuS/fIMJKo6VOax1HA==} + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-popper@2.3.0: + resolution: {integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==} + peerDependencies: + '@popperjs/core': ^2.0.0 + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react-resizable@3.0.5: + resolution: {integrity: sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==} + peerDependencies: + react: '>= 16.3' + + react-router-dom@6.30.0: + resolution: {integrity: sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-router@6.30.0: + resolution: {integrity: sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + + react-telegram-login@1.1.2: + resolution: {integrity: sha512-pDP+bvfaklWgnK5O6yvZnIwgky0nnYUU6Zhk0EjdMSkPsLQoOzZRsXIoZnbxyBXhi7346bsxMH+EwwJPTxClDw==} + peerDependencies: + react: ^16.13.1 + + react-toastify@9.1.3: + resolution: {integrity: sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==} + peerDependencies: + react: '>=16' + react-dom: '>=16' + + react-turnstile@1.1.4: + resolution: {integrity: sha512-oluyRWADdsufCt5eMqacW4gfw8/csr6Tk+fmuaMx0PWMKP1SX1iCviLvD2D5w92eAzIYDHi/krUWGHhlfzxTpQ==} + peerDependencies: + react: '>= 16.13.1' + react-dom: '>= 16.13.1' + + react-window@1.8.11: + resolution: {integrity: sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==} + engines: {node: '>8.0.0'} + peerDependencies: + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + readable-stream@1.1.14: + resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.0: + resolution: {integrity: sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==} + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-mdx@3.1.0: + resolution: {integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-protobuf-schema@2.1.0: + resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} + + rollup@4.39.0: + resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + roughjs@4.5.2: + resolution: {integrity: sha512-2xSlLDKdsWyFxrveYWk9YQ/Y9UfK38EAMRNkYkMqYBJvPX8abCa9PN0x3w02H8Oa6/0bcZICJU+U95VumPqseg==} + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + s.color@0.0.15: + resolution: {integrity: sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sass-embedded-android-arm64@1.89.0: + resolution: {integrity: sha512-pr4R3p5R+Ul9ZA5nzYbBJQFJXW6dMGzgpNBhmaToYDgDhmNX5kg0mZAUlGLHvisLdTiR6oEfDDr9QI6tnD2nqA==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [android] + + sass-embedded-android-arm@1.89.0: + resolution: {integrity: sha512-s6jxkEZQQrtyIGZX6Sbcu7tEixFG2VkqFgrX11flm/jZex7KaxnZtFace+wnYAgHqzzYpx0kNzJUpT+GXxm8CA==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [android] + + sass-embedded-android-ia32@1.89.0: + resolution: {integrity: sha512-GoNnNGYmp1F0ZMHqQbAurlQsjBMZKtDd5H60Ruq86uQFdnuNqQ9wHKJsJABxMnjfAn60IjefytM5PYTMcAmbfA==} + engines: {node: '>=14.0.0'} + cpu: [ia32] + os: [android] + + sass-embedded-android-riscv64@1.89.0: + resolution: {integrity: sha512-di+i4KkKAWTNksaQYTqBEERv46qV/tvv14TPswEfak7vcTQ2pj2mvV4KGjLYfU2LqRkX/NTXix9KFthrzFN51Q==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [android] + + sass-embedded-android-x64@1.89.0: + resolution: {integrity: sha512-1cRRDAnmAS1wLaxfFf6PCHu9sKW8FNxdM7ZkanwxO9mztrCu/uvfqTmaurY9+RaKvPus7sGYFp46/TNtl/wRjg==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [android] + + sass-embedded-darwin-arm64@1.89.0: + resolution: {integrity: sha512-EUNUzI0UkbQ6dASPyf09S3x7fNT54PjyD594ZGTY14Yh4qTuacIj27ckLmreAJNNu5QxlbhyYuOtz+XN5bMMxA==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [darwin] + + sass-embedded-darwin-x64@1.89.0: + resolution: {integrity: sha512-23R8zSuB31Fq/MYpmQ38UR2C26BsYb66VVpJgWmWl/N+sgv/+l9ECuSPMbYNgM3vb9TP9wk9dgL6KkiCS5tAyg==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [darwin] + + sass-embedded-linux-arm64@1.89.0: + resolution: {integrity: sha512-g9Lp57qyx51ttKj0AN/edV43Hu1fBObvD7LpYwVfs6u3I95r0Adi90KujzNrUqXxJVmsfUwseY8kA8zvcRjhYA==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + + sass-embedded-linux-arm@1.89.0: + resolution: {integrity: sha512-KAzA1XD74d8/fiJXxVnLfFwfpmD2XqUJZz+DL6ZAPNLH1sb+yCP7brktaOyClDc/MBu61JERdHaJjIZhfX0Yqw==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + + sass-embedded-linux-ia32@1.89.0: + resolution: {integrity: sha512-5fxBeXyvBr3pb+vyrx9V6yd7QDRXkAPbwmFVVhjqshBABOXelLysEFea7xokh/tM8JAAQ4O8Ls3eW3Eojb477g==} + engines: {node: '>=14.0.0'} + cpu: [ia32] + os: [linux] + + sass-embedded-linux-musl-arm64@1.89.0: + resolution: {integrity: sha512-50oelrOtN64u15vJN9uJryIuT0+UPjyeoq0zdWbY8F7LM9294Wf+Idea+nqDUWDCj1MHndyPFmR1mjeuRouJhw==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + + sass-embedded-linux-musl-arm@1.89.0: + resolution: {integrity: sha512-0Q1JeEU4/tzH7fwAwarfIh+Swn3aXG/jPhVsZpbR1c1VzkeaPngmXdmLJcVXsdb35tjk84DuYcFtJlE1HYGw4Q==} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + + sass-embedded-linux-musl-ia32@1.89.0: + resolution: {integrity: sha512-ILWqpTd+0RdsSw977iVAJf4CLetIbcQgLQf17ycS1N4StZKVRZs1bBfZhg/f/HU/4p5HondPAwepgJepZZdnFA==} + engines: {node: '>=14.0.0'} + cpu: [ia32] + os: [linux] + + sass-embedded-linux-musl-riscv64@1.89.0: + resolution: {integrity: sha512-n2V+Tdjj7SAuiuElJYhWiHjjB1YU0cuFvL1/m5K+ecdNStfHFWIzvBT6/vzQnBOWjI4eZECNVuQ8GwGWCufZew==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + + sass-embedded-linux-musl-x64@1.89.0: + resolution: {integrity: sha512-KOHJdouBK3SLJKZLnFYzuxs3dn+6jaeO3p4p1JUYAcVfndcvh13Sg2sLGfOfpg7Og6ws2Nnqnx0CyL26jPJ7ag==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + + sass-embedded-linux-riscv64@1.89.0: + resolution: {integrity: sha512-0A/UWeKX6MYhVLWLkdX3NPKHO+mvIwzaf6TxGCy3vS3TODWaeDUeBhHShAr7YlOKv5xRGxf7Gx7FXCPV0mUyMA==} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + + sass-embedded-linux-x64@1.89.0: + resolution: {integrity: sha512-dRBoOFPDWctHPYK3hTk3YzyX/icVrXiw7oOjbtpaDr6JooqIWBe16FslkWyvQzdmfOFy80raKVjgoqT7DsznkQ==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + + sass-embedded-win32-arm64@1.89.0: + resolution: {integrity: sha512-RnlVZ14hC/W7ubzvhqnbGfjU5PFNoFP/y5qycgCy+Mezb0IKbWvZ2Lyzux8TbL3OIjOikkNpfXoNQrX706WLAA==} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [win32] + + sass-embedded-win32-ia32@1.89.0: + resolution: {integrity: sha512-eFe9VMNG+90nuoE3eXDy+38+uEHGf7xcqalq5+0PVZfR+H9RlaEbvIUNflZV94+LOH8Jb4lrfuekhHgWDJLfSg==} + engines: {node: '>=14.0.0'} + cpu: [ia32] + os: [win32] + + sass-embedded-win32-x64@1.89.0: + resolution: {integrity: sha512-AaGpr5R6MLCuSvkvDdRq49ebifwLcuGPk0/10hbYw9nh3jpy2/CylYubQpIpR4yPcuD1wFwFqufTXC3HJYGb0g==} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [win32] + + sass-embedded@1.89.0: + resolution: {integrity: sha512-EDrK1el9zdgJFpocCGlxatDWaP18tJBWoM1hxzo2KJBvjdmBichXI6O6KlQrigvQPO3uJ8DfmFmAAx7s7CG6uw==} + engines: {node: '>=16.0.0'} + hasBin: true + + sass-formatter@0.7.9: + resolution: {integrity: sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==} + + sass@1.89.2: + resolution: {integrity: sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==} + engines: {node: '>=14.0.0'} + hasBin: true + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + scroll-into-view-if-needed@2.2.31: + resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} + + semantic-ui-offline@2.5.0: + resolution: {integrity: sha512-Fldx3SfaVtWx5EeCb/5EiJwYkzrGbtsAwVs02xLkeV5z5l8GJmplWEVOeJVjbEpmyiwPWp7cA48JwT5RjbWBVA==} + + semantic-ui-react@2.1.5: + resolution: {integrity: sha512-nIqmmUNpFHfovEb+RI2w3E2/maZQutd8UIWyRjf1SLse+XF51hI559xbz/sLN3O6RpLjr/echLOOXwKCirPy3Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + shallowequal@1.1.0: + resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + + shapefile@0.6.6: + resolution: {integrity: sha512-rLGSWeK2ufzCVx05wYd+xrWnOOdSV7xNUW5/XFgx3Bc02hBkpMlrd2F1dDII7/jhWzv0MSyBFh5uJIy9hLdfuw==} + hasBin: true + + simple-statistics@7.8.8: + resolution: {integrity: sha512-CUtP0+uZbcbsFpqEyvNDYjJCl+612fNgjT8GaVuvMG7tBuJg8gXGpsP5M7X658zy0IcepWOZ6nPBu1Qb9ezA1w==} + + simplify-geojson@1.0.5: + resolution: {integrity: sha512-02l1W4UipP5ivNVq6kX15mAzCRIV1oI3tz0FUEyOsNiv1ltuFDjbNhO+nbv/xhbDEtKqWLYuzpWhUsJrjR/ypA==} + hasBin: true + + simplify-geometry@0.0.2: + resolution: {integrity: sha512-ZEyrplkqgCqDlL7V8GbbYgTLlcnNF+MWWUdy8s8ZeJru50bnI71rDew/I+HG36QS2mPOYAq1ZjwNXxHJ8XOVBw==} + + slice-source@0.4.1: + resolution: {integrity: sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==} + + sort-object-keys@1.1.3: + resolution: {integrity: sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==} + + sort-order@1.1.2: + resolution: {integrity: sha512-Q8tOrwB1TSv9fNUXym9st3TZJODtmcOIi2JWCkVNQPrRg17KPwlpwweTEb7pMwUIFMTAgx2/JsQQXEPFzYQj3A==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + sse.js@https://codeload.github.com/mpetazzoni/sse.js/tar.gz/72cea4849a6da65f526b693b4145fdaec866720f: + resolution: {tarball: https://codeload.github.com/mpetazzoni/sse.js/tar.gz/72cea4849a6da65f526b693b4145fdaec866720f} + version: 2.7.1 + + stream-source@0.3.5: + resolution: {integrity: sha512-ZuEDP9sgjiAwUVoDModftG0JtYiLUV8K4ljYD1VyUMRWtbVf92474o4kuuul43iZ8t/hRuiDAx1dIJSvirrK/g==} + + string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + style-to-js@1.1.16: + resolution: {integrity: sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==} + + style-to-object@1.0.8: + resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==} + + suf-log@2.5.3: + resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + sync-child-process@1.0.2: + resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==} + engines: {node: '>=16.0.0'} + + sync-message-port@1.1.3: + resolution: {integrity: sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==} + engines: {node: '>=16.0.0'} + + text-encoding@0.6.4: + resolution: {integrity: sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==} + deprecated: no longer maintained + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + topojson-client@3.1.0: + resolution: {integrity: sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==} + hasBin: true + + topojson-server@3.0.1: + resolution: {integrity: sha512-/VS9j/ffKr2XAOjlZ9CgyyeLmgJ9dMwq6Y0YEON8O7p/tGGk+dCWnrE03zEdu7i4L7YsFZLEPZPzCvcB7lEEXw==} + hasBin: true + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typedarray@0.0.7: + resolution: {integrity: sha512-ueeb9YybpjhivjbHP2LdFDAjbS948fGEPj+ACAMs4xCMmh72OCOMQWBQKlaN4ZNQ04yfLSDLSx1tGRIoWimObQ==} + + typescript@4.4.2: + resolution: {integrity: sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==} + engines: {node: '>=4.2.0'} + hasBin: true + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utility-types@3.11.0: + resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} + engines: {node: '>= 4'} + + varint@6.0.0: + resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} + + vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite-plugin-semi-theme@0.6.0: + resolution: {integrity: sha512-xAhbbHKHCmc5+d++dt2FTXSQMf3C+1b2ZayP9CgFJq42cLnWmRxUbGBXiZQ/j8WSJvQwbhnUfyto7iyszPubaQ==} + + vite@5.4.16: + resolution: {integrity: sha512-Y5gnfp4NemVfgOTDQAunSD4346fal44L9mszGGY/e+qxsRT5y1sMlS/8tiQ8AFAp+MFgYNSINdfEchJiPm41vQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + + warning@4.0.3: + resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@astrojs/compiler@2.11.0': {} + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.8': {} + + '@babel/core@7.26.10': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helpers': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + convert-source-map: 2.0.0 + debug: 4.4.0 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.27.0': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.0': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.26.5': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.27.0': + dependencies: + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + + '@babel/parser@7.27.0': + dependencies: + '@babel/types': 7.27.0 + + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/runtime@7.27.0': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + + '@babel/traverse@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + debug: 4.4.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.27.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@bufbuild/protobuf@2.4.0': {} + + '@dnd-kit/accessibility@3.1.1(react@18.3.1)': + dependencies: + react: 18.3.1 + tslib: 2.8.1 + + '@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 + + '@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + react: 18.3.1 + tslib: 2.8.1 + + '@dnd-kit/utilities@3.2.2(react@18.3.1)': + dependencies: + react: 18.3.1 + tslib: 2.8.1 + + '@douyinfe/semi-animation-react@2.77.1': + dependencies: + '@douyinfe/semi-animation': 2.77.1 + '@douyinfe/semi-animation-styled': 2.77.1 + classnames: 2.5.1 + + '@douyinfe/semi-animation-styled@2.77.1': {} + + '@douyinfe/semi-animation@2.77.1': + dependencies: + bezier-easing: 2.1.0 + + '@douyinfe/semi-foundation@2.77.1(acorn@8.14.1)': + dependencies: + '@douyinfe/semi-animation': 2.77.1 + '@douyinfe/semi-json-viewer-core': 2.77.1 + '@mdx-js/mdx': 3.1.0(acorn@8.14.1) + async-validator: 3.5.2 + classnames: 2.5.1 + date-fns: 2.30.0 + date-fns-tz: 1.3.8(date-fns@2.30.0) + fast-copy: 3.0.2 + lodash: 4.17.21 + lottie-web: 5.12.2 + memoize-one: 5.2.1 + prismjs: 1.30.0 + remark-gfm: 4.0.1 + scroll-into-view-if-needed: 2.2.31 + transitivePeerDependencies: + - acorn + - supports-color + + '@douyinfe/semi-icons@2.77.1(react@18.3.1)': + dependencies: + classnames: 2.5.1 + react: 18.3.1 + + '@douyinfe/semi-illustrations@2.77.1(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@douyinfe/semi-json-viewer-core@2.77.1': + dependencies: + jsonc-parser: 3.3.1 + + '@douyinfe/semi-theme-default@2.77.1': {} + + '@douyinfe/semi-ui@2.77.1(acorn@8.14.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/sortable': 7.0.2(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + '@douyinfe/semi-animation': 2.77.1 + '@douyinfe/semi-animation-react': 2.77.1 + '@douyinfe/semi-foundation': 2.77.1(acorn@8.14.1) + '@douyinfe/semi-icons': 2.77.1(react@18.3.1) + '@douyinfe/semi-illustrations': 2.77.1(react@18.3.1) + '@douyinfe/semi-theme-default': 2.77.1 + async-validator: 3.5.2 + classnames: 2.5.1 + copy-text-to-clipboard: 2.2.0 + date-fns: 2.30.0 + date-fns-tz: 1.3.8(date-fns@2.30.0) + fast-copy: 3.0.2 + jsonc-parser: 3.3.1 + lodash: 4.17.21 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-resizable: 3.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-window: 1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + scroll-into-view-if-needed: 2.2.31 + utility-types: 3.11.0 + transitivePeerDependencies: + - acorn + - supports-color + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@fluentui/react-component-event-listener@0.63.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@fluentui/react-component-ref@0.63.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 16.13.1 + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@mdx-js/mdx@3.1.0(acorn@8.14.1)': + dependencies: + '@types/estree': 1.0.7 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.0(acorn@8.14.1) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.0 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.4 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - acorn + - supports-color + + '@parcel/watcher-android-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + optional: true + + '@popperjs/core@2.11.8': {} + + '@remix-run/router@1.23.0': {} + + '@resvg/resvg-js-android-arm-eabi@2.4.1': + optional: true + + '@resvg/resvg-js-android-arm64@2.4.1': + optional: true + + '@resvg/resvg-js-darwin-arm64@2.4.1': + optional: true + + '@resvg/resvg-js-darwin-x64@2.4.1': + optional: true + + '@resvg/resvg-js-linux-arm-gnueabihf@2.4.1': + optional: true + + '@resvg/resvg-js-linux-arm64-gnu@2.4.1': + optional: true + + '@resvg/resvg-js-linux-arm64-musl@2.4.1': + optional: true + + '@resvg/resvg-js-linux-x64-gnu@2.4.1': + optional: true + + '@resvg/resvg-js-linux-x64-musl@2.4.1': + optional: true + + '@resvg/resvg-js-win32-arm64-msvc@2.4.1': + optional: true + + '@resvg/resvg-js-win32-ia32-msvc@2.4.1': + optional: true + + '@resvg/resvg-js-win32-x64-msvc@2.4.1': + optional: true + + '@resvg/resvg-js@2.4.1': + optionalDependencies: + '@resvg/resvg-js-android-arm-eabi': 2.4.1 + '@resvg/resvg-js-android-arm64': 2.4.1 + '@resvg/resvg-js-darwin-arm64': 2.4.1 + '@resvg/resvg-js-darwin-x64': 2.4.1 + '@resvg/resvg-js-linux-arm-gnueabihf': 2.4.1 + '@resvg/resvg-js-linux-arm64-gnu': 2.4.1 + '@resvg/resvg-js-linux-arm64-musl': 2.4.1 + '@resvg/resvg-js-linux-x64-gnu': 2.4.1 + '@resvg/resvg-js-linux-x64-musl': 2.4.1 + '@resvg/resvg-js-win32-arm64-msvc': 2.4.1 + '@resvg/resvg-js-win32-ia32-msvc': 2.4.1 + '@resvg/resvg-js-win32-x64-msvc': 2.4.1 + + '@rollup/rollup-android-arm-eabi@4.39.0': + optional: true + + '@rollup/rollup-android-arm64@4.39.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.39.0': + optional: true + + '@rollup/rollup-darwin-x64@4.39.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.39.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.39.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.39.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.39.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.39.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.39.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.39.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.39.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.39.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.39.0': + optional: true + + '@semantic-ui-react/event-stack@3.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + exenv: 1.2.2 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@semi-bot/semi-theme-veloera-theme@1.0.0': {} + + '@so1ve/prettier-config@3.1.0(prettier@3.5.3)': + dependencies: + '@so1ve/prettier-plugin-toml': 3.1.0(prettier@3.5.3) + prettier: 3.5.3 + prettier-plugin-astro: 0.14.1 + prettier-plugin-curly-and-jsdoc: 3.1.0(prettier@3.5.3) + prettier-plugin-pkgsort: 0.2.1(prettier@3.5.3) + + '@so1ve/prettier-plugin-toml@3.1.0(prettier@3.5.3)': + dependencies: + prettier: 3.5.3 + + '@turf/boolean-clockwise@6.5.0': + dependencies: + '@turf/helpers': 6.5.0 + '@turf/invariant': 6.5.0 + + '@turf/clone@6.5.0': + dependencies: + '@turf/helpers': 6.5.0 + + '@turf/flatten@6.5.0': + dependencies: + '@turf/helpers': 6.5.0 + '@turf/meta': 6.5.0 + + '@turf/helpers@6.5.0': {} + + '@turf/invariant@6.5.0': + dependencies: + '@turf/helpers': 6.5.0 + + '@turf/meta@3.14.0': {} + + '@turf/meta@6.5.0': + dependencies: + '@turf/helpers': 6.5.0 + + '@turf/rewind@6.5.0': + dependencies: + '@turf/boolean-clockwise': 6.5.0 + '@turf/clone': 6.5.0 + '@turf/helpers': 6.5.0 + '@turf/invariant': 6.5.0 + '@turf/meta': 6.5.0 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.7 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.27.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + + '@types/babel__traverse@7.20.7': + dependencies: + '@babel/types': 7.27.0 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.7 + + '@types/estree@1.0.7': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.13': {} + + '@types/ms@2.1.0': {} + + '@types/parse-author@2.0.3': {} + + '@types/parse-json@4.0.2': {} + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.0': {} + + '@visactor/react-vchart@1.13.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@visactor/vchart': 1.13.13 + '@visactor/vgrammar-core': 0.16.10 + '@visactor/vrender-core': 0.22.15 + '@visactor/vrender-kits': 0.22.15 + '@visactor/vutils': 0.19.7 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + + '@visactor/vchart-semi-theme@1.8.8(@visactor/vchart@1.13.13)': + dependencies: + '@visactor/vchart': 1.13.13 + '@visactor/vchart-theme-utils': 1.8.8(@visactor/vchart@1.13.13) + + '@visactor/vchart-theme-utils@1.8.8(@visactor/vchart@1.13.13)': + dependencies: + '@visactor/vchart': 1.13.13 + + '@visactor/vchart@1.13.13': + dependencies: + '@visactor/vdataset': 0.19.7 + '@visactor/vgrammar-core': 0.16.10 + '@visactor/vgrammar-hierarchy': 0.16.10 + '@visactor/vgrammar-projection': 0.16.10 + '@visactor/vgrammar-sankey': 0.16.10 + '@visactor/vgrammar-util': 0.16.10 + '@visactor/vgrammar-venn': 0.16.10 + '@visactor/vgrammar-wordcloud': 0.16.10 + '@visactor/vgrammar-wordcloud-shape': 0.16.10 + '@visactor/vrender-components': 0.22.15 + '@visactor/vrender-core': 0.22.15 + '@visactor/vrender-kits': 0.22.15 + '@visactor/vscale': 0.19.7 + '@visactor/vutils': 0.19.7 + '@visactor/vutils-extension': 1.13.13 + + '@visactor/vdataset@0.19.7': + dependencies: + '@turf/flatten': 6.5.0 + '@turf/helpers': 6.5.0 + '@turf/rewind': 6.5.0 + '@visactor/vutils': 0.19.7 + d3-dsv: 2.0.0 + d3-geo: 1.12.1 + d3-hexbin: 0.2.2 + d3-hierarchy: 3.1.2 + eventemitter3: 4.0.7 + geobuf: 3.0.2 + geojson-dissolve: 3.1.0 + path-browserify: 1.0.1 + pbf: 3.3.0 + point-at-length: 1.1.0 + simple-statistics: 7.8.8 + simplify-geojson: 1.0.5 + topojson-client: 3.1.0 + + '@visactor/vgrammar-coordinate@0.16.10': + dependencies: + '@visactor/vgrammar-util': 0.16.10 + '@visactor/vutils': 0.19.7 + + '@visactor/vgrammar-core@0.16.10': + dependencies: + '@visactor/vdataset': 0.19.7 + '@visactor/vgrammar-coordinate': 0.16.10 + '@visactor/vgrammar-util': 0.16.10 + '@visactor/vrender-components': 0.22.15 + '@visactor/vrender-core': 0.22.15 + '@visactor/vrender-kits': 0.22.15 + '@visactor/vscale': 0.19.7 + '@visactor/vutils': 0.19.7 + + '@visactor/vgrammar-hierarchy@0.16.10': + dependencies: + '@visactor/vgrammar-core': 0.16.10 + '@visactor/vgrammar-util': 0.16.10 + '@visactor/vrender-core': 0.22.15 + '@visactor/vrender-kits': 0.22.15 + '@visactor/vutils': 0.19.7 + + '@visactor/vgrammar-projection@0.16.10': + dependencies: + '@visactor/vgrammar-core': 0.16.10 + '@visactor/vgrammar-util': 0.16.10 + '@visactor/vutils': 0.19.7 + d3-geo: 1.12.1 + + '@visactor/vgrammar-sankey@0.16.10': + dependencies: + '@visactor/vgrammar-core': 0.16.10 + '@visactor/vgrammar-util': 0.16.10 + '@visactor/vrender-core': 0.22.15 + '@visactor/vrender-kits': 0.22.15 + '@visactor/vutils': 0.19.7 + + '@visactor/vgrammar-util@0.16.10': + dependencies: + '@visactor/vrender-core': 0.22.15 + '@visactor/vutils': 0.19.7 + + '@visactor/vgrammar-venn@0.16.10': + dependencies: + '@visactor/vgrammar-core': 0.16.10 + '@visactor/vgrammar-util': 0.16.10 + '@visactor/vrender-core': 0.22.15 + '@visactor/vrender-kits': 0.22.15 + '@visactor/vutils': 0.19.7 + + '@visactor/vgrammar-wordcloud-shape@0.16.10': + dependencies: + '@visactor/vgrammar-core': 0.16.10 + '@visactor/vgrammar-util': 0.16.10 + '@visactor/vrender-core': 0.22.15 + '@visactor/vrender-kits': 0.22.15 + '@visactor/vscale': 0.19.7 + '@visactor/vutils': 0.19.7 + + '@visactor/vgrammar-wordcloud@0.16.10': + dependencies: + '@visactor/vgrammar-core': 0.16.10 + '@visactor/vgrammar-util': 0.16.10 + '@visactor/vrender-core': 0.22.15 + '@visactor/vrender-kits': 0.22.15 + '@visactor/vutils': 0.19.7 + + '@visactor/vrender-components@0.22.15': + dependencies: + '@visactor/vrender-core': 0.22.15 + '@visactor/vrender-kits': 0.22.15 + '@visactor/vscale': 0.19.7 + '@visactor/vutils': 0.19.7 + + '@visactor/vrender-core@0.22.15': + dependencies: + '@visactor/vutils': 0.19.7 + color-convert: 2.0.1 + + '@visactor/vrender-kits@0.22.15': + dependencies: + '@resvg/resvg-js': 2.4.1 + '@visactor/vrender-core': 0.22.15 + '@visactor/vutils': 0.19.7 + gifuct-js: 2.1.2 + lottie-web: 5.12.2 + roughjs: 4.5.2 + + '@visactor/vscale@0.19.7': + dependencies: + '@visactor/vutils': 0.19.7 + + '@visactor/vutils-extension@1.13.13': + dependencies: + '@visactor/vdataset': 0.19.7 + '@visactor/vutils': 0.19.7 + + '@visactor/vutils@0.19.7': + dependencies: + '@turf/helpers': 6.5.0 + '@turf/invariant': 6.5.0 + eventemitter3: 4.0.7 + + '@vitejs/plugin-react@4.3.4(vite@5.4.16(sass-embedded@1.89.0)(sass@1.89.2))': + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.4.16(sass-embedded@1.89.0)(sass@1.89.2) + transitivePeerDependencies: + - supports-color + + abs-svg-path@0.1.1: {} + + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + + acorn@8.14.1: {} + + array-source@0.0.4: {} + + astring@1.9.0: {} + + async-validator@3.5.2: {} + + asynckit@0.4.0: {} + + attr-accept@2.2.5: {} + + author-regex@1.0.0: {} + + axios@1.12.0: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.4 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + bail@2.0.2: {} + + balanced-match@1.0.2: {} + + bezier-easing@2.1.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + optional: true + + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001709 + electron-to-chromium: 1.5.130 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) + + buffer-builder@0.2.0: {} + + buffer-from@1.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001709: {} + + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + classnames@2.5.1: {} + + clsx@1.2.1: {} + + collapse-white-space@2.1.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorjs.io@0.5.2: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + comma-separated-tokens@2.0.3: {} + + commander@2.20.3: {} + + commander@4.1.1: {} + + compute-scroll-into-view@1.0.20: {} + + concat-map@0.0.1: {} + + concat-stream@1.4.11: + dependencies: + inherits: 2.0.4 + readable-stream: 1.1.14 + typedarray: 0.0.7 + + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + + convert-source-map@2.0.0: {} + + copy-text-to-clipboard@2.2.0: {} + + core-util-is@1.0.3: {} + + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + + d3-array@1.2.4: {} + + d3-dsv@2.0.0: + dependencies: + commander: 2.20.3 + iconv-lite: 0.4.24 + rw: 1.3.3 + + d3-geo@1.12.1: + dependencies: + d3-array: 1.2.4 + + d3-hexbin@0.2.2: {} + + d3-hierarchy@3.1.2: {} + + date-fns-tz@1.3.8(date-fns@2.30.0): + dependencies: + date-fns: 2.30.0 + + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.27.0 + + dayjs@1.11.13: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + decode-named-character-reference@1.1.0: + dependencies: + character-entities: 2.0.2 + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-libc@1.0.3: + optional: true + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.130: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.14.1 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.2 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-string-regexp@5.0.0: {} + + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.7 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.7 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.4 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.7 + + eventemitter3@4.0.7: {} + + exenv@1.2.2: {} + + extend@3.0.2: {} + + fast-copy@3.0.2: {} + + file-selector@2.1.2: + dependencies: + tslib: 2.8.1 + + file-source@0.6.1: + dependencies: + stream-source: 0.3.5 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + optional: true + + follow-redirects@1.15.11: {} + + form-data@4.0.4: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-extra@4.0.3: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + geobuf@3.0.2: + dependencies: + concat-stream: 2.0.0 + pbf: 3.3.0 + shapefile: 0.6.6 + + geojson-dissolve@3.1.0: + dependencies: + '@turf/meta': 3.14.0 + geojson-flatten: 0.2.4 + geojson-linestring-dissolve: 0.0.1 + topojson-client: 3.1.0 + topojson-server: 3.0.1 + + geojson-flatten@0.2.4: + dependencies: + get-stdin: 6.0.0 + minimist: 1.2.0 + + geojson-linestring-dissolve@0.0.1: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stdin@6.0.0: {} + + gifuct-js@2.1.2: + dependencies: + js-binary-schema-parser: 2.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@11.12.0: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.7 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.0.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.16 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.7 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.0.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.16 + unist-util-position: 5.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + history@5.3.0: + dependencies: + '@babel/runtime': 7.27.0 + + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + + i18next-browser-languagedetector@7.2.2: + dependencies: + '@babel/runtime': 7.27.0 + + i18next@23.16.8: + dependencies: + '@babel/runtime': 7.27.0 + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + immutable@5.1.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + inline-style-parser@0.2.4: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-arrayish@0.2.1: {} + + is-decimal@2.0.1: {} + + is-extglob@2.1.1: + optional: true + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + optional: true + + is-hexadecimal@2.0.1: {} + + is-number@7.0.0: + optional: true + + is-plain-obj@4.1.0: {} + + isarray@0.0.1: {} + + jquery@3.7.1: {} + + js-binary-schema-parser@2.0.3: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json-parse-even-better-errors@2.3.1: {} + + json5@2.2.3: {} + + jsonc-parser@3.3.1: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyboard-key@1.1.0: {} + + lines-and-columns@1.2.4: {} + + lodash-es@4.17.21: {} + + lodash@4.17.21: {} + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lottie-web@5.12.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + markdown-extensions@2.0.0: {} + + markdown-table@3.0.4: {} + + marked@4.3.0: {} + + math-intrinsics@1.1.0: {} + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + memoize-one@5.2.1: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.7 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.7 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.2 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.7 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.2 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.7 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.7 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.2 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.0 + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + optional: true + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimist@1.2.0: {} + + minimist@1.2.6: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + node-addon-api@7.1.1: + optional: true + + node-releases@2.0.19: {} + + object-assign@4.1.1: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-author@2.0.0: + dependencies: + author-regex: 1.0.0 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.1.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.26.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-svg-path@0.1.2: {} + + path-browserify@1.0.1: {} + + path-data-parser@0.1.0: {} + + path-is-absolute@1.0.1: {} + + path-source@0.1.3: + dependencies: + array-source: 0.0.4 + file-source: 0.6.1 + + path-type@4.0.0: {} + + pbf@3.3.0: + dependencies: + ieee754: 1.2.1 + resolve-protobuf-schema: 2.1.0 + + picocolors@1.1.1: {} + + picomatch@2.3.1: + optional: true + + point-at-length@1.1.0: + dependencies: + abs-svg-path: 0.1.1 + isarray: 0.0.1 + parse-svg-path: 0.1.2 + + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + + postcss@8.5.3: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier-package-json@2.8.0: + dependencies: + '@types/parse-author': 2.0.3 + commander: 4.1.1 + cosmiconfig: 7.1.0 + fs-extra: 10.1.0 + glob: 7.2.3 + minimatch: 3.1.2 + parse-author: 2.0.0 + sort-object-keys: 1.1.3 + sort-order: 1.1.2 + + prettier-plugin-astro@0.14.1: + dependencies: + '@astrojs/compiler': 2.11.0 + prettier: 3.5.3 + sass-formatter: 0.7.9 + + prettier-plugin-curly-and-jsdoc@3.1.0(prettier@3.5.3): + dependencies: + prettier: 3.5.3 + + prettier-plugin-pkgsort@0.2.1(prettier@3.5.3): + dependencies: + prettier: 3.5.3 + prettier-package-json: 2.8.0 + + prettier@3.5.3: {} + + prismjs@1.30.0: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + property-information@7.0.0: {} + + protocol-buffers-schema@3.6.0: {} + + proxy-from-env@1.1.0: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-draggable@4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-dropzone@14.3.8(react@18.3.1): + dependencies: + attr-accept: 2.2.5 + file-selector: 2.1.2 + prop-types: 15.8.1 + react: 18.3.1 + + react-fast-compare@3.2.2: {} + + react-fireworks@1.0.4: {} + + react-i18next@13.5.0(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.0 + html-parse-stringify: 3.0.1 + i18next: 23.16.8 + react: 18.3.1 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + + react-is@16.13.1: {} + + react-is@18.3.1: {} + + react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@popperjs/core': 2.11.8 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-fast-compare: 3.2.2 + warning: 4.0.3 + + react-refresh@0.14.2: {} + + react-resizable@3.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + prop-types: 15.8.1 + react: 18.3.1 + react-draggable: 4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + transitivePeerDependencies: + - react-dom + + react-router-dom@6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.30.0(react@18.3.1) + + react-router@6.30.0(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.0 + react: 18.3.1 + + react-telegram-login@1.1.2(react@18.3.1): + dependencies: + react: 18.3.1 + + react-toastify@9.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + clsx: 1.2.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-turnstile@1.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-window@1.8.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.0 + memoize-one: 5.2.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + readable-stream@1.1.14: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@4.1.2: {} + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.7 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.0(acorn@8.14.1): + dependencies: + acorn-jsx: 5.3.2(acorn@8.14.1) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - acorn + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.7 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.7 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + regenerator-runtime@0.14.1: {} + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.7 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.0: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.0 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + resolve-from@4.0.0: {} + + resolve-protobuf-schema@2.1.0: + dependencies: + protocol-buffers-schema: 3.6.0 + + rollup@4.39.0: + dependencies: + '@types/estree': 1.0.7 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.39.0 + '@rollup/rollup-android-arm64': 4.39.0 + '@rollup/rollup-darwin-arm64': 4.39.0 + '@rollup/rollup-darwin-x64': 4.39.0 + '@rollup/rollup-freebsd-arm64': 4.39.0 + '@rollup/rollup-freebsd-x64': 4.39.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.39.0 + '@rollup/rollup-linux-arm-musleabihf': 4.39.0 + '@rollup/rollup-linux-arm64-gnu': 4.39.0 + '@rollup/rollup-linux-arm64-musl': 4.39.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.39.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-musl': 4.39.0 + '@rollup/rollup-linux-s390x-gnu': 4.39.0 + '@rollup/rollup-linux-x64-gnu': 4.39.0 + '@rollup/rollup-linux-x64-musl': 4.39.0 + '@rollup/rollup-win32-arm64-msvc': 4.39.0 + '@rollup/rollup-win32-ia32-msvc': 4.39.0 + '@rollup/rollup-win32-x64-msvc': 4.39.0 + fsevents: 2.3.3 + + roughjs@4.5.2: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + + rw@1.3.3: {} + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + s.color@0.0.15: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + sass-embedded-android-arm64@1.89.0: + optional: true + + sass-embedded-android-arm@1.89.0: + optional: true + + sass-embedded-android-ia32@1.89.0: + optional: true + + sass-embedded-android-riscv64@1.89.0: + optional: true + + sass-embedded-android-x64@1.89.0: + optional: true + + sass-embedded-darwin-arm64@1.89.0: + optional: true + + sass-embedded-darwin-x64@1.89.0: + optional: true + + sass-embedded-linux-arm64@1.89.0: + optional: true + + sass-embedded-linux-arm@1.89.0: + optional: true + + sass-embedded-linux-ia32@1.89.0: + optional: true + + sass-embedded-linux-musl-arm64@1.89.0: + optional: true + + sass-embedded-linux-musl-arm@1.89.0: + optional: true + + sass-embedded-linux-musl-ia32@1.89.0: + optional: true + + sass-embedded-linux-musl-riscv64@1.89.0: + optional: true + + sass-embedded-linux-musl-x64@1.89.0: + optional: true + + sass-embedded-linux-riscv64@1.89.0: + optional: true + + sass-embedded-linux-x64@1.89.0: + optional: true + + sass-embedded-win32-arm64@1.89.0: + optional: true + + sass-embedded-win32-ia32@1.89.0: + optional: true + + sass-embedded-win32-x64@1.89.0: + optional: true + + sass-embedded@1.89.0: + dependencies: + '@bufbuild/protobuf': 2.4.0 + buffer-builder: 0.2.0 + colorjs.io: 0.5.2 + immutable: 5.1.2 + rxjs: 7.8.2 + supports-color: 8.1.1 + sync-child-process: 1.0.2 + varint: 6.0.0 + optionalDependencies: + sass-embedded-android-arm: 1.89.0 + sass-embedded-android-arm64: 1.89.0 + sass-embedded-android-ia32: 1.89.0 + sass-embedded-android-riscv64: 1.89.0 + sass-embedded-android-x64: 1.89.0 + sass-embedded-darwin-arm64: 1.89.0 + sass-embedded-darwin-x64: 1.89.0 + sass-embedded-linux-arm: 1.89.0 + sass-embedded-linux-arm64: 1.89.0 + sass-embedded-linux-ia32: 1.89.0 + sass-embedded-linux-musl-arm: 1.89.0 + sass-embedded-linux-musl-arm64: 1.89.0 + sass-embedded-linux-musl-ia32: 1.89.0 + sass-embedded-linux-musl-riscv64: 1.89.0 + sass-embedded-linux-musl-x64: 1.89.0 + sass-embedded-linux-riscv64: 1.89.0 + sass-embedded-linux-x64: 1.89.0 + sass-embedded-win32-arm64: 1.89.0 + sass-embedded-win32-ia32: 1.89.0 + sass-embedded-win32-x64: 1.89.0 + + sass-formatter@0.7.9: + dependencies: + suf-log: 2.5.3 + + sass@1.89.2: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.2 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.1 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + scroll-into-view-if-needed@2.2.31: + dependencies: + compute-scroll-into-view: 1.0.20 + + semantic-ui-offline@2.5.0: + dependencies: + fs-extra: 4.0.3 + jquery: 3.7.1 + + semantic-ui-react@2.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.0 + '@fluentui/react-component-event-listener': 0.63.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@fluentui/react-component-ref': 0.63.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@popperjs/core': 2.11.8 + '@semantic-ui-react/event-stack': 3.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + clsx: 1.2.1 + keyboard-key: 1.1.0 + lodash: 4.17.21 + lodash-es: 4.17.21 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + shallowequal: 1.1.0 + + semver@6.3.1: {} + + shallowequal@1.1.0: {} + + shapefile@0.6.6: + dependencies: + array-source: 0.0.4 + commander: 2.20.3 + path-source: 0.1.3 + slice-source: 0.4.1 + stream-source: 0.3.5 + text-encoding: 0.6.4 + + simple-statistics@7.8.8: {} + + simplify-geojson@1.0.5: + dependencies: + concat-stream: 1.4.11 + minimist: 1.2.6 + simplify-geometry: 0.0.2 + + simplify-geometry@0.0.2: {} + + slice-source@0.4.1: {} + + sort-object-keys@1.1.3: {} + + sort-order@1.1.2: {} + + source-map-js@1.2.1: {} + + source-map@0.7.4: {} + + space-separated-tokens@2.0.2: {} + + sse.js@https://codeload.github.com/mpetazzoni/sse.js/tar.gz/72cea4849a6da65f526b693b4145fdaec866720f: {} + + stream-source@0.3.5: {} + + string_decoder@0.10.31: {} + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + style-to-js@1.1.16: + dependencies: + style-to-object: 1.0.8 + + style-to-object@1.0.8: + dependencies: + inline-style-parser: 0.2.4 + + suf-log@2.5.3: + dependencies: + s.color: 0.0.15 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + sync-child-process@1.0.2: + dependencies: + sync-message-port: 1.1.3 + + sync-message-port@1.1.3: {} + + text-encoding@0.6.4: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + optional: true + + topojson-client@3.1.0: + dependencies: + commander: 2.20.3 + + topojson-server@3.0.1: + dependencies: + commander: 2.20.3 + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + tslib@2.8.1: {} + + typedarray@0.0.6: {} + + typedarray@0.0.7: {} + + typescript@4.4.2: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + update-browserslist-db@1.1.3(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + util-deprecate@1.0.2: {} + + utility-types@3.11.0: {} + + varint@6.0.0: {} + + vfile-message@4.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + + vite-plugin-semi-theme@0.6.0: + dependencies: + sass: 1.89.2 + + vite@5.4.16(sass-embedded@1.89.0)(sass@1.89.2): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.3 + rollup: 4.39.0 + optionalDependencies: + fsevents: 2.3.3 + sass: 1.89.2 + sass-embedded: 1.89.0 + + void-elements@3.1.0: {} + + warning@4.0.3: + dependencies: + loose-envify: 1.4.0 + + wrappy@1.0.2: {} + + yallist@3.1.1: {} + + yaml@1.10.2: {} + + zwitch@2.0.4: {} diff --git a/web/public/favicon.ico b/web/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0c10d1e5bbfd52b7690008262cec4b7cae7e0a48 Binary files /dev/null and b/web/public/favicon.ico differ diff --git a/web/public/logo.png b/web/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8aea273d4c9cf94636ddc350337516501c6e2e89 Binary files /dev/null and b/web/public/logo.png differ diff --git a/web/public/powered_by.svg b/web/public/powered_by.svg new file mode 100644 index 0000000000000000000000000000000000000000..41e79c7bc4a62c0c98035d9ab2c578673d3cd33a --- /dev/null +++ b/web/public/powered_by.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + Powered by + + + Veloera + + + + + + \ No newline at end of file diff --git a/web/public/ratio.png b/web/public/ratio.png new file mode 100644 index 0000000000000000000000000000000000000000..fbda9172364846709cc203139b595f2ab2dbdc19 --- /dev/null +++ b/web/public/ratio.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61cc0ee3c629d7779b7752c152b627a136fac1dab28e1ce2f4c355c195245fe6 +size 143438 diff --git a/web/public/robots.txt b/web/public/robots.txt new file mode 100644 index 0000000000000000000000000000000000000000..e9e57dc4d41b9b46e05112e9f45b7ea6ac0ba15e --- /dev/null +++ b/web/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/web/src/App.js b/web/src/App.js new file mode 100644 index 0000000000000000000000000000000000000000..3f50645eeeb028f09e29e60f3b8c72fc5277810b --- /dev/null +++ b/web/src/App.js @@ -0,0 +1,366 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { lazy, Suspense, useContext, useEffect } from 'react'; +import { Route, Routes, useLocation } from 'react-router-dom'; +import Loading from './components/Loading'; +import User from './pages/User'; +import { PrivateRoute } from './components/PrivateRoute'; +import RegisterForm from './components/RegisterForm'; +import LoginForm from './components/LoginForm'; +import NotFound from './pages/NotFound'; +import Setting from './pages/Setting'; +import EditUser from './pages/User/EditUser'; +import PasswordResetForm from './components/PasswordResetForm'; +import PasswordResetConfirm from './components/PasswordResetConfirm'; +import Channel from './pages/Channel'; +import Token from './pages/Token'; +import EditChannel from './pages/Channel/EditChannel'; +import Redemption from './pages/Redemption'; +import TopUp from './pages/TopUp'; +import TopUpSuccess from './pages/TopUp/TopUpSuccess'; +import Log from './pages/Log'; +import Chat from './pages/Chat'; +import Chat2Link from './pages/Chat2Link'; +import { Layout } from '@douyinfe/semi-ui'; +import Midjourney from './pages/Midjourney'; +import Pricing from './pages/Pricing/index.js'; +import Task from './pages/Task/index.js'; +import Playground from './pages/Playground/Playground.js'; +import OAuth2Callback from './components/OAuth2Callback.js'; +import PersonalSetting from './components/PersonalSetting.js'; +import Setup from './pages/Setup/index.js'; +import SetupCheck from './components/SetupCheck'; +import Inbox from './pages/Inbox'; +import Message from './pages/Message'; +import { AdminRoute } from './components/AdminRoute'; + +const Home = lazy(() => import('./pages/Home')); +const Detail = lazy(() => import('./pages/Detail')); +const About = lazy(() => import('./pages/About')); + +function App() { + const location = useLocation(); + + return ( + + + } key={location.pathname}> + + + } + /> + } key={location.pathname}> + + + } + /> + + + + } + /> + } key={location.pathname}> + + + } + /> + } key={location.pathname}> + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + } key={location.pathname}> + + + } + /> + } key={location.pathname}> + + + } + /> + } key={location.pathname}> + + + } + /> + } key={location.pathname}> + + + } + /> + } key={location.pathname}> + + + } + /> + } key={location.pathname}> + + + } + /> + } key={location.pathname}> + + + } + /> + }> + + + } + /> + } key={location.pathname}> + + + } + /> + } key={location.pathname}> + + + } + /> + + } key={location.pathname}> + + + + } + /> + + } key={location.pathname}> + + + + } + /> + + } key={location.pathname}> + + + + } + /> + + } key={location.pathname}> + + + + } + /> + + + + } + /> + + } key={location.pathname}> + + + + } + /> + + } key={location.pathname}> + + + + } + /> + + } key={location.pathname}> + + + + } + /> + } key={location.pathname}> + + + } + /> + } key={location.pathname}> + + + } + /> + } key={location.pathname}> + + + } + /> + {/* 方便使用chat2link直接跳转聊天... */} + + } key={location.pathname}> + + + + } + /> + {/* User inbox routes */} + + } key={location.pathname}> + + + + } + /> + + } key={location.pathname}> + + + + } + /> + {/* Admin message management routes */} + + } key={location.pathname}> + + + + } + /> + } /> + + + ); +} + +export default App; diff --git a/web/src/components/AdminRoute.js b/web/src/components/AdminRoute.js new file mode 100644 index 0000000000000000000000000000000000000000..117021395b3e6d375ef800d34f8cf146996afdf7 --- /dev/null +++ b/web/src/components/AdminRoute.js @@ -0,0 +1,35 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import { Navigate } from 'react-router-dom'; +import { isAdmin } from '../helpers/utils'; +import { history } from '../helpers'; + +function AdminRoute({ children }) { + if (!localStorage.getItem('user')) { + return ; + } + + if (!isAdmin()) { + return ; + } + + return children; +} + +export { AdminRoute }; \ No newline at end of file diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js new file mode 100644 index 0000000000000000000000000000000000000000..960550eeebe79efdc8b4de87258ca9ab1cef9dca --- /dev/null +++ b/web/src/components/ChannelsTable.js @@ -0,0 +1,2645 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; +import { + API, + copy, + isMobile, + shouldShowPrompt, + showError, + showInfo, + showSuccess, + showWarning, + timestamp2string, +} from '../helpers'; + +import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../constants'; +import { + getQuotaPerUnit, + renderGroup, + renderNumberWithPoint, + renderQuota, + renderQuotaWithPrompt, + stringToColor, +} from '../helpers/render'; +import { + Button, + Divider, + Dropdown, + Form, + Input, + InputNumber, + Modal, + Popconfirm, + Space, + SplitButtonGroup, + Switch, + Table, + Tag, + Tooltip, + Typography, + Checkbox, + Layout, +} from '@douyinfe/semi-ui'; +import EditChannel from '../pages/Channel/EditChannel'; +import { + IconList, + IconTreeTriangleDown, + IconClose, + IconFilter, + IconPlus, + IconRefresh, + IconSetting, + IconCopy, +} from '@douyinfe/semi-icons'; +import { loadChannelModels } from './utils.js'; +import EditTagModal from '../pages/Channel/EditTagModal.js'; +import TextNumberInput from './custom/TextNumberInput.js'; +import { useTranslation } from 'react-i18next'; + +function renderTimestamp(timestamp) { + return <>{timestamp2string(timestamp)}; +} + +// 辅助函数:获取失败的模型统计 +const getFailedModelsStats = (batchTestResults, currentModels = null) => { + // 如果提供了当前模型列表,只统计当前模型中的失败情况 + const currentModelSet = currentModels ? new Set(currentModels.split(',').map(m => m.trim()).filter(m => m)) : null; + + // 过滤测试结果:只保留当前模型中的结果 + const relevantResults = currentModelSet + ? batchTestResults.filter(r => currentModelSet.has(r.model)) + : batchTestResults; + + // 优化性能:只遍历一次,先获取所有失败的模型 + const failedModels = relevantResults.filter(r => !r.success); + // 然后从失败模型中过滤出可重试的,避免第二次完整遍历 + const retryableFailedModels = failedModels.filter(r => r.isRetryable); + + return { + failed: failedModels, + retryable: retryableFailedModels, + failedCount: failedModels.length, + retryableCount: retryableFailedModels.length + }; +}; + +// 辅助函数:处理模型列表数据 +const processModelData = ( + models, + searchKeyword, + batchTestResults, + isBatchTesting, + currentTestingModel, + testingModelsSet = null +) => { + if (!models) return []; + + // 优化性能:将测试结果转换为 Map,查找时间复杂度从 O(N) 降低到 O(1) + const testResultsMap = new Map(batchTestResults.map(result => [result.model, result])); + + return models + .split(',') + .map(model => model.trim()) + .filter(model => model && model.toLowerCase().includes(searchKeyword.toLowerCase())) + .map((model, index) => { + const testResult = testResultsMap.get(model); + return { + key: model, + model: model, + testResult: testResult, + isSuccess: testResult?.success, + isFailed: testResult && !testResult.success, + isTesting: isBatchTesting + ? currentTestingModel.includes(model) + : Boolean(testingModelsSet && testingModelsSet.has(model)), + isRetryable: testResult?.isRetryable + }; + }); +}; + +// 模型测试内容组件 +const ModelTestContent = ({ + channel, + isBatchTesting, + concurrentLimit, + setConcurrentLimit, + testAllModels, + modelSearchKeyword, + setModelSearchKeyword, + batchTestResults, + retryFailedModels, + handleDeleteFailedModels, + testProgress, + currentTestingModel, + testChannel, + showSuccess, + showError, + t +}) => { + const failedStats = getFailedModelsStats(batchTestResults, channel.models); + + // 追踪单个模型测试状态 + const [testingModels, setTestingModels] = React.useState(() => new Set()); + + // 单个模型测试函数 + const runSingleTest = async (model) => { + setTestingModels(prev => { + const next = new Set(prev); + next.add(model); + return next; + }); + try { + await testChannel(channel, model); + } finally { + setTestingModels(prev => { + const next = new Set(prev); + next.delete(model); + return next; + }); + } + }; + + // 使用 useMemo 缓存模型数据处理结果 + const modelData = React.useMemo(() => processModelData( + channel.models, + modelSearchKeyword, + batchTestResults, + isBatchTesting, + currentTestingModel, + testingModels + ), [channel.models, modelSearchKeyword, batchTestResults, isBatchTesting, currentTestingModel, testingModels]); + + return ( +
+ {/* 渠道信息和操作按钮 */} +
+ + {t('渠道')}: {channel.name} + + + {/* 主要操作按钮组 */} +
+ {!isBatchTesting && ( + <> + + {t('并发测试模型数')}: + + + + )} + + +
+
+ + {/* 搜索框和操作按钮 */} +
+ setModelSearchKeyword(v)} + prefix={} + showClear + style={{ flex: 1 }} + /> + + {/* 失败模型处理按钮组 */} + {batchTestResults.length > 0 && failedStats.failedCount > 0 && ( +
+ {failedStats.retryableCount > 0 && ( + + )} + + +
+ )} +
+ + {/* 测试进度显示 */} + {isBatchTesting && ( +
+
+ + {t('进度')}: {testProgress.completed}/{testProgress.total} + +
+
0 ? (testProgress.completed / testProgress.total) * 100 : 0}%`, + height: '100%', + backgroundColor: 'var(--semi-color-primary)', + borderRadius: '3px', + transition: 'width 0.3s ease' + }} /> +
+
+ {currentTestingModel && ( + + {t('正在测试')}: {currentTestingModel.includes(':') ? currentTestingModel.split(':').slice(1).join(':').trim() : currentTestingModel} + + )} +
+ )} + + {/* 模型测试表格 */} + ( + + {text} + + ) + }, + { + title: t('状态'), + key: 'status', + width: '20%', + render: (_, record) => { + if (record.isTesting) { + return {t('测试中')}; + } + if (record.testResult) { + return ( + + {record.isSuccess ? t('成功') : t('失败')} + + ); + } + return {t('未测试')}; + } + }, + { + title: t('耗时'), + key: 'duration', + width: '15%', + render: (_, record) => { + if (record.testResult?.time) { + return ( + + {record.testResult.time.toFixed(2)}s + + ); + } + return '-'; + } + }, + { + title: t('操作'), + key: 'actions', + width: '25%', + render: (_, record) => ( +
+ +
+ ) + } + ]} + pagination={false} + size='small' + style={{ marginBottom: '25px' }} + /> + + {/* 显示搜索结果数量 */} + {modelSearchKeyword && ( +
+ + {t('找到 {{count}} 个模型', { count: modelData.length })} + +
+ )} + + ); +}; + +const ChannelsTable = () => { + const { t } = useTranslation(); + + let type2label = undefined; + + const renderType = (type) => { + if (!type2label) { + type2label = new Map(); + for (let i = 0; i < CHANNEL_OPTIONS.length; i++) { + type2label[CHANNEL_OPTIONS[i].value] = CHANNEL_OPTIONS[i]; + } + type2label[0] = { value: 0, label: t('未知类型'), color: 'grey' }; + } + return ( + + {type2label[type]?.label} + + ); + }; + + const renderTagType = () => { + return ( + } + size='large' + shape='circle' + type='light' + > + {t('标签聚合')} + + ); + }; + + const renderStatus = (status) => { + switch (status) { + case 1: + return ( + + {t('已启用')} + + ); + case 2: + return ( + + {t('已禁用')} + + ); + case 3: + return ( + + {t('自动禁用')} + + ); + default: + return ( + + {t('未知状态')} + + ); + } + }; + + const renderResponseTime = (responseTime) => { + let time = responseTime / 1000; + time = time.toFixed(2) + t(' 秒'); + if (responseTime === 0) { + return ( + + {t('未测试')} + + ); + } else if (responseTime <= 1000) { + return ( + + {time} + + ); + } else if (responseTime <= 3000) { + return ( + + {time} + + ); + } else if (responseTime <= 5000) { + return ( + + {time} + + ); + } else { + return ( + + {time} + + ); + } + }; + + // Define column keys for selection + const COLUMN_KEYS = { + ID: 'id', + NAME: 'name', + GROUP: 'group', + TYPE: 'type', + STATUS: 'status', + RESPONSE_TIME: 'response_time', + BALANCE: 'balance', + PRIORITY: 'priority', + WEIGHT: 'weight', + OPERATE: 'operate', + }; + + // State for column visibility + const [visibleColumns, setVisibleColumns] = useState({}); + const [showColumnSelector, setShowColumnSelector] = useState(false); + + // Load saved column preferences from localStorage + useEffect(() => { + const savedColumns = localStorage.getItem('channels-table-columns'); + if (savedColumns) { + try { + const parsed = JSON.parse(savedColumns); + // Make sure all columns are accounted for + const defaults = getDefaultColumnVisibility(); + const merged = { ...defaults, ...parsed }; + setVisibleColumns(merged); + } catch (e) { + console.error('Failed to parse saved column preferences', e); + initDefaultColumns(); + } + } else { + initDefaultColumns(); + } + }, []); + + // Update table when column visibility changes + useEffect(() => { + if (Object.keys(visibleColumns).length > 0) { + // Save to localStorage + localStorage.setItem( + 'channels-table-columns', + JSON.stringify(visibleColumns), + ); + } + }, [visibleColumns]); + + // Get default column visibility + const getDefaultColumnVisibility = () => { + return { + [COLUMN_KEYS.ID]: true, + [COLUMN_KEYS.NAME]: true, + [COLUMN_KEYS.GROUP]: true, + [COLUMN_KEYS.TYPE]: true, + [COLUMN_KEYS.STATUS]: true, + [COLUMN_KEYS.RESPONSE_TIME]: true, + [COLUMN_KEYS.BALANCE]: true, + [COLUMN_KEYS.PRIORITY]: true, + [COLUMN_KEYS.WEIGHT]: true, + [COLUMN_KEYS.OPERATE]: true, + }; + }; + + // Initialize default column visibility + const initDefaultColumns = () => { + const defaults = getDefaultColumnVisibility(); + setVisibleColumns(defaults); + }; + + // Handle column visibility change + const handleColumnVisibilityChange = (columnKey, checked) => { + const updatedColumns = { ...visibleColumns, [columnKey]: checked }; + setVisibleColumns(updatedColumns); + }; + + // Handle "Select All" checkbox + const handleSelectAll = (checked) => { + const allKeys = Object.keys(COLUMN_KEYS).map((key) => COLUMN_KEYS[key]); + const updatedColumns = {}; + + allKeys.forEach((key) => { + updatedColumns[key] = checked; + }); + + setVisibleColumns(updatedColumns); + }; + + // Define all columns with keys + const allColumns = [ + { + key: COLUMN_KEYS.ID, + title: t('ID'), + dataIndex: 'id', + }, + { + key: COLUMN_KEYS.NAME, + title: t('名称'), + dataIndex: 'name', + }, + { + key: COLUMN_KEYS.GROUP, + title: t('分组'), + dataIndex: 'group', + render: (text, record, index) => { + return ( +
+ + {text + ?.split(',') + .sort((a, b) => { + if (a === 'default') return -1; + if (b === 'default') return 1; + return a.localeCompare(b); + }) + .map((item, index) => { + return renderGroup(item); + })} + +
+ ); + }, + }, + { + key: COLUMN_KEYS.TYPE, + title: t('类型'), + dataIndex: 'type', + render: (text, record, index) => { + if (record.children === undefined) { + return <>{renderType(text)}; + } else { + return <>{renderTagType()}; + } + }, + }, + { + key: COLUMN_KEYS.STATUS, + title: t('状态'), + dataIndex: 'status', + render: (text, record, index) => { + if (text === 3) { + if (record.other_info === '') { + record.other_info = '{}'; + } + let otherInfo = JSON.parse(record.other_info); + let reason = otherInfo['status_reason']; + let time = otherInfo['status_time']; + return ( +
+ + {renderStatus(text)} + +
+ ); + } else { + return renderStatus(text); + } + }, + }, + { + key: COLUMN_KEYS.RESPONSE_TIME, + title: t('响应时间'), + dataIndex: 'response_time', + render: (text, record, index) => { + return
{renderResponseTime(text)}
; + }, + }, + { + key: COLUMN_KEYS.BALANCE, + title: t('已用/剩余'), + dataIndex: 'expired_time', + render: (text, record, index) => { + if (record.children === undefined) { + return ( +
+ + + + {renderQuota(record.used_quota)} + + + + { + updateChannelBalance(record); + }} + > + ${renderNumberWithPoint(record.balance)} + + + +
+ ); + } else { + return ( + + + {renderQuota(record.used_quota)} + + + ); + } + }, + }, + { + key: COLUMN_KEYS.PRIORITY, + title: t('优先级'), + dataIndex: 'priority', + render: (text, record, index) => { + if (record.children === undefined) { + return ( +
+ { + manageChannel(record.id, 'priority', record, e.target.value); + }} + keepFocus={true} + innerButtons + defaultValue={record.priority} + min={-999} + /> +
+ ); + } else { + return ( + <> + { + Modal.warning({ + title: t('修改子渠道优先级'), + content: + t('确定要修改所有子渠道优先级为 ') + + e.target.value + + t(' 吗?'), + onOk: () => { + if (e.target.value === '') { + return; + } + submitTagEdit('priority', { + tag: record.key, + priority: e.target.value, + }); + }, + }); + }} + innerButtons + defaultValue={record.priority} + min={-999} + /> + + ); + } + }, + }, + { + key: COLUMN_KEYS.WEIGHT, + title: t('权重'), + dataIndex: 'weight', + render: (text, record, index) => { + if (record.children === undefined) { + return ( +
+ { + manageChannel(record.id, 'weight', record, e.target.value); + }} + keepFocus={true} + innerButtons + defaultValue={record.weight} + min={0} + /> +
+ ); + } else { + return ( + { + Modal.warning({ + title: t('修改子渠道权重'), + content: + t('确定要修改所有子渠道权重为 ') + + e.target.value + + t(' 吗?'), + onOk: () => { + if (e.target.value === '') { + return; + } + submitTagEdit('weight', { + tag: record.key, + weight: e.target.value, + }); + }, + }); + }} + innerButtons + defaultValue={record.weight} + min={-999} + /> + ); + } + }, + }, + { + key: COLUMN_KEYS.OPERATE, + title: '', + dataIndex: 'operate', + render: (text, record, index) => { + if (record.children === undefined) { + return ( +
+ + + + + { + manageChannel(record.id, 'delete', record).then(() => { + removeRecord(record); + }); + }} + > + + + {record.status === 1 ? ( + + ) : ( + + )} + + { + copySelectedChannel(record); + }} + > + + +
+ ); + } else { + return ( + <> + + + + + ); + } + }, + }, + ]; + + // Filter columns based on visibility settings + const getVisibleColumns = () => { + return allColumns.filter((column) => visibleColumns[column.key]); + }; + + // Column selector modal + const renderColumnSelector = () => { + return ( + setShowColumnSelector(false)} + footer={ + <> + + + + + } + style={{ width: isMobile() ? '90%' : 500 }} + bodyStyle={{ padding: '24px' }} + > +
+ v === true)} + indeterminate={ + Object.values(visibleColumns).some((v) => v === true) && + !Object.values(visibleColumns).every((v) => v === true) + } + onChange={(e) => handleSelectAll(e.target.checked)} + > + {t('全选')} + +
+
+ {allColumns.map((column) => { + // Skip columns without title + if (!column.title) { + return null; + } + + return ( +
+ + handleColumnVisibilityChange(column.key, e.target.checked) + } + > + {column.title} + +
+ ); + })} +
+
+ ); + }; + + const [channels, setChannels] = useState([]); + const [loading, setLoading] = useState(true); + const [activePage, setActivePage] = useState(1); + const [idSort, setIdSort] = useState(false); + const [searchKeyword, setSearchKeyword] = useState(''); + const [searchGroup, setSearchGroup] = useState(''); + const [searchModel, setSearchModel] = useState(''); + const [searching, setSearching] = useState(false); + const [selectedTypes, setSelectedTypes] = useState([]); // 新增:类型多选 + const [selectedStatuses, setSelectedStatuses] = useState([]); // 新增:状态多选 + const [selectedTags, setSelectedTags] = useState([]); // 新增:标签多选 + const [tagOptions, setTagOptions] = useState([]); // 新增:标签选项 + + const [updatingBalance, setUpdatingBalance] = useState(false); + const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); + const [showPrompt, setShowPrompt] = useState( + shouldShowPrompt('channel-test'), + ); + const [channelCount, setChannelCount] = useState(pageSize); + const [groupOptions, setGroupOptions] = useState([]); + const [showEdit, setShowEdit] = useState(false); + const [enableBatchDelete, setEnableBatchDelete] = useState(false); + const [editingChannel, setEditingChannel] = useState({ + id: undefined, + }); + const [showEditTag, setShowEditTag] = useState(false); + const [editingTag, setEditingTag] = useState(''); + const [selectedChannels, setSelectedChannels] = useState([]); + const [showEditPriority, setShowEditPriority] = useState(false); + const [enableTagMode, setEnableTagMode] = useState(() => { + const savedState = localStorage.getItem('channel-tag-mode'); + return savedState !== null ? JSON.parse(savedState) : false; + }); + useEffect(() => { + localStorage.setItem('channel-tag-mode', JSON.stringify(enableTagMode)); + }, [enableTagMode]); + const [showBatchSetTag, setShowBatchSetTag] = useState(false); + const [batchSetTagValue, setBatchSetTagValue] = useState(''); + const [showModelTestModal, setShowModelTestModal] = useState(false); + const [currentTestChannel, setCurrentTestChannel] = useState(null); + const [modelSearchKeyword, setModelSearchKeyword] = useState(''); + // 测试结果缓存 + const [testResultsCache, setTestResultsCache] = useState(() => Object.create(null)); + // 批量测试相关状态 + const [isBatchTesting, setIsBatchTesting] = useState(false); + const [batchTestResults, setBatchTestResults] = useState([]); + const [currentTestingModel, setCurrentTestingModel] = useState(''); + const [testProgress, setTestProgress] = useState({ completed: 0, total: 0 }); + const [concurrentLimit, setConcurrentLimit] = useState(3); // 默认并发数 + const [batchTestDelay, setBatchTestDelay] = useState(200); // 批次间延迟(ms) + // 批量测试控制 + const [batchTestAbortController, setBatchTestAbortController] = useState(null); + + const removeRecord = (record) => { + let newDataSource = [...channels]; + if (record.id != null) { + let idx = newDataSource.findIndex((data) => { + if (data.children !== undefined) { + for (let i = 0; i < data.children.length; i++) { + if (data.children[i].id === record.id) { + data.children.splice(i, 1); + return false; + } + } + } else { + return data.id === record.id; + } + }); + + if (idx > -1) { + newDataSource.splice(idx, 1); + setChannels(newDataSource); + } + } + }; + + const setChannelFormat = (channels, enableTagMode) => { + let channelDates = []; + let channelTags = {}; + for (let i = 0; i < channels.length; i++) { + // 为React Table设置key属性,同时保留原始的key字段(API密钥) + channels[i].rowKey = '' + channels[i].id; + if (!enableTagMode) { + channelDates.push(channels[i]); + } else { + let tag = channels[i].tag ? channels[i].tag : ''; + // find from channelTags + let tagIndex = channelTags[tag]; + let tagChannelDates = undefined; + if (tagIndex === undefined) { + // not found, create a new tag + channelTags[tag] = 1; + tagChannelDates = { + rowKey: tag, // React Table的key + key: tag, // 标签名 + id: tag, + tag: tag, + name: '标签:' + tag, + group: '', + used_quota: 0, + response_time: 0, + priority: -1, + weight: -1, + }; + tagChannelDates.children = []; + channelDates.push(tagChannelDates); + } else { + // found, add to the tag + tagChannelDates = channelDates.find((item) => item.key === tag); + } + if (tagChannelDates.priority === -1) { + tagChannelDates.priority = channels[i].priority; + } else { + if (tagChannelDates.priority !== channels[i].priority) { + tagChannelDates.priority = ''; + } + } + if (tagChannelDates.weight === -1) { + tagChannelDates.weight = channels[i].weight; + } else { + if (tagChannelDates.weight !== channels[i].weight) { + tagChannelDates.weight = ''; + } + } + + if (tagChannelDates.group === '') { + tagChannelDates.group = channels[i].group; + } else { + let channelGroupsStr = channels[i].group; + channelGroupsStr.split(',').forEach((item, index) => { + if (tagChannelDates.group.indexOf(item) === -1) { + // join + tagChannelDates.group += ',' + item; + } + }); + } + + tagChannelDates.children.push(channels[i]); + if (channels[i].status === 1) { + tagChannelDates.status = 1; + } + tagChannelDates.used_quota += channels[i].used_quota; + tagChannelDates.response_time += channels[i].response_time; + tagChannelDates.response_time = tagChannelDates.response_time / 2; + } + } + // 为React Table设置rowKey属性 + setChannels(channelDates); + if (channelDates.length >= pageSize) { + setChannelCount(channelDates.length + pageSize); + } else { + setChannelCount(channelDates.length); + } + }; + + const loadChannels = async (startIdx, pageSize, idSort, enableTagMode) => { + setLoading(true); + const res = await API.get( + `/api/channel/?p=${startIdx}&page_size=${pageSize}&id_sort=${idSort}&tag_mode=${enableTagMode}`, + ); + if (res === undefined) { + return; + } + const { success, message, data } = res.data; + if (success) { + if (startIdx === 0) { + setChannelFormat(data, enableTagMode); + } else { + let newChannels = [...channels]; + newChannels.splice(startIdx * pageSize, data.length, ...data); + setChannelFormat(newChannels, enableTagMode); + } + } else { + showError(message); + } + setLoading(false); + }; + + const copySelectedChannel = async (record) => { + const channelToCopy = record; + channelToCopy.name += t('_复制'); + channelToCopy.created_time = null; + channelToCopy.balance = 0; + channelToCopy.used_quota = 0; + if (!channelToCopy) { + showError(t('渠道未找到,请刷新页面后重试。')); + return; + } + try { + const newChannel = { ...channelToCopy, id: undefined }; + const response = await API.post('/api/channel/', newChannel); + if (response.data.success) { + showSuccess(t('渠道复制成功')); + await refresh(); + } else { + showError(response.data.message); + } + } catch (error) { + showError(t('渠道复制失败: ') + error.message); + } + }; + + const refresh = async () => { + if (searchKeyword === '' && searchGroup === '' && searchModel === '') { + await loadChannels(activePage - 1, pageSize, idSort, enableTagMode); + } else { + await searchChannels( + searchKeyword, + searchGroup, + searchModel, + enableTagMode, + ); + } + }; + + useEffect(() => { + // console.log('default effect') + const localIdSort = localStorage.getItem('id-sort') === 'true'; + const localPageSize = + parseInt(localStorage.getItem('page-size')) || ITEMS_PER_PAGE; + setIdSort(localIdSort); + setPageSize(localPageSize); + loadChannels(0, localPageSize, localIdSort, enableTagMode) + .then() + .catch((reason) => { + showError(reason); + }); + fetchGroups().then(); + loadChannelModels().then(); + // 加载测试结果缓存 + loadTestResultsCache(); + }, []); + + // 监听模态框可见性变化,加载缓存的测试结果 + useEffect(() => { + if (showModelTestModal && currentTestChannel) { + const cachedResults = getCachedTestResults(currentTestChannel.id); + if (cachedResults) { + setBatchTestResults(cachedResults); + setTestProgress({ + completed: cachedResults.length, + total: currentTestChannel.models?.split(',').filter(m => m.trim()).length || 0 + }); + } + } + }, [showModelTestModal, currentTestChannel]); + + // 加载测试结果缓存 + const loadTestResultsCache = () => { + try { + const cached = localStorage.getItem('channel-test-results-cache'); + if (cached) { + const parsedCache = JSON.parse(cached); + // 清理过期的缓存(超过24小时) + const now = Date.now(); + const validCache = Object.create(null); + + Object.keys(parsedCache).forEach(channelId => { + // 验证channelId是安全的字符串,防止原型污染 + if (channelId === '__proto__' || + channelId === 'constructor' || + channelId === 'prototype') { + return; + } + + const channelCache = parsedCache[channelId]; + // 验证channelCache是对象且timestamp是有限数字 + if (channelCache && + typeof channelCache === 'object' && + channelCache !== null && + typeof channelCache.timestamp === 'number' && + Number.isFinite(channelCache.timestamp) && + (now - channelCache.timestamp) < 24 * 60 * 60 * 1000) { + validCache[channelId] = channelCache; + } + }); + + setTestResultsCache(validCache); + // 更新localStorage中的有效缓存 + if (Object.keys(validCache).length !== Object.keys(parsedCache).length) { + localStorage.setItem('channel-test-results-cache', JSON.stringify(validCache)); + } + } + } catch (e) { + console.error('加载测试结果缓存失败:', e); + localStorage.removeItem('channel-test-results-cache'); + } + }; + + // 保存测试结果缓存 + const saveTestResultsCache = (channelId, results) => { + try { + // 将channelId转换为字符串并验证安全性,防止原型污染 + const id = String(channelId); + if (id === '__proto__' || + id === 'constructor' || + id === 'prototype') { + console.warn('无效的channelId,跳过缓存保存:', channelId); + return; + } + + const newCache = Object.assign(Object.create(null), testResultsCache, { + [id]: { + results: results, + timestamp: Date.now() + } + }); + setTestResultsCache(newCache); + localStorage.setItem('channel-test-results-cache', JSON.stringify(newCache)); + } catch (e) { + console.error('保存测试结果缓存失败:', e); + } + }; + + // 获取缓存的测试结果 + const getCachedTestResults = (channelId) => { + // 将channelId转换为字符串并验证安全性,防止原型污染 + const id = String(channelId); + if (id === '__proto__' || + id === 'constructor' || + id === 'prototype') { + return null; + } + + const channelCache = testResultsCache[id]; + if (channelCache && (Date.now() - channelCache.timestamp) < 24 * 60 * 60 * 1000) { + return channelCache.results; + } + return null; + }; + + const manageChannel = async (id, action, record, value) => { + let data = { id }; + let res; + switch (action) { + case 'delete': + res = await API.delete(`/api/channel/${id}/`); + break; + case 'enable': + data.status = 1; + res = await API.put('/api/channel/', data); + break; + case 'disable': + data.status = 2; + res = await API.put('/api/channel/', data); + break; + case 'priority': + if (value === '') { + return; + } + data.priority = parseInt(value); + res = await API.put('/api/channel/', data); + break; + case 'weight': + if (value === '') { + return; + } + data.weight = parseInt(value); + if (data.weight < 0) { + data.weight = 0; + } + res = await API.put('/api/channel/', data); + break; + } + const { success, message } = res.data; + if (success) { + showSuccess(t('操作成功完成!')); + let channel = res.data.data; + let newChannels = [...channels]; + if (action === 'delete') { + } else { + record.status = channel.status; + } + setChannels(newChannels); + } else { + showError(message); + } + }; + + const manageTag = async (tag, action) => { + console.log(tag, action); + let res; + switch (action) { + case 'enable': + res = await API.post('/api/channel/tag/enabled', { + tag: tag, + }); + break; + case 'disable': + res = await API.post('/api/channel/tag/disabled', { + tag: tag, + }); + break; + } + const { success, message } = res.data; + if (success) { + showSuccess('操作成功完成!'); + let newChannels = [...channels]; + for (let i = 0; i < newChannels.length; i++) { + if (newChannels[i].tag === tag) { + let status = action === 'enable' ? 1 : 2; + newChannels[i]?.children?.forEach((channel) => { + channel.status = status; + }); + newChannels[i].status = status; + } + } + setChannels(newChannels); + } else { + showError(message); + } + }; + + // 支持传入覆盖的类型/状态/标签参数,避免因setState异步导致筛选失效 + const searchChannels = async ( + searchKeyword, + searchGroup, + searchModel, + enableTagMode, + typesOverride = null, + statusesOverride = null, + tagsOverride = null, + ) => { + const nextTypes = typesOverride !== null ? typesOverride : selectedTypes; + const nextStatuses = statusesOverride !== null ? statusesOverride : selectedStatuses; + const nextTags = tagsOverride !== null ? tagsOverride : selectedTags; + + const noBasic = searchKeyword === '' && searchGroup === '' && searchModel === ''; + const noAdvanced = (!nextTypes || nextTypes.length === 0) && (!nextStatuses || nextStatuses.length === 0) && (!nextTags || nextTags.length === 0); + if (noBasic && noAdvanced) { + await loadChannels(activePage - 1, pageSize, idSort, enableTagMode); + return; + } + setSearching(true); + const params = new URLSearchParams(); + + // 添加基础搜索参数 + params.append('keyword', searchKeyword); + params.append('group', searchGroup); + params.append('model', searchModel); + params.append('id_sort', String(idSort)); + params.append('tag_mode', String(enableTagMode)); + + // 条件添加数组参数 + if (nextTypes && nextTypes.length > 0) { + params.append('types', nextTypes.map(Number).join(',')); + } + if (nextStatuses && nextStatuses.length > 0) { + params.append('statuses', nextStatuses.map(Number).join(',')); + } + if (nextTags && nextTags.length > 0) { + params.append('tags', nextTags.join(',')); + } + + const res = await API.get(`/api/channel/search?${params.toString()}`); + const { success, message, data } = res.data; + if (success) { + setChannelFormat(data, enableTagMode); + setActivePage(1); + } else { + showError(message); + } + setSearching(false); + }; + + const updateChannelProperty = (channelId, updateFn) => { + // Create a new copy of channels array + const newChannels = [...channels]; + let updated = false; + + // Find and update the correct channel + newChannels.forEach((channel) => { + if (channel.children !== undefined) { + // If this is a tag group, search in its children + channel.children.forEach((child) => { + if (child.id === channelId) { + updateFn(child); + updated = true; + } + }); + } else if (channel.id === channelId) { + // Direct channel match + updateFn(channel); + updated = true; + } + }); + + // Only update state if we actually modified a channel + if (updated) { + setChannels(newChannels); + } + }; + + // 辅助函数:更新测试结果到状态 + const updateTestResult = (model, result) => { + setBatchTestResults(prevResults => { + const existingIndex = prevResults.findIndex(r => r.model === model); + if (existingIndex >= 0) { + const newResults = [...prevResults]; + newResults[existingIndex] = result; + return newResults; + } else { + return [...prevResults, result]; + } + }); + }; + + // 辅助函数:创建测试结果对象 + const createTestResult = (model, success, message, time = null, isRetryable = true) => ({ + model, + success, + message, + time, + retryCount: 0, + isRetryable: success ? false : isRetryable + }); + + // 辅助函数:处理测试成功 + const handleTestSuccess = (record, model, time) => { + // 更新渠道属性 + updateChannelProperty(record.id, (channel) => { + channel.response_time = time * 1000; + channel.test_time = Date.now() / 1000; + }); + + // 更新模态框中的测试结果 + if (shouldUpdateModalResults(record.id)) { + const result = createTestResult( + model, + true, + t('测试成功,耗时 {{time}} 秒', { time: time.toFixed(2) }), + time + ); + updateTestResult(model, result); + } + + showInfo( + t('通道 {{name}} 测试成功,耗时 {{time}} 秒。', { + name: record.name, + time: time.toFixed(2) + }) + ); + }; + + // 辅助函数:处理测试失败 + const handleTestFailure = (record, model, message) => { + if (shouldUpdateModalResults(record.id)) { + const result = createTestResult(model, false, message); + updateTestResult(model, result); + } + showError(message); + }; + + // 辅助函数:判断是否需要更新模态框结果 + const shouldUpdateModalResults = (recordId) => { + return showModelTestModal && currentTestChannel && currentTestChannel.id === recordId; + }; + + // 主要的测试渠道函数 + const testChannel = async (record, model) => { + try { + const res = await API.get(`/api/channel/test/${record.id}?model=${model}`); + const { success, message, time } = res.data; + + if (success) { + handleTestSuccess(record, model, time); + } else { + handleTestFailure(record, model, message); + } + } catch (error) { + handleTestFailure(record, model, error.message || t('测试请求失败')); + } + }; + + // 统一的取消检测函数 + const isCancellationError = (error) => { + return ( + error.name === 'AbortError' || + error.name === 'CanceledError' || + axios.isCancel(error) || + error.code === 'ERR_CANCELED' + ); + }; + + // 单个模型测试函数(用于并发)- 支持重试 + const testSingleModel = async (model, abortController, maxRetries = 2) => { + let lastError = null; + + for (let retryCount = 0; retryCount <= maxRetries; retryCount++) { + try { + const res = await API.get(`/api/channel/test/${currentTestChannel.id}?model=${encodeURIComponent(model)}`, { + signal: abortController?.signal + }); + const { success, message, time } = res.data; + + const result = { + model, + success, + message: success ? `测试成功,耗时 ${time.toFixed(2)} 秒` : message, + time: success ? time : null, + retryCount, + isRetryable: !success && retryCount < maxRetries + }; + + if (success) { + // 更新渠道状态 + updateChannelProperty(currentTestChannel.id, (channel) => { + channel.response_time = time * 1000; + channel.test_time = Date.now() / 1000; + }); + } + + return result; + } catch (error) { + lastError = error; + + // 如果是取消请求,立即抛出错误以停止执行 + if (isCancellationError(error) || abortController?.signal?.aborted) { + throw error; + } + + // 如果达到最大重试次数,返回失败结果 + if (retryCount >= maxRetries) { + return { + model, + success: false, + message: error.message || '测试失败', + time: null, + retryCount, + isRetryable: true, // 仍然可以手动重试 + error: error + }; + } + + // 重试前等待一段时间(指数退避) + const delay = Math.min(1000 * Math.pow(2, retryCount), 5000); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + }; + + // 分批并发处理函数 + const processBatch = async (models, batchSize, abortController) => { + const results = []; + + for (let i = 0; i < models.length; i += batchSize) { + // 4) 每次迭代都检查取消状态 + if (abortController?.signal?.aborted) { + throw new Error('测试被用户取消'); + } + + const batch = models.slice(i, i + batchSize); + setCurrentTestingModel(`批次 ${Math.floor(i / batchSize) + 1}: ${batch.join(', ')}`); + + // 4) 在await之前再次检查取消状态 + if (abortController?.signal?.aborted) { + throw new Error('测试被用户取消'); + } + + // 并发执行当前批次 + const batchPromises = batch.map(model => testSingleModel(model, abortController)); + const batchResults = await Promise.allSettled(batchPromises); + + // 4) 在await之后检查取消状态 + if (abortController?.signal?.aborted) { + throw new Error('测试被用户取消'); + } + + // 处理批次结果 + const processedResults = batchResults.map((result, index) => { + if (result.status === 'fulfilled') { + return result.value; + } else { + // 如果是取消错误,直接抛出 + if (isCancellationError(result.reason) || abortController?.signal?.aborted) { + throw result.reason; + } + return { + model: batch[index], + success: false, + message: result.reason?.message || '测试失败', + time: null + }; + } + }); + + results.push(...processedResults); + + // 更新进度和结果 + setTestProgress({ completed: results.length, total: models.length }); + setBatchTestResults([...results]); + + // 3) 批次间使用可配置的延迟 + if (i + batchSize < models.length) { + // 4) 在延迟期间也要检查取消状态 + if (abortController?.signal?.aborted) { + throw new Error('测试被用户取消'); + } + await new Promise(resolve => setTimeout(resolve, batchTestDelay)); + } + } + + return results; + }; + + // 重试失败的模型 + const retryFailedModels = async () => { + if (!currentTestChannel || isBatchTesting) { + return; + } + + const failedModels = batchTestResults.filter(r => !r.success && r.isRetryable); + if (failedModels.length === 0) { + showInfo(t('没有可重试的失败模型')); + return; + } + + const controller = new AbortController(); + setBatchTestAbortController(controller); + setIsBatchTesting(true); + setCurrentTestingModel('重试失败模型...'); + + try { + const failedModelNames = failedModels.map(r => r.model); + const retryResults = await processBatch(failedModelNames, concurrentLimit, controller); + + // 更新批量测试结果,合并重试结果 + const updatedResults = batchTestResults.map(result => { + const retryResult = retryResults.find(r => r.model === result.model); + return retryResult || result; + }); + + setBatchTestResults(updatedResults); + + const newSuccessCount = retryResults.filter(r => r.success).length; + showInfo(t('重试完成:成功 {{successCount}} 个,失败 {{failCount}} 个', { + successCount: newSuccessCount, + failCount: failedModelNames.length - newSuccessCount + })); + } catch (error) { + if (isCancellationError(error)) { + showInfo(t('重试已取消')); + } else { + showError(t('重试过程中发生错误:') + error.message); + } + } finally { + setIsBatchTesting(false); + setCurrentTestingModel(''); + setBatchTestAbortController(null); + } + }; + + // 处理模型测试弹窗关闭 + const handleModelTestModalClose = () => { + // 停止正在进行的批量测试 + if (batchTestAbortController) { + batchTestAbortController.abort(); + setBatchTestAbortController(null); + } + setShowModelTestModal(false); + setModelSearchKeyword(''); + // 重置批量测试相关状态 + setIsBatchTesting(false); + setBatchTestResults([]); + setCurrentTestingModel(''); + setTestProgress({ completed: 0, total: 0 }); + }; + + // 处理删除失败模型 + const handleDeleteFailedModels = (failedModels) => { + const currentModels = currentTestChannel.models.split(',').map(m => m.trim()); + const failedModelNames = failedModels.map(r => r.model); + // 优化性能:将数组转换为 Set,查找时间复杂度从 O(M) 降低到 O(1) + const failedModelNamesSet = new Set(failedModelNames); + const remainingModels = currentModels.filter(m => !failedModelNamesSet.has(m)); + + if (remainingModels.length === 0) { + Modal.warning({ + title: t('无法删除所有模型'), + content: t('删除这些失败的模型会导致渠道没有任何可用模型,这将使渠道无法正常工作。请至少保留一个模型或先添加新的可用模型。'), + }); + return; + } + + Modal.confirm({ + title: t('批量删除失败模型'), + content: ( +
+

{t('确定要删除所有 {{count}} 个测试失败的模型吗?此操作不可撤销。', { count: failedModels.length })}

+

+ {t('删除后将保留 {{remaining}} 个正常模型:', { remaining: remainingModels.length })} +

+
+ {remainingModels.map((model, index) => ( +
{model}
+ ))} +
+
+ ), + onOk: async () => { + try { + const newModels = remainingModels; + + const updateData = { + id: currentTestChannel.id, + models: newModels.join(','), + }; + + const res = await API.put('/api/channel/', updateData); + if (res.data.success) { + updateChannelProperty(currentTestChannel.id, (channel) => { + channel.models = newModels.join(','); + }); + setCurrentTestChannel({ + ...currentTestChannel, + models: newModels.join(',') + }); + + setBatchTestResults(prev => prev.filter(r => !failedModelNamesSet.has(r.model))); + await refresh(); + showSuccess(t('已删除 {{count}} 个失败的模型', { count: failedModels.length })); + } else { + showError(res.data.message || t('批量删除失败')); + } + } catch (error) { + showError(error.message || t('批量删除失败')); + } + } + }); + }; + + // 批量测试所有模型(并发版本) + const testAllModels = async () => { + // 1) 验证currentTestChannel存在且具有期望的属性 + if (!currentTestChannel || + typeof currentTestChannel !== 'object' || + !currentTestChannel.id || + !currentTestChannel.models || + typeof currentTestChannel.models !== 'string') { + showError(t('当前测试渠道无效或缺少必需属性')); + return; + } + + // 修剪空白、去重并过滤空字符串 + const models = [...new Set( + currentTestChannel.models + .split(',') + .map(model => model.trim()) + .filter(model => model.length > 0) + )]; + if (models.length === 0) { + showError(t('没有可测试的模型')); + return; + } + + // 创建 AbortController + const controller = new AbortController(); + setBatchTestAbortController(controller); + + setIsBatchTesting(true); + setBatchTestResults([]); + setCurrentTestingModel(''); + setTestProgress({ completed: 0, total: models.length }); + + // 2) 使用try-catch-finally包装整个异步测试逻辑 + try { + const results = await processBatch(models, concurrentLimit, controller); + + const successCount = results.filter(r => r.success).length; + const failCount = results.filter(r => !r.success).length; + + // 保存测试结果到缓存 + saveTestResultsCache(currentTestChannel.id, results); + + showInfo(t('批量测试完成:成功 {{successCount}} 个,失败 {{failCount}} 个', { + successCount, + failCount + })); + } catch (error) { + if (isCancellationError(error) || error.message.includes('取消')) { + showInfo(t('测试已取消')); + } else { + showError(t('批量测试过程中发生错误:') + error.message); + } + } finally { + // 3) 确保在finally中重置状态 + setIsBatchTesting(false); + setCurrentTestingModel(''); + setBatchTestAbortController(null); + } + }; + + const updateChannelBalance = async (record) => { + const res = await API.get(`/api/channel/update_balance/${record.id}/`); + const { success, message, balance } = res.data; + if (success) { + updateChannelProperty(record.id, (channel) => { + channel.balance = balance; + channel.balance_updated_time = Date.now() / 1000; + }); + showInfo( + t('通道 {{name}} 余额更新成功!', { name: record.name }), + ); + } else { + showError(message); + } + }; + + const testAllChannels = async () => { + const res = await API.get(`/api/channel/test`); + const { success, message } = res.data; + if (success) { + showInfo(t('已成功开始测试所有已启用通道,请刷新页面查看结果。')); + } else { + showError(message); + } + }; + + const deleteAllDisabledChannels = async () => { + const res = await API.delete(`/api/channel/disabled`); + const { success, message, data } = res.data; + if (success) { + showSuccess( + t('已删除所有禁用渠道,共计 {{count}} 个', { count: data }), + ); + await refresh(); + } else { + showError(message); + } + }; + + const updateAllChannelsBalance = async () => { + setUpdatingBalance(true); + const res = await API.get(`/api/channel/update_balance`); + const { success, message } = res.data; + if (success) { + showInfo(t('已更新完毕所有已启用通道余额!')); + } else { + showError(message); + } + setUpdatingBalance(false); + }; + + const batchDeleteChannels = async () => { + if (selectedChannels.length === 0) { + showError(t('请先选择要删除的通道!')); + return; + } + setLoading(true); + let ids = []; + selectedChannels.forEach((channel) => { + ids.push(channel.id); + }); + const res = await API.post(`/api/channel/batch`, { ids: ids }); + const { success, message, data } = res.data; + if (success) { + showSuccess(t('已删除 {{count}} 个通道!', { count: data })); + await refresh(); + } else { + showError(message); + } + setLoading(false); + }; + + const fixChannelsAbilities = async () => { + const res = await API.post(`/api/channel/fix`); + const { success, message, data } = res.data; + if (success) { + showSuccess(t('已修复 {{count}} 个通道!', { count: data })); + await refresh(); + } else { + showError(message); + } + }; + + let pageData = channels.slice( + (activePage - 1) * pageSize, + activePage * pageSize, + ); + + const handlePageChange = (page) => { + setActivePage(page); + if (page === Math.ceil(channels.length / pageSize) + 1) { + // In this case we have to load more data and then append them. + loadChannels(page - 1, pageSize, idSort, enableTagMode).then((r) => {}); + } + }; + + const handlePageSizeChange = async (size) => { + localStorage.setItem('page-size', size + ''); + setPageSize(size); + setActivePage(1); + loadChannels(0, size, idSort, enableTagMode) + .then() + .catch((reason) => { + showError(reason); + }); + }; + + const fetchGroups = async () => { + try { + let res = await API.get(`/api/group/`); + // add 'all' option + // res.data.data.unshift('all'); + if (res === undefined) { + return; + } + setGroupOptions( + res.data.data.map((group) => ({ + label: group, + value: group, + })), + ); + } catch (error) { + showError(error.message); + } + }; + + const submitTagEdit = async (type, data) => { + switch (type) { + case 'priority': + if (data.priority === undefined || data.priority === '') { + showError(t('优先级必须是整数!')); + return; + } + data.priority = parseInt(data.priority); + break; + case 'weight': + if ( + data.weight === undefined || + data.weight < 0 || + data.weight === '' + ) { + showInfo(t('权重必须是非负整数!')); + return; + } + data.weight = parseInt(data.weight); + break; + } + + try { + const res = await API.put('/api/channel/tag', data); + if (res?.data?.success) { + showSuccess(t('更新成功!')); + await refresh(); + } + } catch (error) { + showError(error); + } + }; + + const closeEdit = () => { + setShowEdit(false); + }; + + const handleRow = (record, index) => { + if (record.status !== 1) { + return { + style: { + background: 'var(--semi-color-disabled-border)', + }, + }; + } else { + return {}; + } + }; + + const batchSetChannelTag = async () => { + if (selectedChannels.length === 0) { + showError(t('请先选择要设置标签的渠道!')); + return; + } + if (batchSetTagValue === '') { + showError(t('标签不能为空!')); + return; + } + let ids = selectedChannels.map((channel) => channel.id); + const res = await API.post('/api/channel/batch/tag', { + ids: ids, + tag: batchSetTagValue === '' ? null : batchSetTagValue, + }); + if (res.data.success) { + showSuccess( + t('已为 {{count}} 个渠道设置标签!', { count: res.data.data }), + ); + await refresh(); + setShowBatchSetTag(false); + } else { + showError(res.data.message); + } + }; + + return ( + <> + {renderColumnSelector()} + setShowEditTag(false)} + refresh={refresh} + /> + +
{ + searchChannels( + searchKeyword, + searchGroup, + searchModel, + enableTagMode, + ); + }} + labelPosition='left' + > +
+ + { + setSearchKeyword(v.trim()); + }} + /> + { + setSearchModel(v.trim()); + }} + /> + { + setSearchGroup(v); + searchChannels(searchKeyword, v, searchModel, enableTagMode); + }} + /> + {/* 新增:类型多选 */} + ({ label: o.label, value: o.value }))} + onChange={(v) => { + const next = (v || []).map(Number); + setSelectedTypes(next); + searchChannels(searchKeyword, searchGroup, searchModel, enableTagMode, next, null, null); + }} + /> + {/* 新增:状态多选 */} + { + const next = (v || []).map(Number); + setSelectedStatuses(next); + searchChannels(searchKeyword, searchGroup, searchModel, enableTagMode, null, next, null); + }} + /> + {/* 新增:标签多选 */} + { + if (tagOptions.length===0) { + const res = await API.get('/api/channel/tags?offset=0&limit=200'); + const opts = (res?.data?.data||[]).filter(Boolean).map(t=>({label:t, value:t})); + setTagOptions(opts); + } + }} + onChange={(v) => { + const next = (v || []); + setSelectedTags(next); + searchChannels(searchKeyword, searchGroup, searchModel, enableTagMode, null, null, next); + }} + /> + + + + + +
+ + +
+ +
+ + {t('使用ID排序')} + + { + localStorage.setItem('id-sort', v + ''); + setIdSort(v); + loadChannels(0, pageSize, v, enableTagMode) + .then() + .catch((reason) => { + showError(reason); + }); + }} + > +
+ +
+ + + + + + + + + + + + + + + + + + + + + + } + > + + +
+
+
+
+
+ + {t('开启批量操作')} + + { + setEnableBatchDelete(v); + }} + /> +
+ +
+ + + + + + +
+
+ +
+
+ + {t('标签聚合模式')} + + { + setEnableTagMode(v); + loadChannels(0, pageSize, idSort, v); + }} + /> +
+ +
+ + + +
+
+ +
'', + onPageSizeChange: (size) => { + handlePageSizeChange(size).then(); + }, + onPageChange: handlePageChange, + }} + expandAllRows={false} + onRow={handleRow} + rowSelection={ + enableBatchDelete + ? { + onChange: (selectedRowKeys, selectedRows) => { + // console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows); + setSelectedChannels(selectedRows); + }, + } + : null + } + /> + setShowBatchSetTag(false)} + maskClosable={false} + centered={true} + style={{ width: isMobile() ? '90%' : 500 }} + > +
+ {t('请输入要设置的标签名称')} +
+ setBatchSetTagValue(v)} + size='large' + /> +
+ + {t('已选择 {{count}} 个渠道', { count: selectedChannels.length })} + +
+
+ + {/* 模型测试弹窗 */} + +
+ {currentTestChannel && ( + + )} +
+
+ + + + ); +}; + +export default ChannelsTable; diff --git a/web/src/components/Footer.js b/web/src/components/Footer.js new file mode 100644 index 0000000000000000000000000000000000000000..8aa50420f79c105841cc5270197eae268aa2bd60 --- /dev/null +++ b/web/src/components/Footer.js @@ -0,0 +1,110 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { getFooterHTML } from '../helpers'; + +const FooterBar = () => { + const [footer, setFooter] = useState(getFooterHTML()); + const [poweredByText, setPoweredByText] = useState(''); + let remainCheckTimes = 5; + + const poweredByTexts = [ + '由 Veloera 驱动', + '功能与速度由 Veloera 提供', + 'Made with ♥️ by Veloera', + 'Powered by Veloera', + 'Veloera 强力驱动', + '基于 Veloera 构建', + 'Built on Veloera', + 'Driven by Veloera', + 'Veloera Powered', + ]; + + const loadFooter = () => { + let footer_html = localStorage.getItem('footer_html'); + if (footer_html) { + setFooter(footer_html); + } + }; + + useEffect(() => { + // Set random powered by text on component mount + const randomIndex = Math.floor(Math.random() * poweredByTexts.length); + setPoweredByText(poweredByTexts[randomIndex]); + + const timer = setInterval(() => { + if (remainCheckTimes <= 0) { + clearInterval(timer); + return; + } + remainCheckTimes--; + loadFooter(); + }, 200); + return () => clearTimeout(timer); + }, []); + + const PoweredByLink = ( + e.target.style.textDecoration = 'underline'} + onMouseLeave={(e) => e.target.style.textDecoration = 'none'} + > + {poweredByText} + + ); + + let content; + if (footer) { + content = ( +
+
+
{PoweredByLink}
+
+ ); + } else { + content = ( +
+ {PoweredByLink} +
+ ); + } + + return ( +
+ {content} +
+ ); +}; + +export default FooterBar; \ No newline at end of file diff --git a/web/src/components/HeaderBar.js b/web/src/components/HeaderBar.js new file mode 100644 index 0000000000000000000000000000000000000000..43f5b27a46798f52969ce1e5ee6289a7734ab1a5 --- /dev/null +++ b/web/src/components/HeaderBar.js @@ -0,0 +1,538 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useContext, useEffect, useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { UserContext } from '../context/User'; +import { useSetTheme, useTheme } from '../context/Theme'; +import { useTranslation } from 'react-i18next'; + +import { API, getLogo, getSystemName, isMobile, showSuccess } from '../helpers'; +import '../index.css'; + +import fireworks from 'react-fireworks'; + +import { + IconClose, + IconHelpCircle, + IconHome, + IconHomeStroked, + IconIndentLeft, + IconComment, + IconKey, + IconMenu, + IconNoteMoneyStroked, + IconPriceTag, + IconUser, + IconLanguage, + IconInfoCircle, + IconCreditCard, + IconTerminal, +} from '@douyinfe/semi-icons'; +import { + Avatar, + Button, + Dropdown, + Layout, + Nav, + Switch, + Tag, +} from '@douyinfe/semi-ui'; +import { stringToColor } from '../helpers/render'; +import Text from '@douyinfe/semi-ui/lib/es/typography/text'; +import { StyleContext } from '../context/Style/index.js'; +import { StatusContext } from '../context/Status/index.js'; +import InboxIcon from './InboxIcon'; + +// 自定义顶部栏样式 +const headerStyle = { + boxShadow: '0 2px 10px rgba(0, 0, 0, 0.1)', + borderBottom: '1px solid var(--semi-color-border)', + background: 'var(--semi-color-bg-0)', + transition: 'all 0.3s ease', + width: '100%', +}; + +// 自定义顶部栏按钮样式 +const headerItemStyle = { + borderRadius: '4px', + margin: '0 4px', + transition: 'all 0.3s ease', +}; + +// 自定义顶部栏按钮悬停样式 +const headerItemHoverStyle = { + backgroundColor: 'var(--semi-color-primary-light-default)', + color: 'var(--semi-color-primary)', +}; + +// 自定义顶部栏Logo样式 +const logoStyle = { + display: 'flex', + alignItems: 'center', + gap: '10px', + padding: '0 10px', + height: '100%', +}; + +// 自定义顶部栏系统名称样式 +const systemNameStyle = { + fontWeight: 'bold', + fontSize: '18px', + background: + 'linear-gradient(45deg, var(--semi-color-primary), var(--semi-color-link-hover))', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + padding: '0 5px', +}; + +// 自定义顶部栏按钮图标样式 +const headerIconStyle = { + fontSize: '18px', + transition: 'all 0.3s ease', +}; + +// 自定义头像样式 +const avatarStyle = { + margin: '4px', + cursor: 'pointer', + boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', + transition: 'all 0.3s ease', +}; + +// 自定义下拉菜单样式 +const dropdownStyle = { + borderRadius: '8px', + boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', + overflow: 'hidden', +}; + +// 自定义主题切换开关样式 +const switchStyle = { + margin: '0 8px', +}; + +const HeaderBar = () => { + const { t, i18n } = useTranslation(); + const [userState, userDispatch] = useContext(UserContext); + const [styleState, styleDispatch] = useContext(StyleContext); + const [statusState, statusDispatch] = useContext(StatusContext); + let navigate = useNavigate(); + const [currentLang, setCurrentLang] = useState(i18n.language); + + const systemName = getSystemName(); + const logo = getLogo(); + const currentDate = new Date(); + // enable fireworks on new year(1.1 and 2.9-2.24) + const isNewYear = currentDate.getMonth() === 0 && currentDate.getDate() === 1; + + // Check if self-use mode is enabled + const isSelfUseMode = statusState?.status?.self_use_mode_enabled || false; + const docsLink = statusState?.status?.docs_link || ''; + const isDemoSiteMode = statusState?.status?.demo_site_enabled || false; + + let buttons = [ + { + text: t('首页'), + itemKey: 'home', + to: '/', + icon: , + }, + { + text: t('控制台'), + itemKey: 'detail', + to: '/', + icon: , + }, + { + text: t('定价'), + itemKey: 'pricing', + to: '/pricing', + icon: , + }, + // Only include the docs button if docsLink exists + ...(docsLink + ? [ + { + text: t('文档'), + itemKey: 'docs', + isExternal: true, + externalLink: docsLink, + icon: , + }, + ] + : []), + { + text: t('关于'), + itemKey: 'about', + to: '/about', + icon: , + }, + ]; + + async function logout() { + await API.get('/api/user/logout'); + showSuccess(t('注销成功!')); + userDispatch({ type: 'logout' }); + localStorage.removeItem('user'); + navigate('/login'); + } + + const handleNewYearClick = () => { + fireworks.init('root', {}); + fireworks.start(); + setTimeout(() => { + fireworks.stop(); + setTimeout(() => { + window.location.reload(); + }, 10000); + }, 3000); + }; + + const theme = useTheme(); + const setTheme = useSetTheme(); + + useEffect(() => { + if (theme === 'dark') { + document.body.setAttribute('theme-mode', 'dark'); + } else { + document.body.removeAttribute('theme-mode'); + } + // 发送当前主题模式给子页面 + const iframe = document.querySelector('iframe'); + if (iframe) { + iframe.contentWindow.postMessage({ themeMode: theme }, '*'); + } + + if (isNewYear) { + console.log('Happy New Year!'); + } + }, [theme]); + + useEffect(() => { + const handleLanguageChanged = (lng) => { + setCurrentLang(lng); + const iframe = document.querySelector('iframe'); + if (iframe) { + iframe.contentWindow.postMessage({ lang: lng }, '*'); + } + }; + + i18n.on('languageChanged', handleLanguageChanged); + + return () => { + i18n.off('languageChanged', handleLanguageChanged); + }; + }, [i18n]); + + const handleLanguageChange = (lang) => { + i18n.changeLanguage(lang); + }; + + return ( + <> + +
+ +
+
+ + ); +}; + +export default HeaderBar; diff --git a/web/src/components/IDCFlareIcon.js b/web/src/components/IDCFlareIcon.js new file mode 100644 index 0000000000000000000000000000000000000000..bb6198201a4580b959c6ac9de109a605c10b5401 --- /dev/null +++ b/web/src/components/IDCFlareIcon.js @@ -0,0 +1,47 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React from 'react'; +import { Icon } from '@douyinfe/semi-ui'; + +const IDCFlareIcon = (props) => { + function CustomIcon() { + return ( + + IDC Flare + + + + + + + + ); + } + + return } />; +}; + +export default IDCFlareIcon; \ No newline at end of file diff --git a/web/src/components/InboxIcon.js b/web/src/components/InboxIcon.js new file mode 100644 index 0000000000000000000000000000000000000000..51685353ff146c15a719cc56ed5044e43b58aa67 --- /dev/null +++ b/web/src/components/InboxIcon.js @@ -0,0 +1,104 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { Badge, Button, Tooltip } from '@douyinfe/semi-ui'; +import { IconMail } from '@douyinfe/semi-icons'; +import { useNavigate } from 'react-router-dom'; +import { API } from '../helpers'; +import { useTranslation } from 'react-i18next'; + +const InboxIcon = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const [unreadCount, setUnreadCount] = useState(0); + const [loading, setLoading] = useState(false); + + const loadUnreadCount = async () => { + if (loading) return; + + setLoading(true); + try { + const res = await API.get('/api/user/messages/unread_count'); + const { success, data } = res.data; + if (success) { + setUnreadCount(data.unread_count || 0); + } + } catch (error) { + // Silently fail - unread count is not critical + console.error('Failed to load unread count:', error); + } finally { + setLoading(false); + } + }; + + const handleClick = () => { + navigate('/app/inbox'); + }; + + useEffect(() => { + loadUnreadCount(); + + // Poll for unread count every 30 seconds + const interval = setInterval(loadUnreadCount, 30000); + + return () => clearInterval(interval); + }, []); + + return ( + +
+
+
+ ); +}; + +export default InboxIcon; \ No newline at end of file diff --git a/web/src/components/LinuxDoIcon.js b/web/src/components/LinuxDoIcon.js new file mode 100644 index 0000000000000000000000000000000000000000..366c3eb6b6d8b0c8e2893740e17cd310b067e068 --- /dev/null +++ b/web/src/components/LinuxDoIcon.js @@ -0,0 +1,55 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React from 'react'; +import { Icon } from '@douyinfe/semi-ui'; + +const LinuxDoIcon = (props) => { + function CustomIcon() { + return ( + + + + + + + + ); + } + + return } />; +}; + +export default LinuxDoIcon; diff --git a/web/src/components/Loading.js b/web/src/components/Loading.js new file mode 100644 index 0000000000000000000000000000000000000000..a21a7f97bca283666df2d2e45e575cd02783b179 --- /dev/null +++ b/web/src/components/Loading.js @@ -0,0 +1,107 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React from 'react'; + +const Loading = ({ prompt: name = 'page' }) => { + return ( +
+
+ + Loading {name} + + + + + + + + + + + + + + + + + + +
+
+ ); +}; + +export default Loading; diff --git a/web/src/components/LoginForm.js b/web/src/components/LoginForm.js new file mode 100644 index 0000000000000000000000000000000000000000..bde0bad8fc0a851e2b246442c0bd2b62f9c3c437 --- /dev/null +++ b/web/src/components/LoginForm.js @@ -0,0 +1,194 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; +import { API, showError, showInfo, showSuccess, updateAPI } from '../helpers'; +import { Button, Form, Modal } from '@douyinfe/semi-ui'; +import Text from '@douyinfe/semi-ui/lib/es/typography/text'; +import { setUserData } from '../helpers/data.js'; +import { useTranslation } from 'react-i18next'; +import { VALIDATION_MESSAGES } from '../utils/authConstants'; + +// Shared components and hooks +import { useAuthForm } from '../hooks/useAuthForm'; +import { useTurnstile } from '../hooks/useTurnstile'; +import AuthFormLayout from './shared/AuthFormLayout'; +import ThirdPartyAuth from './shared/ThirdPartyAuth'; +import WeChatLoginModal from './shared/WeChatLoginModal'; +import TurnstileWrapper from './shared/TurnstileWrapper'; + +const LoginForm = () => { + const { t } = useTranslation(); + const [submitted, setSubmitted] = useState(false); + + // Use shared hooks + const { + inputs, + handleChange, + status, + showWeChatLoginModal, + setShowWeChatLoginModal, + userDispatch, + navigate, + searchParams, + processAffCode, + onWeChatLoginClicked, + onSubmitWeChatVerificationCode, + onTelegramLoginClicked, + } = useAuthForm({ + username: '', + password: '', + wechat_verification_code: '', + }); + + const { + turnstileEnabled, + turnstileSiteKey, + turnstileToken, + setTurnstileToken, + validateTurnstile, + } = useTurnstile(); + + const { username, password } = inputs; + + // Process AFF code on component mount + useEffect(() => { + processAffCode(); + }, []); + + // Check for expired session + useEffect(() => { + if (searchParams.get('expired')) { + showError(t(VALIDATION_MESSAGES.SESSION_EXPIRED)); + } + }, [searchParams, t]); + + const handleSubmit = async (e) => { + if (!validateTurnstile()) { + showInfo(VALIDATION_MESSAGES.TURNSTILE_WAIT); + return; + } + + setSubmitted(true); + if (username && password) { + const res = await API.post( + `/api/user/login?turnstile=${turnstileToken}`, + { + username, + password, + }, + ); + const { success, message, data } = res.data; + if (success) { + userDispatch({ type: 'login', payload: data }); + setUserData(data); + updateAPI(); + showSuccess(VALIDATION_MESSAGES.LOGIN_SUCCESS); + if (username === 'root' && password === '123456') { + Modal.error({ + title: VALIDATION_MESSAGES.DEFAULT_PASSWORD_WARNING, + content: VALIDATION_MESSAGES.CHANGE_DEFAULT_PASSWORD, + centered: true, + }); + } + navigate(searchParams.get('returnTo') || '/app/tokens'); + } else { + showError(message); + } + } else { + showError(VALIDATION_MESSAGES.MISSING_CREDENTIALS); + } + }; + + const handleWeChatSubmit = () => { + onSubmitWeChatVerificationCode(turnstileToken); + }; + + return ( + +
+ handleChange('username', value)} + /> + handleChange('password', value)} + /> + + + + +
+ + {t('没有账户?')}{' '} + {t('点击注册')} + + + {t('忘记密码?')} {t('点击重置')} + +
+ + + + setShowWeChatLoginModal(false)} + status={status} + inputs={inputs} + handleChange={handleChange} + /> + + +
+ ); +}; + +export default LoginForm; diff --git a/web/src/components/LogsTable.js b/web/src/components/LogsTable.js new file mode 100644 index 0000000000000000000000000000000000000000..1fd4b1f651a05e50a2a3f1205585828be81e86e0 --- /dev/null +++ b/web/src/components/LogsTable.js @@ -0,0 +1,1675 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useContext, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + API, + copy, + getTodayStartTimestamp, + isAdmin, + showError, + showSuccess, + timestamp2string, +} from '../helpers'; + +import { + Avatar, + Button, + Descriptions, + Form, + Layout, + Modal, + Popover, + Select, + Space, + Spin, + Table, + Tag, + Tooltip, + Checkbox, +} from '@douyinfe/semi-ui'; +import { ITEMS_PER_PAGE } from '../constants'; +import { + renderAudioModelPrice, + renderClaudeLogContent, + renderClaudeModelPrice, + renderClaudeModelPriceSimple, + renderGroup, + renderLogContent, + renderModelPrice, + renderModelPriceSimple, + renderNumber, + renderQuota, + stringToColor, +} from '../helpers/render'; +import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph'; +import { getLogOther } from '../helpers/other.js'; +import { StyleContext } from '../context/Style/index.js'; +import { IconInherit, IconRefresh, IconSetting } from '@douyinfe/semi-icons'; + +const { Header } = Layout; + +function renderTimestamp(timestamp) { + return <>{timestamp2string(timestamp)}; +} + +const MODE_OPTIONS = [ + { key: 'all', text: 'all', value: 'all' }, + { key: 'self', text: 'current user', value: 'self' }, +]; + +const colors = [ + 'amber', + 'blue', + 'cyan', + 'green', + 'grey', + 'indigo', + 'light-blue', + 'lime', + 'orange', + 'pink', + 'purple', + 'red', + 'teal', + 'violet', + 'yellow', +]; + +const LogsTable = () => { + const { t } = useTranslation(); + + function renderType(type) { + switch (type) { + case 1: + return ( + + {t('充值')} + + ); + case 2: + return ( + + {t('消费')} + + ); + case 3: + return ( + + {t('管理')} + + ); + case 4: + return ( + + {t('系统')} + + ); + case 5: // 添加签到日志类型 + return ( + {/* 你可以选择一个合适的颜色 */} + {t('签到')} {/* 前端显示的文字 */} + + ); + case 6: + return ( + + {t('错误')} + + ); + default: + return ( + + {t('未知')} + + ); + } + } + + function renderIsStream(bool) { + if (bool) { + return ( + + {t('流')} + + ); + } else { + return ( + + {t('非流')} + + ); + } + } + + function renderUseTime(type) { + const time = parseInt(type); + if (time < 101) { + return ( + + {' '} + {time} s{' '} + + ); + } else if (time < 300) { + return ( + + {' '} + {time} s{' '} + + ); + } else { + return ( + + {' '} + {time} s{' '} + + ); + } + } + + function renderFirstUseTime(type) { + let time = parseFloat(type) / 1000.0; + time = time.toFixed(1); + if (time < 3) { + return ( + + {' '} + {time} s{' '} + + ); + } else if (time < 10) { + return ( + + {' '} + {time} s{' '} + + ); + } else { + return ( + + {' '} + {time} s{' '} + + ); + } + } + + function renderModelName(record) { + let other = getLogOther(record.other); + let modelMapped = + other?.is_model_mapped && + other?.upstream_model_name && + other?.upstream_model_name !== ''; + if (!modelMapped) { + return ( + { + copyText(event, record.model_name).then((r) => {}); + }} + > + {' '} + {record.model_name}{' '} + + ); + } else { + return ( + <> + + + + { + copyText(event, record.model_name).then((r) => {}); + }} + > + {t('请求并计费模型')} {record.model_name}{' '} + + { + copyText(event, other.upstream_model_name).then( + (r) => {}, + ); + }} + > + {t('实际模型')} {other.upstream_model_name}{' '} + + + + } + > + { + copyText(event, record.model_name).then((r) => {}); + }} + suffixIcon={ + + } + > + {' '} + {record.model_name}{' '} + + + {/**/} + {/* {*/} + {/* copyText(event, other.upstream_model_name).then(r => {});*/} + {/* }}*/} + {/* >*/} + {/* {' '}{other.upstream_model_name}{' '}*/} + {/* */} + {/**/} + + + ); + } + } + + // Define column keys for selection + const COLUMN_KEYS = { + TIME: 'time', + CHANNEL: 'channel', + USERNAME: 'username', + TOKEN: 'token', + GROUP: 'group', + TYPE: 'type', + MODEL: 'model', + USE_TIME: 'use_time', + PROMPT: 'prompt', + COMPLETION: 'completion', + COST: 'cost', + RETRY: 'retry', + DETAILS: 'details', + IP: 'ip', // Add IP column key + }; + + // State for column visibility + const [visibleColumns, setVisibleColumns] = useState({}); + const [showColumnSelector, setShowColumnSelector] = useState(false); + + // Load saved column preferences from localStorage + useEffect(() => { + const savedColumns = localStorage.getItem('logs-table-columns'); + if (savedColumns) { + try { + const parsed = JSON.parse(savedColumns); + // Make sure all columns are accounted for + const defaults = getDefaultColumnVisibility(); + const merged = { ...defaults, ...parsed }; + setVisibleColumns(merged); + } catch (e) { + console.error('Failed to parse saved column preferences', e); + initDefaultColumns(); + } + } else { + initDefaultColumns(); + } + }, []); + + // Get default column visibility based on user role + const getDefaultColumnVisibility = () => { + return { + [COLUMN_KEYS.TIME]: true, + [COLUMN_KEYS.CHANNEL]: isAdminUser, + [COLUMN_KEYS.USERNAME]: isAdminUser, + [COLUMN_KEYS.TOKEN]: true, + [COLUMN_KEYS.GROUP]: true, + [COLUMN_KEYS.TYPE]: true, + [COLUMN_KEYS.MODEL]: true, + [COLUMN_KEYS.USE_TIME]: true, + [COLUMN_KEYS.PROMPT]: true, + [COLUMN_KEYS.COMPLETION]: true, + [COLUMN_KEYS.COST]: true, + [COLUMN_KEYS.RETRY]: isAdminUser, + [COLUMN_KEYS.DETAILS]: true, + [COLUMN_KEYS.IP]: true, // IP column visible by default for all users + }; + }; + + // Initialize default column visibility + const initDefaultColumns = () => { + const defaults = getDefaultColumnVisibility(); + setVisibleColumns(defaults); + localStorage.setItem('logs-table-columns', JSON.stringify(defaults)); + }; + + // Handle column visibility change + const handleColumnVisibilityChange = (columnKey, checked) => { + const updatedColumns = { ...visibleColumns, [columnKey]: checked }; + setVisibleColumns(updatedColumns); + }; + + // Handle "Select All" checkbox + const handleSelectAll = (checked) => { + const allKeys = Object.keys(COLUMN_KEYS).map((key) => COLUMN_KEYS[key]); + const updatedColumns = {}; + + allKeys.forEach((key) => { + // For admin-only columns, only enable them if user is admin + if ( + (key === COLUMN_KEYS.CHANNEL || + key === COLUMN_KEYS.USERNAME || + key === COLUMN_KEYS.RETRY) && + !isAdminUser + ) { + updatedColumns[key] = false; + } else { + updatedColumns[key] = checked; + } + }); + + setVisibleColumns(updatedColumns); + }; + + // Define all columns + const allColumns = [ + { + key: COLUMN_KEYS.TIME, + title: t('时间'), + dataIndex: 'timestamp2string', + }, + { + key: COLUMN_KEYS.CHANNEL, + title: t('渠道'), + dataIndex: 'channel', + className: isAdmin() ? 'tableShow' : 'tableHiddle', + render: (text, record, index) => { + return isAdminUser ? ( + record.type === 0 || record.type === 2 || record.type === 6 ? ( +
+ { + + {' '} + {text}{' '} ({record.channel_name || '[未知]'}) + + } +
+ ) : ( + <> + ) + ) : ( + <> + ); + }, + }, + { + key: COLUMN_KEYS.USERNAME, + title: t('用户'), + dataIndex: 'username', + className: isAdmin() ? 'tableShow' : 'tableHiddle', + render: (text, record, index) => { + return isAdminUser ? ( +
+ { + event.stopPropagation(); + showUserInfo(record.user_id); + }} + > + {typeof text === 'string' && text.slice(0, 1)} + + {text} +
+ ) : ( + <> + ); + }, + }, + { + key: COLUMN_KEYS.TOKEN, + title: t('令牌'), + dataIndex: 'token_name', + render: (text, record, index) => { + return record.type === 0 || record.type === 2 || record.type === 6 ? ( +
+ { + //cancel the row click event + copyText(event, text); + }} + > + {' '} + {t(text)}{' '} + +
+ ) : ( + <> + ); + }, + }, + { + key: COLUMN_KEYS.GROUP, + title: t('分组'), + dataIndex: 'group', + render: (text, record, index) => { + if (record.type === 0 || record.type === 2 || record.type === 6) { + if (record.group) { + return <>{renderGroup(record.group)}; + } else { + let other = null; + try { + other = JSON.parse(record.other); + } catch (e) { + console.error( + `Failed to parse record.other: "${record.other}".`, + e, + ); + } + if (other === null) { + return <>; + } + if (other.group !== undefined) { + return <>{renderGroup(other.group)}; + } else { + return <>; + } + } + } else { + return <>; + } + }, + }, + { + key: COLUMN_KEYS.TYPE, + title: t('类型'), + dataIndex: 'type', + render: (text, record, index) => { + return <>{renderType(text)}; + }, + }, + { + key: COLUMN_KEYS.MODEL, + title: t('模型'), + dataIndex: 'model_name', + render: (text, record, index) => { + return record.type === 0 || record.type === 2 || record.type === 6 ? ( + <>{renderModelName(record)} + ) : ( + <> + ); + }, + }, + { + key: COLUMN_KEYS.USE_TIME, + title: t('用时/首字'), + dataIndex: 'use_time', + render: (text, record, index) => { + if (record.is_stream) { + let other = getLogOther(record.other); + return ( + <> + + {renderUseTime(text)} + {renderFirstUseTime(other?.frt)} + {renderIsStream(record.is_stream)} + + + ); + } else { + return ( + <> + + {renderUseTime(text)} + {renderIsStream(record.is_stream)} + + + ); + } + }, + }, + { + key: COLUMN_KEYS.PROMPT, + title: t('提示'), + dataIndex: 'prompt_tokens', + render: (text, record, index) => { + return record.type === 0 || record.type === 2 || record.type === 6 ? ( + <>{ {text} } + ) : ( + <> + ); + }, + }, + { + key: COLUMN_KEYS.COMPLETION, + title: t('补全'), + dataIndex: 'completion_tokens', + render: (text, record, index) => { + return parseInt(text) > 0 && + (record.type === 0 || record.type === 2 || record.type === 6) ? ( + <>{ {text} } + ) : ( + <> + ); + }, + }, + { + key: COLUMN_KEYS.COST, + title: t('花费'), + dataIndex: 'quota', + render: (text, record, index) => { + return record.type === 0 || record.type === 2 || record.type === 6 ? ( + <>{renderQuota(text, 6)} + ) : ( + <> + ); + }, + }, + + { + key: COLUMN_KEYS.RETRY, + title: t('重试'), + dataIndex: 'retry', + className: isAdmin() ? 'tableShow' : 'tableHiddle', + render: (text, record, index) => { + let content = t('渠道') + `:${record.channel}`; + if (record.other !== '') { + let other = JSON.parse(record.other); + if (other === null) { + return <>; + } + if (other.admin_info !== undefined) { + if ( + other.admin_info.use_channel !== null && + other.admin_info.use_channel !== undefined && + other.admin_info.use_channel !== '' + ) { + // channel id array + let useChannel = other.admin_info.use_channel; + let useChannelStr = useChannel.join('->'); + content = t('渠道') + `:${useChannelStr}`; + } + } + } + return isAdminUser ?
{content}
: <>; + }, + }, + { + key: COLUMN_KEYS.IP, + title: t('IP地址'), + dataIndex: 'client_ip', + className: 'tableShow', // Always show the column, visibility controlled by column selector + render: (text, record, index) => { + const isSupportedType = record.type === 2 || record.type === 6; + const hasIp = text && text.trim() !== ''; + + if (!isSupportedType || !hasIp) { + return <>; + } + + // Display IP with copy functionality + return ( + { + copyText(event, text); + }} + style={{ + fontFamily: 'monospace', + cursor: 'pointer' + }} + > + {text} + + ); + }, + }, + { + key: COLUMN_KEYS.DETAILS, + title: t('详情'), + dataIndex: 'content', + render: (text, record, index) => { + let other = getLogOther(record.other); + if (other == null || record.type !== 2) { + return ( + + {text} + + ); + } + + let content = other?.claude + ? renderClaudeModelPriceSimple( + other.model_ratio, + other.model_price, + other.group_ratio, + other.cache_tokens || 0, + other.cache_ratio || 1.0, + other.cache_creation_tokens || 0, + other.cache_creation_ratio || 1.0, + ) + : renderModelPriceSimple( + other.model_ratio, + other.model_price, + other.group_ratio, + other.cache_tokens || 0, + other.cache_ratio || 1.0, + ); + return ( + + {content} + + ); + }, + }, + ]; + + // Update table when column visibility changes + useEffect(() => { + if (Object.keys(visibleColumns).length > 0) { + // Save to localStorage + localStorage.setItem( + 'logs-table-columns', + JSON.stringify(visibleColumns), + ); + } + }, [visibleColumns]); + + // Filter columns based on visibility settings + const getVisibleColumns = () => { + return allColumns.filter((column) => visibleColumns[column.key]); + }; + + // Column selector modal + const renderColumnSelector = () => { + return ( + setShowColumnSelector(false)} + footer={ + <> + + + + + } + > +
+ v === true)} + indeterminate={ + Object.values(visibleColumns).some((v) => v === true) && + !Object.values(visibleColumns).every((v) => v === true) + } + onChange={(e) => handleSelectAll(e.target.checked)} + > + {t('全选')} + +
+
+ {allColumns.map((column) => { + // Skip admin-only columns for non-admin users + if ( + !isAdminUser && + (column.key === COLUMN_KEYS.CHANNEL || + column.key === COLUMN_KEYS.USERNAME || + column.key === COLUMN_KEYS.RETRY) + ) { + return null; + } + + return ( +
+ + handleColumnVisibilityChange(column.key, e.target.checked) + } + > + {column.title} + +
+ ); + })} +
+
+ ); + }; + + const [styleState, styleDispatch] = useContext(StyleContext); + const [logs, setLogs] = useState([]); + const [expandData, setExpandData] = useState({}); + const [showStat, setShowStat] = useState(false); + const [loading, setLoading] = useState(false); + const [loadingStat, setLoadingStat] = useState(false); + const [activePage, setActivePage] = useState(1); + const [logCount, setLogCount] = useState(ITEMS_PER_PAGE); + const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); + const [logType, setLogType] = useState(0); + const isAdminUser = isAdmin(); + let now = new Date(); + // 初始化start_timestamp为今天0点 + const [inputs, setInputs] = useState({ + username: '', + token_name: '', + model_name: '', + start_timestamp: timestamp2string(getTodayStartTimestamp()), + end_timestamp: timestamp2string(now.getTime() / 1000 + 3600), + channel: '', + group: '', + }); + const { + username, + token_name, + model_name, + start_timestamp, + end_timestamp, + channel, + group, + } = inputs; + + const [stat, setStat] = useState({ + quota: 0, + token: 0, + token_total: 0, + token_input: 0, + token_output: 0, + rpm: 0, + tpm: 0 + }); + + const handleInputChange = (value, name) => { + setInputs((inputs) => ({ ...inputs, [name]: value })); + }; + + const getLogSelfStat = async () => { + let localStartTimestamp = Date.parse(start_timestamp) / 1000; + let localEndTimestamp = Date.parse(end_timestamp) / 1000; + let url = `/api/log/self/stat?type=${logType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&group=${group}`; + url = encodeURI(url); + let res = await API.get(url); + const { success, message, data } = res.data; + if (success) { + // 保留现有的token统计信息,只更新API返回的数据 + setStat(prevStat => ({ + ...prevStat, + quota: data.quota || 0, + rpm: data.rpm || 0, + tpm: data.tpm || 0 + })); + } else { + showError(message); + } + }; + + const getLogStat = async () => { + let localStartTimestamp = Date.parse(start_timestamp) / 1000; + let localEndTimestamp = Date.parse(end_timestamp) / 1000; + let url = `/api/log/stat?type=${logType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}&group=${group}`; + url = encodeURI(url); + let res = await API.get(url); + const { success, message, data } = res.data; + if (success) { + // 保留现有的token统计信息,只更新API返回的数据 + setStat(prevStat => ({ + ...prevStat, + quota: data.quota || 0, + rpm: data.rpm || 0, + tpm: data.tpm || 0 + })); + } else { + showError(message); + } + }; + + const handleEyeClick = async () => { + if (loadingStat) { + return; + } + setLoadingStat(true); + if (isAdminUser) { + await getLogStat(); + } else { + await getLogSelfStat(); + } + setShowStat(true); + setLoadingStat(false); + }; + + const showUserInfo = async (userId) => { + if (!isAdminUser) { + return; + } + const res = await API.get(`/api/user/${userId}`); + const { success, message, data } = res.data; + if (success) { + Modal.info({ + title: t('用户信息'), + content: ( +
+

+ {t('用户名')}: {data.username} +

+

+ {t('余额')}: {renderQuota(data.quota)} +

+

+ {t('已用额度')}:{renderQuota(data.used_quota)} +

+

+ {t('请求次数')}:{renderNumber(data.request_count)} +

+
+ ), + centered: true, + }); + } else { + showError(message); + } + }; + + const setLogsFormat = (logs) => { + let expandDatesLocal = {}; + // Calculate total token counts for currently displayed logs + let totalTokens = 0; + let totalInputTokens = 0; + let totalOutputTokens = 0; + + for (let i = 0; i < logs.length; i++) { + logs[i].timestamp2string = timestamp2string(logs[i].created_at); + logs[i].key = logs[i].id; + let other = getLogOther(logs[i].other); + + // Sum up tokens from current logs + if (logs[i].type === 0 || logs[i].type === 2) { + const promptTokens = parseInt(logs[i].prompt_tokens) || 0; + const completionTokens = parseInt(logs[i].completion_tokens) || 0; + totalInputTokens += promptTokens; + totalOutputTokens += completionTokens; + totalTokens += promptTokens + completionTokens; + } + + let expandDataLocal = []; + if (isAdmin()) { + // let content = '渠道:' + logs[i].channel; + // if (other.admin_info !== undefined) { + // if ( + // other.admin_info.use_channel !== null && + // other.admin_info.use_channel !== undefined && + // other.admin_info.use_channel !== '' + // ) { + // // channel id array + // let useChannel = other.admin_info.use_channel; + // let useChannelStr = useChannel.join('->'); + // content = `渠道:${useChannelStr}`; + // } + // } + // expandDataLocal.push({ + // key: '渠道重试', + // value: content, + // }) + } + if (isAdminUser && (logs[i].type === 0 || logs[i].type === 2)) { + expandDataLocal.push({ + key: t('渠道信息'), + value: `${logs[i].channel} - ${logs[i].channel_name || '[未知]'}`, + }); + } + if (other?.ws || other?.audio) { + expandDataLocal.push({ + key: t('语音输入'), + value: other.audio_input, + }); + expandDataLocal.push({ + key: t('语音输出'), + value: other.audio_output, + }); + expandDataLocal.push({ + key: t('文字输入'), + value: other.text_input, + }); + expandDataLocal.push({ + key: t('文字输出'), + value: other.text_output, + }); + } + if (other?.cache_tokens > 0) { + expandDataLocal.push({ + key: t('缓存 Tokens'), + value: other.cache_tokens, + }); + } + if (other?.cache_creation_tokens > 0) { + expandDataLocal.push({ + key: t('缓存创建 Tokens'), + value: other.cache_creation_tokens, + }); + } + if (logs[i].type === 2) { + expandDataLocal.push({ + key: t('日志详情'), + value: other?.claude + ? renderClaudeLogContent( + other?.model_ratio, + other.completion_ratio, + other.model_price, + other.group_ratio, + other.user_group_ratio, + other.cache_ratio || 1.0, + other.cache_creation_ratio || 1.0, + ) + : renderLogContent( + other?.model_ratio, + other.completion_ratio, + other.model_price, + other.group_ratio, + other.user_group_ratio, + ), + }); + } + if (logs[i].type === 2) { + let modelMapped = + other?.is_model_mapped && + other?.upstream_model_name && + other?.upstream_model_name !== ''; + if (modelMapped) { + expandDataLocal.push({ + key: t('请求并计费模型'), + value: logs[i].model_name, + }); + expandDataLocal.push({ + key: t('实际模型'), + value: other.upstream_model_name, + }); + } + let content = ''; + if (other?.ws || other?.audio) { + content = renderAudioModelPrice( + other?.text_input, + other?.text_output, + other?.model_ratio, + other?.model_price, + other?.completion_ratio, + other?.audio_input, + other?.audio_output, + other?.audio_ratio, + other?.audio_completion_ratio, + other?.group_ratio, + other?.cache_tokens || 0, + other?.cache_ratio || 1.0, + ); + } else if (other?.claude) { + content = renderClaudeModelPrice( + logs[i].prompt_tokens, + logs[i].completion_tokens, + other.model_ratio, + other.model_price, + other.completion_ratio, + other.group_ratio, + other.cache_tokens || 0, + other.cache_ratio || 1.0, + other.cache_creation_tokens || 0, + other.cache_creation_ratio || 1.0, + ); + } else { + content = renderModelPrice( + logs[i].prompt_tokens, + logs[i].completion_tokens, + other?.model_ratio, + other?.model_price, + other?.completion_ratio, + other?.group_ratio, + other?.cache_tokens || 0, + other?.cache_ratio || 1.0, + ); + } + expandDataLocal.push({ + key: t('计费过程'), + value: content, + }); + if (other?.reasoning_effort) { + expandDataLocal.push({ + key: t('Reasoning Effort'), + value: other.reasoning_effort, + }); + } + } + + // 添加错误详细信息 + if (logs[i].type === 6) { // 错误类型 + if (other?.error_type) { + expandDataLocal.push({ + key: t('错误类型'), + value: other.error_type, + }); + } + if (other?.error_code) { + expandDataLocal.push({ + key: t('错误代码'), + value: other.error_code, + }); + } + if (other?.status_code) { + expandDataLocal.push({ + key: t('状态码'), + value: other.status_code, + }); + } + if (other?.channel_name) { + expandDataLocal.push({ + key: t('错误渠道'), + value: `${other.channel_id} - ${other.channel_name}`, + }); + } + // 添加客户端IP(如果有) + if (logs[i].client_ip && logs[i].client_ip.trim() !== '') { + expandDataLocal.push({ + key: t('客户端IP'), + value: ( + { + copyText(event, logs[i].client_ip); + }} + style={{ + fontFamily: 'monospace', + cursor: 'pointer' + }} + > + {logs[i].client_ip} + + ), + }); + } + expandDataLocal.push({ + key: t('错误详情'), + value: ( + + {logs[i].content} + + ), + }); + } + + // 添加上下文、输入和输出内容 + if (logs[i].type === 2) { // 消费类型 + let other = getLogOther(logs[i].other); + + // 添加系统提示信息(如果有) + if (other?.system_prompt) { + expandDataLocal.push({ + key: t('系统提示'), + value: ( + + {other.system_prompt} + + ), + }); + } + + // 添加上下文信息(如果有) + if (other?.context) { + let contextStr = ""; + + if (Array.isArray(other.context)) { + // 如果是消息数组,格式化显示每条消息 + if (other.context.length > 0) { + contextStr = other.context.map(msg => { + if (typeof msg === 'object' && msg !== null && msg.role && msg.content) { + // 根据角色格式化消息 + let roleDisplay = '未知'; + switch(msg.role) { + case 'user': + roleDisplay = '用户'; + break; + case 'assistant': + roleDisplay = '助手'; + break; + case 'system': + roleDisplay = '系统'; + break; + default: + roleDisplay = msg.role; + } + return `${roleDisplay}消息: ${msg.content}`; + } else { + // 无法解析的对象 + return JSON.stringify(msg, null, 2); + } + }).join('\n\n'); // 使用双换行分隔不同消息,提高可读性 + } else { + contextStr = "空上下文"; + } + } else if (typeof other.context === 'string') { + contextStr = other.context; + } else { + contextStr = JSON.stringify(other.context, null, 2); + } + + expandDataLocal.push({ + key: t('上下文'), + value: ( + + {contextStr} + + ), + }); + } + + // 添加输入内容 + if (other?.input_content) { + // 格式化用户输入消息 + let inputContent = ""; + if (typeof other.input_content === 'object' && other.input_content !== null) { + if (other.input_content.role === 'user' && other.input_content.content) { + // 用户消息格式化显示 + inputContent = `用户消息: ${other.input_content.content}`; + } else if (other.input_content.content) { + // 其他角色但有content + const role = other.input_content.role || '未知'; + inputContent = `${role}消息: ${other.input_content.content}`; + } else { + // 无法解析的对象 + inputContent = JSON.stringify(other.input_content, null, 2); + } + } else if (typeof other.input_content === 'string') { + inputContent = other.input_content; + } else { + inputContent = JSON.stringify(other.input_content, null, 2); + } + + expandDataLocal.push({ + key: t('输入'), + value: ( + + {inputContent} + + ), + }); + } + + // 添加输出内容 + if (other?.output_content) { + expandDataLocal.push({ + key: t('输出'), + value: ( + + {other.output_content} + + ), + }); + } + + // 添加客户端IP(如果有) + if (logs[i].client_ip && logs[i].client_ip.trim() !== '') { + expandDataLocal.push({ + key: t('客户端IP'), + value: ( + { + copyText(event, logs[i].client_ip); + }} + style={{ + fontFamily: 'monospace', + cursor: 'pointer' + }} + > + {logs[i].client_ip} + + ), + }); + } + } + + expandDatesLocal[logs[i].key] = expandDataLocal; + } + + // Update state with token sums + setStat(prevStat => ({ + ...prevStat, + token_total: totalTokens, + token_input: totalInputTokens, + token_output: totalOutputTokens + })); + + setExpandData(expandDatesLocal); + setLogs(logs); + }; + + const loadLogs = async (startIdx, pageSize, logType = 0) => { + setLoading(true); + + let url = ''; + let localStartTimestamp = Date.parse(start_timestamp) / 1000; + let localEndTimestamp = Date.parse(end_timestamp) / 1000; + if (isAdminUser) { + url = `/api/log/?p=${startIdx}&page_size=${pageSize}&type=${logType}&username=${username}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&channel=${channel}&group=${group}`; + } else { + url = `/api/log/self/?p=${startIdx}&page_size=${pageSize}&type=${logType}&token_name=${token_name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}&group=${group}`; + } + url = encodeURI(url); + const res = await API.get(url); + const { success, message, data } = res.data; + if (success) { + const newPageData = data.items; + setActivePage(data.page); + setPageSize(data.page_size); + setLogCount(data.total); + + setLogsFormat(newPageData); + } else { + showError(message); + } + setLoading(false); + }; + + const handlePageChange = (page) => { + setActivePage(page); + loadLogs(page, pageSize, logType).then((r) => {}); + }; + + const handlePageSizeChange = async (size) => { + localStorage.setItem('page-size', size + ''); + setPageSize(size); + setActivePage(1); + loadLogs(activePage, size) + .then() + .catch((reason) => { + showError(reason); + }); + }; + + const refresh = async () => { + setActivePage(1); + handleEyeClick(); + await loadLogs(activePage, pageSize, logType); + }; + + const copyText = async (e, text) => { + e.stopPropagation(); + if (await copy(text)) { + showSuccess('已复制:' + text); + } else { + Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text }); + } + }; + + useEffect(() => { + const localPageSize = + parseInt(localStorage.getItem('page-size')) || ITEMS_PER_PAGE; + setPageSize(localPageSize); + loadLogs(activePage, localPageSize) + .then() + .catch((reason) => { + showError(reason); + }); + handleEyeClick(); + }, []); + + const expandRowRender = (record, index) => { + return ; + }; + + return ( + <> + {renderColumnSelector()} + +
+ + + + {t('消耗额度')}: {renderQuota(stat.quota)} + + + {t('总Token')}: {stat.token_total || 0} + + + {t('输入Token')}: {stat.token_input || 0} + + + {t('输出Token')}: {stat.token_output || 0} + + + RPM: {stat.rpm} + + + TPM: {stat.tpm} + + + +
+
+ <> + +
+ {styleState.isMobile ? ( +
+ { + console.log(value); + handleInputChange(value, 'start_timestamp'); + }} + /> + + handleInputChange(value, 'end_timestamp') + } + /> +
+ ) : ( + { + if (Array.isArray(value) && value.length === 2) { + handleInputChange(value[0], 'start_timestamp'); + handleInputChange(value[1], 'end_timestamp'); + } + }} + /> + )} +
+
+ handleInputChange(value, 'token_name')} + /> + handleInputChange(value, 'model_name')} + /> + handleInputChange(value, 'group')} + /> + {isAdminUser && ( + <> + handleInputChange(value, 'channel')} + /> + handleInputChange(value, 'username')} + /> + + )} + + + + + +
+ + +
+
+ t('第 {{start}} - {{end}} 条,共 {{total}} 条', { + start: page.currentStart, + end: page.currentEnd, + total: logCount, + }), + currentPage: activePage, + pageSize: pageSize, + total: logCount, + pageSizeOpts: [10, 20, 50, 100], + showSizeChanger: true, + onPageSizeChange: (size) => { + handlePageSizeChange(size); + }, + onPageChange: handlePageChange, + }} + /> + + + ); +}; + +export default LogsTable; diff --git a/web/src/components/MjLogsTable.js b/web/src/components/MjLogsTable.js new file mode 100644 index 0000000000000000000000000000000000000000..a69dcf7ceb2b91c7b12076376c270bfb0f1af2d3 --- /dev/null +++ b/web/src/components/MjLogsTable.js @@ -0,0 +1,678 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { + API, + copy, + isAdmin, + showError, + showSuccess, + timestamp2string, +} from '../helpers'; + +import { + Banner, + Button, + Form, + ImagePreview, + Layout, + Modal, + Progress, + Table, + Tag, + Typography, +} from '@douyinfe/semi-ui'; +import { ITEMS_PER_PAGE } from '../constants'; +import { useTranslation } from 'react-i18next'; + +const colors = [ + 'amber', + 'blue', + 'cyan', + 'green', + 'grey', + 'indigo', + 'light-blue', + 'lime', + 'orange', + 'pink', + 'purple', + 'red', + 'teal', + 'violet', + 'yellow', +]; + +const LogsTable = () => { + const { t } = useTranslation(); + const [isModalOpen, setIsModalOpen] = useState(false); + const [modalContent, setModalContent] = useState(''); + function renderType(type) { + switch (type) { + case 'IMAGINE': + return ( + + {t('绘图')} + + ); + case 'UPSCALE': + return ( + + {t('放大')} + + ); + case 'VARIATION': + return ( + + {t('变换')} + + ); + case 'HIGH_VARIATION': + return ( + + {t('强变换')} + + ); + case 'LOW_VARIATION': + return ( + + {t('弱变换')} + + ); + case 'PAN': + return ( + + {t('平移')} + + ); + case 'DESCRIBE': + return ( + + {t('图生文')} + + ); + case 'BLEND': + return ( + + {t('图混合')} + + ); + case 'UPLOAD': + return ( + + 上传文件 + + ); + case 'SHORTEN': + return ( + + {t('缩词')} + + ); + case 'REROLL': + return ( + + {t('重绘')} + + ); + case 'INPAINT': + return ( + + {t('局部重绘-提交')} + + ); + case 'ZOOM': + return ( + + {t('变焦')} + + ); + case 'CUSTOM_ZOOM': + return ( + + {t('自定义变焦-提交')} + + ); + case 'MODAL': + return ( + + {t('窗口处理')} + + ); + case 'SWAP_FACE': + return ( + + {t('换脸')} + + ); + default: + return ( + + {t('未知')} + + ); + } + } + + function renderCode(code) { + switch (code) { + case 1: + return ( + + {t('已提交')} + + ); + case 21: + return ( + + {t('等待中')} + + ); + case 22: + return ( + + {t('重复提交')} + + ); + case 0: + return ( + + {t('未提交')} + + ); + default: + return ( + + {t('未知')} + + ); + } + } + + function renderStatus(type) { + switch (type) { + case 'SUCCESS': + return ( + + {t('成功')} + + ); + case 'NOT_START': + return ( + + {t('未启动')} + + ); + case 'SUBMITTED': + return ( + + {t('队列中')} + + ); + case 'IN_PROGRESS': + return ( + + {t('执行中')} + + ); + case 'FAILURE': + return ( + + {t('失败')} + + ); + case 'MODAL': + return ( + + {t('窗口等待')} + + ); + default: + return ( + + {t('未知')} + + ); + } + } + + const renderTimestamp = (timestampInSeconds) => { + const date = new Date(timestampInSeconds * 1000); // 从秒转换为毫秒 + + const year = date.getFullYear(); // 获取年份 + const month = ('0' + (date.getMonth() + 1)).slice(-2); // 获取月份,从0开始需要+1,并保证两位数 + const day = ('0' + date.getDate()).slice(-2); // 获取日期,并保证两位数 + const hours = ('0' + date.getHours()).slice(-2); // 获取小时,并保证两位数 + const minutes = ('0' + date.getMinutes()).slice(-2); // 获取分钟,并保证两位数 + const seconds = ('0' + date.getSeconds()).slice(-2); // 获取秒钟,并保证两位数 + + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; // 格式化输出 + }; + // 修改renderDuration函数以包含颜色逻辑 + function renderDuration(submit_time, finishTime) { + if (!submit_time || !finishTime) return 'N/A'; + + const start = new Date(submit_time); + const finish = new Date(finishTime); + const durationMs = finish - start; + const durationSec = (durationMs / 1000).toFixed(1); + const color = durationSec > 60 ? 'red' : 'green'; + + return ( + + {durationSec} {t('秒')} + + ); + } + const columns = [ + { + title: t('提交时间'), + dataIndex: 'submit_time', + render: (text, record, index) => { + return
{renderTimestamp(text / 1000)}
; + }, + }, + { + title: t('花费时间'), + dataIndex: 'finish_time', // 以finish_time作为dataIndex + key: 'finish_time', + render: (finish, record) => { + // 假设record.start_time是存在的,并且finish是完成时间的时间戳 + return renderDuration(record.submit_time, finish); + }, + }, + { + title: t('渠道'), + dataIndex: 'channel_id', + className: isAdmin() ? 'tableShow' : 'tableHiddle', + render: (text, record, index) => { + return ( +
+ { + copyText(text); // 假设copyText是用于文本复制的函数 + }} + > + {' '} + {text}{' '} + +
+ ); + }, + }, + { + title: t('类型'), + dataIndex: 'action', + render: (text, record, index) => { + return
{renderType(text)}
; + }, + }, + { + title: t('任务ID'), + dataIndex: 'mj_id', + render: (text, record, index) => { + return
{text}
; + }, + }, + { + title: t('提交结果'), + dataIndex: 'code', + className: isAdmin() ? 'tableShow' : 'tableHiddle', + render: (text, record, index) => { + return
{renderCode(text)}
; + }, + }, + { + title: t('任务状态'), + dataIndex: 'status', + className: isAdmin() ? 'tableShow' : 'tableHiddle', + render: (text, record, index) => { + return
{renderStatus(text)}
; + }, + }, + { + title: t('进度'), + dataIndex: 'progress', + render: (text, record, index) => { + return ( +
+ { + // 转换例如100%为数字100,如果text未定义,返回0 + + } +
+ ); + }, + }, + { + title: t('结果图片'), + dataIndex: 'image_url', + render: (text, record, index) => { + if (!text) { + return t('无'); + } + return ( + + ); + }, + }, + { + title: 'Prompt', + dataIndex: 'prompt', + render: (text, record, index) => { + // 如果text未定义,返回替代文本,例如空字符串''或其他 + if (!text) { + return t('无'); + } + + return ( + { + setModalContent(text); + setIsModalOpen(true); + }} + > + {text} + + ); + }, + }, + { + title: 'PromptEn', + dataIndex: 'prompt_en', + render: (text, record, index) => { + // 如果text未定义,返回替代文本,例如空字符串''或其他 + if (!text) { + return t('无'); + } + + return ( + { + setModalContent(text); + setIsModalOpen(true); + }} + > + {text} + + ); + }, + }, + { + title: t('失败原因'), + dataIndex: 'fail_reason', + render: (text, record, index) => { + // 如果text未定义,返回替代文本,例如空字符串''或其他 + if (!text) { + return t('无'); + } + + return ( + { + setModalContent(text); + setIsModalOpen(true); + }} + > + {text} + + ); + }, + }, + ]; + + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(true); + const [activePage, setActivePage] = useState(1); + const [logCount, setLogCount] = useState(ITEMS_PER_PAGE); + const [logType, setLogType] = useState(0); + const isAdminUser = isAdmin(); + const [isModalOpenurl, setIsModalOpenurl] = useState(false); + const [showBanner, setShowBanner] = useState(false); + + // 定义模态框图片URL的状态和更新函数 + const [modalImageUrl, setModalImageUrl] = useState(''); + let now = new Date(); + // 初始化start_timestamp为前一天 + const [inputs, setInputs] = useState({ + channel_id: '', + mj_id: '', + start_timestamp: timestamp2string(now.getTime() / 1000 - 2592000), + end_timestamp: timestamp2string(now.getTime() / 1000 + 3600), + }); + const { channel_id, mj_id, start_timestamp, end_timestamp } = inputs; + + const [stat, setStat] = useState({ + quota: 0, + token: 0, + }); + + const handleInputChange = (value, name) => { + setInputs((inputs) => ({ ...inputs, [name]: value })); + }; + + const setLogsFormat = (logs) => { + for (let i = 0; i < logs.length; i++) { + logs[i].timestamp2string = timestamp2string(logs[i].created_at); + logs[i].key = '' + logs[i].id; + } + // data.key = '' + data.id + setLogs(logs); + setLogCount(logs.length + ITEMS_PER_PAGE); + // console.log(logCount); + }; + + const loadLogs = async (startIdx) => { + setLoading(true); + + let url = ''; + let localStartTimestamp = Date.parse(start_timestamp); + let localEndTimestamp = Date.parse(end_timestamp); + if (isAdminUser) { + url = `/api/mj/?p=${startIdx}&channel_id=${channel_id}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; + } else { + url = `/api/mj/self/?p=${startIdx}&mj_id=${mj_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; + } + const res = await API.get(url); + const { success, message, data } = res.data; + if (success) { + if (startIdx === 0) { + setLogsFormat(data); + } else { + let newLogs = [...logs]; + newLogs.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data); + setLogsFormat(newLogs); + } + } else { + showError(message); + } + setLoading(false); + }; + + const pageData = logs.slice( + (activePage - 1) * ITEMS_PER_PAGE, + activePage * ITEMS_PER_PAGE, + ); + + const handlePageChange = (page) => { + setActivePage(page); + if (page === Math.ceil(logs.length / ITEMS_PER_PAGE) + 1) { + // In this case we have to load more data and then append them. + loadLogs(page - 1).then((r) => {}); + } + }; + + const refresh = async () => { + // setLoading(true); + setActivePage(1); + await loadLogs(0); + }; + + const copyText = async (text) => { + if (await copy(text)) { + showSuccess('已复制:' + text); + } else { + // setSearchKeyword(text); + Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text }); + } + }; + + useEffect(() => { + refresh().then(); + }, [logType]); + + useEffect(() => { + const mjNotifyEnabled = localStorage.getItem('mj_notify_enabled'); + if (mjNotifyEnabled !== 'true') { + setShowBanner(true); + } + }, []); + + return ( + <> + + {isAdminUser && showBanner ? ( + + ) : ( + <> + )} +
+ <> + handleInputChange(value, 'channel_id')} + /> + handleInputChange(value, 'mj_id')} + /> + handleInputChange(value, 'start_timestamp')} + /> + handleInputChange(value, 'end_timestamp')} + /> + + + + + + +
+ t('第 {{start}} - {{end}} 条,共 {{total}} 条', { + start: page.currentStart, + end: page.currentEnd, + total: logCount, + }), + }} + loading={loading} + /> + setIsModalOpen(false)} + onCancel={() => setIsModalOpen(false)} + closable={null} + bodyStyle={{ height: '400px', overflow: 'auto' }} // 设置模态框内容区域样式 + width={800} // 设置模态框宽度 + > +

{modalContent}

+
+ setIsModalOpenurl(visible)} + /> + + + ); +}; + +export default LogsTable; diff --git a/web/src/components/ModelPricing.js b/web/src/components/ModelPricing.js new file mode 100644 index 0000000000000000000000000000000000000000..5eaf61a1ba0d382ae3686a4a74c752aeabc7039b --- /dev/null +++ b/web/src/components/ModelPricing.js @@ -0,0 +1,466 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useContext, useEffect, useRef, useMemo, useState } from 'react'; +import { API, copy, showError, showInfo, showSuccess } from '../helpers'; +import { useTranslation } from 'react-i18next'; + +import { + Banner, + Input, + Layout, + Modal, + Space, + Table, + Tag, + Tooltip, + Popover, + ImagePreview, + Button, + Switch, +} from '@douyinfe/semi-ui'; +import { + IconMore, + IconVerify, + IconUploadError, + IconHelpCircle, +} from '@douyinfe/semi-icons'; +import { UserContext } from '../context/User/index.js'; +import Text from '@douyinfe/semi-ui/lib/es/typography/text'; +import Decimal from 'decimal.js'; + +const ModelPricing = () => { + const { t } = useTranslation(); + const [filteredValue, setFilteredValue] = useState([]); + const compositionRef = useRef({ isComposition: false }); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [modalImageUrl, setModalImageUrl] = useState(''); + const [isModalOpenurl, setIsModalOpenurl] = useState(false); + const [selectedGroup, setSelectedGroup] = useState('default'); + const [showRatio, setShowRatio] = useState(false); + const [hideUnavailable, setHideUnavailable] = useState(true); + + const rowSelection = useMemo( + () => ({ + onChange: (selectedRowKeys, selectedRows) => { + setSelectedRowKeys(selectedRowKeys); + }, + }), + [], + ); + + const handleChange = (value) => { + if (compositionRef.current.isComposition) { + return; + } + const newFilteredValue = value ? [value] : []; + setFilteredValue(newFilteredValue); + }; + const handleCompositionStart = () => { + compositionRef.current.isComposition = true; + }; + + const handleCompositionEnd = (event) => { + compositionRef.current.isComposition = false; + const value = event.target.value; + const newFilteredValue = value ? [value] : []; + setFilteredValue(newFilteredValue); + }; + + function renderQuotaType(type) { + // Ensure all cases are string literals by adding quotes. + switch (type) { + case 1: + return ( + + {t('按次计费')} + + ); + case 0: + return ( + + {t('按量计费')} + + ); + default: + return t('未知'); + } + } + + function renderAvailable(available) { + return ( + {t('您的分组可以使用该模型')} + } + position='top' + key={available} + style={{ + backgroundColor: 'rgba(var(--semi-blue-4),1)', + borderColor: 'rgba(var(--semi-blue-4),1)', + color: 'var(--semi-color-white)', + borderWidth: 1, + borderStyle: 'solid', + }} + > + + + ); + } + + const columns = [ + { + title: t('可用性'), + dataIndex: 'available', + render: (text, record, index) => { + // if record.enable_groups contains selectedGroup, then available is true + return renderAvailable(record.enable_groups.includes(selectedGroup)); + }, + sorter: (a, b) => a.available - b.available, + }, + { + title: t('模型名称'), + dataIndex: 'model_name', + render: (text, record, index) => { + return ( + <> + { + copyText(text); + }} + > + {text} + + + ); + }, + onFilter: (value, record) => + record.model_name.toLowerCase().includes(value.toLowerCase()), + filteredValue, + }, + { + title: t('计费类型'), + dataIndex: 'quota_type', + render: (text, record, index) => { + return renderQuotaType(parseInt(text)); + }, + sorter: (a, b) => a.quota_type - b.quota_type, + }, + { + title: t('可用分组'), + dataIndex: 'enable_groups', + render: (text, record, index) => { + // enable_groups is a string array + return ( + + {text.map((group) => { + if (usableGroup[group]) { + if (group === selectedGroup) { + return ( + }> + {group} + + ); + } else { + return ( + { + setSelectedGroup(group); + showInfo( + t('当前查看的分组为:{{group}},倍率为:{{ratio}}', { + group: group, + ratio: groupRatio[group], + }), + ); + }} + > + {group} + + ); + } + } + })} + + ); + }, + }, + { + title: () => ( + + {t('倍率')} + + {t('倍率是为了方便换算不同价格的模型')} +
+ {t('点击查看倍率说明')} + + } + position='top' + style={{ + backgroundColor: 'rgba(var(--semi-blue-4),1)', + borderColor: 'rgba(var(--semi-blue-4),1)', + color: 'var(--semi-color-white)', + borderWidth: 1, + borderStyle: 'solid', + }} + > + { + setModalImageUrl('/ratio.png'); + setIsModalOpenurl(true); + }} + /> +
+
+ ), + dataIndex: 'model_ratio', + render: (text, record, index) => { + let content = text; + let completionRatio = parseFloat(record.completion_ratio.toFixed(3)); + content = ( + <> + + {t('模型倍率')}:{record.quota_type === 0 ? text : t('无')} + +
+ + {t('补全倍率')}: + {record.quota_type === 0 ? completionRatio : t('无')} + +
+ + {t('分组倍率')}:{groupRatio[selectedGroup]} + + + ); + return
{content}
; + }, + }, + { + title: t('模型价格'), + dataIndex: 'model_price', + render: (text, record, index) => { + let content = text; + if (record.quota_type === 0) { + // 这里的 *2 是因为 1倍率=0.002刀,请勿删除 + let inputRatioPrice = new Decimal(record.model_ratio).mul(2).mul(groupRatio[selectedGroup]).toFixed(3); + let completionRatioPrice = new Decimal(record.model_ratio).mul(record.completion_ratio).mul(2).mul(groupRatio[selectedGroup]).toFixed(3); + content = ( + <> + + {t('提示')} ${inputRatioPrice} / 1M tokens + +
+ + {t('补全')} ${completionRatioPrice} / 1M tokens + + + ); + } else { + let price = new Decimal(text).mul(groupRatio[selectedGroup]).toFixed(3); + content = ( + <> + {t('模型价格')}:${price} + + ); + } + return
{content}
; + }, + }, + ]; + + const [models, setModels] = useState([]); + const [loading, setLoading] = useState(true); + const [userState, userDispatch] = useContext(UserContext); + const [groupRatio, setGroupRatio] = useState({}); + const [usableGroup, setUsableGroup] = useState({}); + + const setModelsFormat = (models, groupRatio) => { + for (let i = 0; i < models.length; i++) { + models[i].key = models[i].model_name; + models[i].group_ratio = groupRatio[models[i].model_name]; + } + // sort by quota_type + models.sort((a, b) => { + return a.quota_type - b.quota_type; + }); + + // sort by model_name, start with gpt is max, other use localeCompare + models.sort((a, b) => { + if (a.model_name.startsWith('gpt') && !b.model_name.startsWith('gpt')) { + return -1; + } else if ( + !a.model_name.startsWith('gpt') && + b.model_name.startsWith('gpt') + ) { + return 1; + } else { + return a.model_name.localeCompare(b.model_name); + } + }); + + setModels(models); + }; + + const loadPricing = async () => { + setLoading(true); + + let url = ''; + url = `/api/pricing`; + const res = await API.get(url); + const { success, message, data, group_ratio, usable_group } = res.data; + if (success) { + setGroupRatio(group_ratio); + setUsableGroup(usable_group); + setSelectedGroup(userState.user ? userState.user.group : 'default'); + setModelsFormat(data, group_ratio); + } else { + showError(message); + } + setLoading(false); + }; + + const refresh = async () => { + await loadPricing(); + }; + + const copyText = async (text) => { + if (await copy(text)) { + showSuccess('已复制:' + text); + } else { + // setSearchKeyword(text); + Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text }); + } + }; + + useEffect(() => { + refresh().then(); + }, []); + + return ( + <> + + {userState.user ? ( + + ) : ( + + )} +
+ + {t( + '按量计费费用 = 分组倍率 × 模型倍率 × (提示token数 + 补全token数 × 补全倍率)/ 500000 (单位:美元)', + )} + + } + closeIcon='null' + /> +
+ + + + + {t('显示倍率')}: + + + + {t('隐藏不可用模型')}: + + + +
{ + if (column.dataIndex === 'available' && hideUnavailable) return false; + if (column.dataIndex === 'model_ratio' && !showRatio) return false; + return true; + })} + dataSource={models.filter(model => { + if (hideUnavailable && !model.enable_groups.includes(selectedGroup)) return false; + return true; + })} + loading={loading} + pagination={{ + formatPageText: (page) => + t('第 {{start}} - {{end}} 条,共 {{total}} 条', { + start: page.currentStart, + end: page.currentEnd, + total: models.length, + }), + pageSize: models.length, + showSizeChanger: false, + }} + rowSelection={rowSelection} + /> + setIsModalOpenurl(visible)} + /> + + + ); +}; + +export default ModelPricing; diff --git a/web/src/components/ModelSetting.js b/web/src/components/ModelSetting.js new file mode 100644 index 0000000000000000000000000000000000000000..88ffbb71ba32293873ec13c9f69d6c9601be2e33 --- /dev/null +++ b/web/src/components/ModelSetting.js @@ -0,0 +1,127 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { Card, Spin, Tabs } from '@douyinfe/semi-ui'; + +import { API, showError, showSuccess } from '../helpers'; +import { useTranslation } from 'react-i18next'; +import SettingGeminiModel from '../pages/Setting/Model/SettingGeminiModel'; +import SettingClaudeModel from '../pages/Setting/Model/SettingClaudeModel'; +import SettingGlobalModel from '../pages/Setting/Model/SettingGlobalModel'; +import SettingModelMapping from '../pages/Setting/Model/SettingModelMapping'; + +const ModelSetting = () => { + const { t } = useTranslation(); + let [inputs, setInputs] = useState({ + 'gemini.safety_settings': '', + 'gemini.version_settings': '', + 'gemini.supported_imagine_models': '', + 'claude.model_headers_settings': '', + 'claude.thinking_adapter_enabled': true, + 'claude.default_max_tokens': '', + 'claude.thinking_adapter_budget_tokens_percentage': 0.8, + 'global.pass_through_request_enabled': false, + 'global.hide_upstream_error_enabled': false, + 'global.block_browser_extension_enabled': false, + 'global.rate_limit_exempt_enabled': false, + 'global.rate_limit_exempt_group': 'bulk-ok', + 'global.safe_check_exempt_enabled': false, + 'global.safe_check_exempt_group': 'nsfw-ok', + 'global.auto_retry_enabled': false, + 'global.auto_retry_count': 3, + 'global.auto_retry_force_channel_switch': false, + 'global.auto_retry_status_codes': '5xx,4xx', + 'general_setting.ping_interval_enabled': false, + 'general_setting.ping_interval_seconds': 60, + 'gemini.thinking_adapter_enabled': false, + 'gemini.thinking_adapter_budget_tokens_percentage': 0.6, + 'gemini.models_supported_thinking_budget': '', + }); + + let [loading, setLoading] = useState(false); + + const getOptions = async () => { + const res = await API.get('/api/option/'); + const { success, message, data } = res.data; + if (success) { + let newInputs = {}; + data.forEach((item) => { + if ( + item.key === 'gemini.safety_settings' || + item.key === 'gemini.version_settings' || + item.key === 'claude.model_headers_settings' || + item.key === 'claude.default_max_tokens' || + item.key === 'gemini.supported_imagine_models' || + item.key === 'gemini.models_supported_thinking_budget' + ) { + item.value = JSON.stringify(JSON.parse(item.value), null, 2); + } + if (item.key.endsWith('Enabled') || item.key.endsWith('enabled')) { + newInputs[item.key] = item.value === 'true'; + } else { + newInputs[item.key] = item.value; + } + }); + + setInputs(newInputs); + } else { + showError(message); + } + }; + async function onRefresh() { + try { + setLoading(true); + await getOptions(); + // showSuccess('刷新成功'); + } catch (error) { + showError('刷新失败'); + } finally { + setLoading(false); + } + } + + useEffect(() => { + onRefresh(); + }, []); + + return ( + <> + + {/* OpenAI */} + + + + {/* Gemini */} + + + + {/* Claude */} + + + + {/* Model Mapping */} + + + + + + ); +}; + +export default ModelSetting; diff --git a/web/src/components/OAuth2Callback.js b/web/src/components/OAuth2Callback.js new file mode 100644 index 0000000000000000000000000000000000000000..1d0a9591c3a89027cd933c66165a613af3d11701 --- /dev/null +++ b/web/src/components/OAuth2Callback.js @@ -0,0 +1,81 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useContext, useEffect, useState } from 'react'; +import { Dimmer, Loader, Segment } from 'semantic-ui-react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { API, showError, showSuccess, updateAPI } from '../helpers'; +import { UserContext } from '../context/User'; +import { setUserData } from '../helpers/data.js'; + +const OAuth2Callback = (props) => { + const [searchParams, setSearchParams] = useSearchParams(); + + const [userState, userDispatch] = useContext(UserContext); + const [prompt, setPrompt] = useState('处理中...'); + const [processing, setProcessing] = useState(true); + + let navigate = useNavigate(); + + const sendCode = async (code, state, count) => { + const res = await API.get( + `/api/oauth/${props.type}?code=${code}&state=${state}`, + ); + const { success, message, data } = res.data; + if (success) { + if (message === 'bind') { + showSuccess('绑定成功!'); + navigate('/admin/settings'); + } else { + userDispatch({ type: 'login', payload: data }); + localStorage.setItem('user', JSON.stringify(data)); + setUserData(data); + updateAPI(); + showSuccess('登录成功!'); + navigate(searchParams.get('returnTo') || '/app/tokens'); + } + } else { + showError(message); + if (count === 0) { + setPrompt(`操作失败,重定向至登录界面中...`); + navigate('/admin/settings'); // in case this is failed to bind GitHub + return; + } + count++; + setPrompt(`出现错误,第 ${count} 次重试中...`); + await new Promise((resolve) => setTimeout(resolve, count * 2000)); + await sendCode(code, state, count); + } + }; + + useEffect(() => { + let code = searchParams.get('code'); + let state = searchParams.get('state'); + sendCode(code, state, 0).then(); + }, []); + + return ( + + + {prompt} + + + ); +}; + +export default OAuth2Callback; diff --git a/web/src/components/OIDCIcon.js b/web/src/components/OIDCIcon.js new file mode 100644 index 0000000000000000000000000000000000000000..92e9a60cbf50e428c6f6b90e594aaba61b920630 --- /dev/null +++ b/web/src/components/OIDCIcon.js @@ -0,0 +1,56 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React from 'react'; +import { Icon } from '@douyinfe/semi-ui'; + +const OIDCIcon = (props) => { + function CustomIcon() { + return ( + + + + + ); + } + + return } />; +}; + +export default OIDCIcon; diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js new file mode 100644 index 0000000000000000000000000000000000000000..fddd2b29fd4454e547a0fbe49c67c755c71c4f70 --- /dev/null +++ b/web/src/components/OperationSetting.js @@ -0,0 +1,214 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { Card, Spin, Tabs } from '@douyinfe/semi-ui'; +import SettingsGeneral from '../pages/Setting/Operation/SettingsGeneral.js'; +import SettingsDrawing from '../pages/Setting/Operation/SettingsDrawing.js'; +import SettingsSensitiveWords from '../pages/Setting/Operation/SettingsSensitiveWords.js'; +import SettingsLog from '../pages/Setting/Operation/SettingsLog.js'; +import SettingsDataDashboard from '../pages/Setting/Operation/SettingsDataDashboard.js'; +import SettingsMonitoring from '../pages/Setting/Operation/SettingsMonitoring.js'; +import SettingsCheckIn from '../pages/Setting/Operation/SettingsCheckIn.js'; +import SettingsCreditLimit from '../pages/Setting/Operation/SettingsCreditLimit.js'; +import SettingsRebate from '../pages/Setting/Operation/SettingsRebate.js'; +import ModelSettingsVisualEditor from '../pages/Setting/Operation/ModelSettingsVisualEditor.js'; +import GroupRatioSettings from '../pages/Setting/Operation/GroupRatioSettings.js'; +import ModelCommonRatioSettings from '../pages/Setting/Operation/ModelCommonRatioSettings.js'; +import ModelRatioSettings from '../pages/Setting/Operation/ModelRatioSettings.js'; + +import { API, showError, showSuccess } from '../helpers'; +import SettingsChats from '../pages/Setting/Operation/SettingsChats.js'; +import { useTranslation } from 'react-i18next'; +import ModelRatioNotSetEditor from '../pages/Setting/Operation/ModelRationNotSetEditor.js'; + +const OperationSetting = () => { + const { t } = useTranslation(); + let [inputs, setInputs] = useState({ + QuotaForNewUser: 0, + QuotaForInviter: 0, + QuotaForInvitee: 0, + QuotaRemindThreshold: 0, + PreConsumedQuota: 0, + StreamCacheQueueLength: 0, + ModelRatio: '', + CacheRatio: '', + CompletionRatio: '', + ModelPrice: '', + GroupRatio: '', + UserUsableGroups: '', + TopUpLink: '', + 'general_setting.docs_link': '', + // ChatLink2: '', // 添加的新状态变量 + QuotaPerUnit: 0, + AutomaticDisableChannelEnabled: false, + AutomaticEnableChannelEnabled: false, + ChannelDisableThreshold: 0, + LogConsumeEnabled: false, + LogChatContentEnabled: false, + LogErrorEnabled: false, + DisplayInCurrencyEnabled: false, + DisplayTokenStatEnabled: false, + CheckSensitiveEnabled: false, + CheckSensitiveOnPromptEnabled: false, + CheckSensitiveOnCompletionEnabled: '', + StopOnSensitiveEnabled: '', + SensitiveWords: '', + MjNotifyEnabled: false, + MjAccountFilterEnabled: false, + MjModeClearEnabled: false, + MjForwardUrlEnabled: false, + MjActionCheckSuccessEnabled: false, + DrawingEnabled: false, + DataExportEnabled: false, + DataExportDefaultTime: 'hour', + DataExportInterval: 5, + DefaultCollapseSidebar: false, // 默认折叠侧边栏 + RetryTimes: 0, + Chats: '[]', + DemoSiteEnabled: false, + SelfUseModeEnabled: false, + AutomaticDisableKeywords: '', + CheckInEnabled: false, + CheckInQuota: '', + CheckInMaxQuota: '', + RebateEnabled: false, + RebatePercentage: 0, + AffEnabled: false, + }); + + let [loading, setLoading] = useState(false); + + const getOptions = async () => { + const res = await API.get('/api/option/'); + const { success, message, data } = res.data; + if (success) { + let newInputs = {}; + data.forEach((item) => { + if ( + item.key === 'ModelRatio' || + item.key === 'GroupRatio' || + item.key === 'UserUsableGroups' || + item.key === 'CompletionRatio' || + item.key === 'ModelPrice' || + item.key === 'CacheRatio' + ) { + item.value = JSON.stringify(JSON.parse(item.value), null, 2); + } + if ( + item.key.endsWith('Enabled') || + ['DefaultCollapseSidebar'].includes(item.key) + ) { + newInputs[item.key] = item.value === 'true' ? true : false; + } else { + newInputs[item.key] = item.value; + } + }); + + setInputs(newInputs); + } else { + showError(message); + } + }; + async function onRefresh() { + try { + setLoading(true); + await getOptions(); + // showSuccess('刷新成功'); + } catch (error) { + showError('刷新失败'); + } finally { + setLoading(false); + } + } + + useEffect(() => { + onRefresh(); + }, []); + + return ( + <> + + {/* 通用设置 */} + + + + {/* 绘图设置 */} + + + + {/* 屏蔽词过滤设置 */} + + + + {/* 日志设置 */} + + + + {/* 数据看板 */} + + + + {/* 监控设置 */} + + + + {/* 签到设置 */} + + + + {/* 额度设置 */} + + + + {/* 返佣设置 */} + + + + {/* 聊天设置 */} + + + + {/* 分组倍率设置 */} + + + + {/* 模型倍率相关设置 */} + + + + {/* 合并模型倍率设置和可视化倍率设置 */} + + + + + + + + + + + + + + + + ); +}; + +export default OperationSetting; diff --git a/web/src/components/OtherSetting.js b/web/src/components/OtherSetting.js new file mode 100644 index 0000000000000000000000000000000000000000..d0a0b25525db0a17be50d1b51dd16b00aeaad06b --- /dev/null +++ b/web/src/components/OtherSetting.js @@ -0,0 +1,552 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useContext, useEffect, useRef, useState } from 'react'; +import { + Banner, + Button, + Col, + Form, + Row, + Modal, + Space, + Card, + Switch, +} from '@douyinfe/semi-ui'; +import { API, showError, showSuccess, timestamp2string } from '../helpers'; +import { marked } from 'marked'; +import { useTranslation } from 'react-i18next'; +import { StatusContext } from '../context/Status/index.js'; +import Text from '@douyinfe/semi-ui/lib/es/typography/text'; + +const OtherSetting = () => { + const { t } = useTranslation(); + let [inputs, setInputs] = useState({ + Notice: '', + SystemName: '', + Logo: '', + Footer: '', + About: '', + HomePageContent: '', + custom_head_html: '', + global_css: '', + global_js: '', + DisplayInCurrencyEnabled: false, + DisplayTokenStatEnabled: false, + }); + let [loading, setLoading] = useState(false); + const [showUpdateModal, setShowUpdateModal] = useState(false); + const [statusState, statusDispatch] = useContext(StatusContext); + const [updateData, setUpdateData] = useState({ + tag_name: '', + content: '', + }); + + const updateOption = async (key, value) => { + setLoading(true); + const res = await API.put('/api/option/', { + key, + value, + }); + const { success, message } = res.data; + if (success) { + setInputs((inputs) => ({ ...inputs, [key]: value })); + } else { + showError(message); + } + setLoading(false); + }; + + const [loadingInput, setLoadingInput] = useState({ + Notice: false, + SystemName: false, + Logo: false, + HomePageContent: false, + About: false, + Footer: false, + CheckUpdate: false, + custom_head_html: false, + global_css: false, + global_js: false, + }); + const handleInputChange = async (value, e) => { + const name = e.target.id; + setInputs((inputs) => ({ ...inputs, [name]: value })); + }; + + // 通用设置 + const formAPISettingGeneral = useRef(); + // 通用设置 - Notice + const submitNotice = async () => { + try { + setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: true })); + await updateOption('Notice', inputs.Notice); + showSuccess(t('公告已更新')); + } catch (error) { + console.error(t('公告更新失败'), error); + showError(t('公告更新失败')); + } finally { + setLoadingInput((loadingInput) => ({ ...loadingInput, Notice: false })); + } + }; + // 个性化设置 + const formAPIPersonalization = useRef(); + // 个性化设置 - SystemName + const submitSystemName = async () => { + try { + setLoadingInput((loadingInput) => ({ + ...loadingInput, + SystemName: true, + })); + await updateOption('SystemName', inputs.SystemName); + showSuccess(t('系统名称已更新')); + } catch (error) { + console.error(t('系统名称更新失败'), error); + showError(t('系统名称更新失败')); + } finally { + setLoadingInput((loadingInput) => ({ + ...loadingInput, + SystemName: false, + })); + } + }; + + // 个性化设置 - Logo + const submitLogo = async () => { + try { + setLoadingInput((loadingInput) => ({ ...loadingInput, Logo: true })); + await updateOption('Logo', inputs.Logo); + showSuccess('Logo 已更新'); + } catch (error) { + console.error('Logo 更新失败', error); + showError('Logo 更新失败'); + } finally { + setLoadingInput((loadingInput) => ({ ...loadingInput, Logo: false })); + } + }; + // 个性化设置 - 首页内容 + const submitOption = async (key) => { + try { + setLoadingInput((loadingInput) => ({ + ...loadingInput, + HomePageContent: true, + })); + await updateOption(key, inputs[key]); + showSuccess('首页内容已更新'); + } catch (error) { + console.error('首页内容更新失败', error); + showError('首页内容更新失败'); + } finally { + setLoadingInput((loadingInput) => ({ + ...loadingInput, + HomePageContent: false, + })); + } + }; + // 个性化设置 - 关于 + const submitAbout = async () => { + try { + setLoadingInput((loadingInput) => ({ ...loadingInput, About: true })); + await updateOption('About', inputs.About); + showSuccess('关于内容已更新'); + } catch (error) { + console.error('关于内容更新失败', error); + showError('关于内容更新失败'); + } finally { + setLoadingInput((loadingInput) => ({ ...loadingInput, About: false })); + } + }; + // 个性化设置 - 页脚 + const submitFooter = async () => { + try { + setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: true })); + await updateOption('Footer', inputs.Footer); + showSuccess('页脚内容已更新'); + } catch (error) { + console.error('页脚内容更新失败', error); + showError('页脚内容更新失败'); + } finally { + setLoadingInput((loadingInput) => ({ ...loadingInput, Footer: false })); + } + }; + + // 个性化设置 - 自定义头部HTML + const submitCustomHeadHtml = async () => { + try { + setLoadingInput((loadingInput) => ({ ...loadingInput, custom_head_html: true })); + await updateOption('custom_head_html', inputs.custom_head_html); + showSuccess('自定义头部HTML已更新'); + } catch (error) { + console.error('自定义头部HTML更新失败', error); + showError('自定义头部HTML更新失败'); + } finally { + setLoadingInput((loadingInput) => ({ ...loadingInput, custom_head_html: false })); + } + }; + + // 个性化设置 - 全局CSS样式 + const submitGlobalCss = async () => { + try { + setLoadingInput((loadingInput) => ({ ...loadingInput, global_css: true })); + await updateOption('global_css', inputs.global_css); + showSuccess('全局CSS样式已更新'); + } catch (error) { + console.error('全局CSS样式更新失败', error); + showError('全局CSS样式更新失败'); + } finally { + setLoadingInput((loadingInput) => ({ ...loadingInput, global_css: false })); + } + }; + + // 个性化设置 - 全局JavaScript代码 + const submitGlobalJs = async () => { + try { + setLoadingInput((loadingInput) => ({ ...loadingInput, global_js: true })); + await updateOption('global_js', inputs.global_js); + showSuccess('全局JavaScript代码已更新'); + } catch (error) { + console.error('全局JavaScript代码更新失败', error); + showError('全局JavaScript代码更新失败'); + } finally { + setLoadingInput((loadingInput) => ({ ...loadingInput, global_js: false })); + } + }; + + const checkUpdate = async () => { + try { + setLoadingInput((loadingInput) => ({ + ...loadingInput, + CheckUpdate: true, + })); + // Use a CORS proxy to avoid direct cross-origin requests to GitHub API + // Option 1: Use a public CORS proxy service + // const proxyUrl = 'https://cors-anywhere.herokuapp.com/'; + // const res = await API.get( + // `${proxyUrl}https://api.github.com/repos/Calcium-Ion/new-api/releases/latest`, + // ); + + // Option 2: Use the JSON proxy approach which often works better with GitHub API + const res = await fetch( + 'https://api.github.com/repos/Veloera/Veloera/releases/latest', + { + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + // Adding User-Agent which is often required by GitHub API + 'User-Agent': 'veloera-update-checker', + }, + }, + ).then((response) => response.json()); + + // Option 3: Use a local proxy endpoint + // Create a cached version of the response to avoid frequent GitHub API calls + // const res = await API.get('/api/status/github-latest-release'); + + const { tag_name, body } = res; + if (tag_name === statusState?.status?.version) { + showSuccess(`已是最新版本:${tag_name}`); + } else { + setUpdateData({ + tag_name: tag_name, + content: marked.parse(body), + }); + setShowUpdateModal(true); + } + } catch (error) { + console.error('Failed to check for updates:', error); + showError('检查更新失败,请稍后再试'); + } finally { + setLoadingInput((loadingInput) => ({ + ...loadingInput, + CheckUpdate: false, + })); + } + }; + const getOptions = async () => { + const res = await API.get('/api/option/'); + const { success, message, data } = res.data; + if (success) { + let newInputs = {}; + data.forEach((item) => { + if (item.key in inputs) { + if (item.key === 'DisplayInCurrencyEnabled' || item.key === 'DisplayTokenStatEnabled') { + newInputs[item.key] = item.value === 'true'; + } else { + newInputs[item.key] = item.value; + } + } + }); + setInputs(newInputs); + formAPISettingGeneral.current.setValues(newInputs); + formAPIPersonalization.current.setValues(newInputs); + } else { + showError(message); + } + }; + + useEffect(() => { + getOptions(); + }, []); + + // Function to open GitHub release page + const openGitHubRelease = () => { + window.open( + `https://github.com/Veloera/Veloera/releases/tag/${updateData.tag_name}`, + '_blank', + ); + }; + + const getStartTimeString = () => { + const timestamp = statusState?.status?.start_time; + return statusState.status ? timestamp2string(timestamp) : ''; + }; + + return ( + + + {/* 版本信息 */} + + + + + + + + {t('当前版本')}: + {statusState?.status?.version || t('未知')} + + + + + + + + + {t('启动时间')}:{getStartTimeString()} + + + + + + + {/* 通用设置 */} + (formAPISettingGeneral.current = formAPI)} + > + + + + + + + + {/* 个性化设置 */} + (formAPIPersonalization.current = formAPI)} + > + + + + + + + + + + + {/* */} + + + + + + + + + + + + +
+ { + setInputs({ ...inputs, DisplayInCurrencyEnabled: checked }); + await updateOption('DisplayInCurrencyEnabled', checked.toString()); + }} + /> + {t('以货币形式显示配额')} +
+
+ { + setInputs({ ...inputs, DisplayTokenStatEnabled: checked }); + await updateOption('DisplayTokenStatEnabled', checked.toString()); + }} + /> + {t('显示 Token 统计信息')} +
+
+
+
+ + + setShowUpdateModal(false)} + footer={[ + , + ]} + > +
+
+ + ); +}; + +export default OtherSetting; diff --git a/web/src/components/PageLayout.js b/web/src/components/PageLayout.js new file mode 100644 index 0000000000000000000000000000000000000000..41337e37e6d3bc39935302b83ae64d8b50bc8d7f --- /dev/null +++ b/web/src/components/PageLayout.js @@ -0,0 +1,184 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import HeaderBar from './HeaderBar.js'; +import { Layout } from '@douyinfe/semi-ui'; +import SiderBar from './SiderBar.js'; +import App from '../App.js'; +import FooterBar from './Footer.js'; +import { ToastContainer } from 'react-toastify'; +import React, { useContext, useEffect } from 'react'; +import { StyleContext } from '../context/Style/index.js'; +import { useTranslation } from 'react-i18next'; +import { API, getLogo, getSystemName, showError } from '../helpers/index.js'; +import { setStatusData } from '../helpers/data.js'; +import { UserContext } from '../context/User/index.js'; +import { StatusContext } from '../context/Status/index.js'; +const { Sider, Content, Header, Footer } = Layout; + +const PageLayout = () => { + const [userState, userDispatch] = useContext(UserContext); + const [statusState, statusDispatch] = useContext(StatusContext); + const [styleState, styleDispatch] = useContext(StyleContext); + const { i18n } = useTranslation(); + + const loadUser = () => { + let user = localStorage.getItem('user'); + if (user) { + let data = JSON.parse(user); + userDispatch({ type: 'login', payload: data }); + } + }; + + const loadStatus = async () => { + try { + const res = await API.get('/api/status'); + const { success, data } = res.data; + if (success) { + statusDispatch({ type: 'set', payload: data }); + setStatusData(data); + // Notify other components that status has been updated + window.dispatchEvent(new Event('statusUpdated')); + } else { + showError('Unable to connect to server'); + } + } catch (error) { + showError('Failed to load status'); + } + }; + + useEffect(() => { + loadUser(); + loadStatus().catch(console.error); + let systemName = getSystemName(); + if (systemName) { + document.title = systemName; + } + let logo = getLogo(); + if (logo) { + let linkElement = document.querySelector("link[rel~='icon']"); + if (linkElement) { + linkElement.href = logo; + } + } + // 从localStorage获取上次使用的语言 + const savedLang = localStorage.getItem('i18nextLng'); + if (savedLang) { + i18n.changeLanguage(savedLang); + } + + // 默认显示侧边栏 + styleDispatch({ type: 'SET_SIDER', payload: true }); + }, [i18n]); + + // 获取侧边栏折叠状态 + const isSidebarCollapsed = + localStorage.getItem('default_collapse_sidebar') === 'true'; + + return ( + +
+ +
+ + {styleState.showSider && ( + + + + )} + + + + + + + + + + +
+ ); +}; + +export default PageLayout; diff --git a/web/src/components/PasswordResetConfirm.js b/web/src/components/PasswordResetConfirm.js new file mode 100644 index 0000000000000000000000000000000000000000..93911bfa6441da26656306f5229c1dbf49e17cf4 --- /dev/null +++ b/web/src/components/PasswordResetConfirm.js @@ -0,0 +1,131 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { Button, Form, Grid, Header, Image, Segment } from 'semantic-ui-react'; +import { API, copy, showError, showNotice } from '../helpers'; +import { useSearchParams } from 'react-router-dom'; + +const PasswordResetConfirm = () => { + const [inputs, setInputs] = useState({ + email: '', + token: '', + }); + const { email, token } = inputs; + + const [loading, setLoading] = useState(false); + + const [disableButton, setDisableButton] = useState(false); + const [countdown, setCountdown] = useState(30); + + const [newPassword, setNewPassword] = useState(''); + + const [searchParams, setSearchParams] = useSearchParams(); + useEffect(() => { + let token = searchParams.get('token'); + let email = searchParams.get('email'); + setInputs({ + token, + email, + }); + }, []); + + useEffect(() => { + let countdownInterval = null; + if (disableButton && countdown > 0) { + countdownInterval = setInterval(() => { + setCountdown(countdown - 1); + }, 1000); + } else if (countdown === 0) { + setDisableButton(false); + setCountdown(30); + } + return () => clearInterval(countdownInterval); + }, [disableButton, countdown]); + + async function handleSubmit(e) { + setDisableButton(true); + if (!email) return; + setLoading(true); + const res = await API.post(`/api/user/reset`, { + email, + token, + }); + const { success, message } = res.data; + if (success) { + let password = res.data.data; + setNewPassword(password); + await copy(password); + showNotice(`新密码已复制到剪贴板:${password}`); + } else { + showError(message); + } + setLoading(false); + } + + return ( + + +
+ 密码重置确认 +
+
+ + + {newPassword && ( + { + e.target.select(); + navigator.clipboard.writeText(newPassword); + showNotice(`密码已复制到剪贴板:${newPassword}`); + }} + /> + )} + + + +
+
+ ); +}; + +export default PasswordResetConfirm; diff --git a/web/src/components/PasswordResetForm.js b/web/src/components/PasswordResetForm.js new file mode 100644 index 0000000000000000000000000000000000000000..44fa30f04583fc51d291efcfa31d0249ec2dc257 --- /dev/null +++ b/web/src/components/PasswordResetForm.js @@ -0,0 +1,120 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { Button, Form, Grid, Header, Image, Segment } from 'semantic-ui-react'; +import { API, showError, showInfo, showSuccess } from '../helpers'; +import Turnstile from 'react-turnstile'; + +const PasswordResetForm = () => { + const [inputs, setInputs] = useState({ + email: '', + }); + const { email } = inputs; + + const [loading, setLoading] = useState(false); + const [turnstileEnabled, setTurnstileEnabled] = useState(false); + const [turnstileSiteKey, setTurnstileSiteKey] = useState(''); + const [turnstileToken, setTurnstileToken] = useState(''); + const [disableButton, setDisableButton] = useState(false); + const [countdown, setCountdown] = useState(30); + + useEffect(() => { + let countdownInterval = null; + if (disableButton && countdown > 0) { + countdownInterval = setInterval(() => { + setCountdown(countdown - 1); + }, 1000); + } else if (countdown === 0) { + setDisableButton(false); + setCountdown(30); + } + return () => clearInterval(countdownInterval); + }, [disableButton, countdown]); + + function handleChange(e) { + const { name, value } = e.target; + setInputs((inputs) => ({ ...inputs, [name]: value })); + } + + async function handleSubmit(e) { + setDisableButton(true); + if (!email) return; + if (turnstileEnabled && turnstileToken === '') { + showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!'); + return; + } + setLoading(true); + const res = await API.get( + `/api/reset_password?email=${email}&turnstile=${turnstileToken}`, + ); + const { success, message } = res.data; + if (success) { + showSuccess('重置邮件发送成功,请检查邮箱!'); + setInputs({ ...inputs, email: '' }); + } else { + showError(message); + } + setLoading(false); + } + + return ( + + +
+ 密码重置 +
+
+ + + {turnstileEnabled ? ( + { + setTurnstileToken(token); + }} + /> + ) : ( + <> + )} + + + +
+
+ ); +}; + +export default PasswordResetForm; diff --git a/web/src/components/PersonalSetting.js b/web/src/components/PersonalSetting.js new file mode 100644 index 0000000000000000000000000000000000000000..244e632db25de17f2277d7009b6ceec3073c3003 --- /dev/null +++ b/web/src/components/PersonalSetting.js @@ -0,0 +1,1337 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useContext, useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + API, + copy, + isRoot, + showError, + showInfo, + showSuccess, +} from '../helpers'; +import Turnstile from 'react-turnstile'; +import { UserContext } from '../context/User'; +import { + onGitHubOAuthClicked, + onOIDCClicked, + onLinuxDOOAuthClicked, + onIDCFlareOAuthClicked, +} from './utils'; +import { + Avatar, + Banner, + Button, + Card, + Descriptions, + Image, + Input, + InputNumber, + Layout, + Modal, + Space, + Tag, + Typography, + Collapsible, + Select, + Radio, + RadioGroup, + AutoComplete, + Checkbox, + Tabs, + TabPane, + Switch, +} from '@douyinfe/semi-ui'; +import { + getQuotaPerUnit, + renderQuota, + renderQuotaWithPrompt, + stringToColor, +} from '../helpers/render'; +import TelegramLoginButton from 'react-telegram-login'; +import { useTranslation } from 'react-i18next'; + +const PersonalSetting = () => { + const [userState, userDispatch] = useContext(UserContext); + let navigate = useNavigate(); + const { t } = useTranslation(); + + const [inputs, setInputs] = useState({ + wechat_verification_code: '', + email_verification_code: '', + email: '', + self_account_deletion_confirmation: '', + set_new_password: '', + set_new_password_confirmation: '', + }); + const [status, setStatus] = useState({}); + const [showChangePasswordModal, setShowChangePasswordModal] = useState(false); + const [showWeChatBindModal, setShowWeChatBindModal] = useState(false); + const [showEmailBindModal, setShowEmailBindModal] = useState(false); + const [showAccountDeleteModal, setShowAccountDeleteModal] = useState(false); + const [turnstileEnabled, setTurnstileEnabled] = useState(false); + const [turnstileSiteKey, setTurnstileSiteKey] = useState(''); + const [turnstileToken, setTurnstileToken] = useState(''); + const [loading, setLoading] = useState(false); + const [disableButton, setDisableButton] = useState(false); + const [countdown, setCountdown] = useState(30); + const [affLink, setAffLink] = useState(''); + const [systemToken, setSystemToken] = useState(''); + const [affEnabled, setAffEnabled] = useState(true); + const [models, setModels] = useState([]); + const [openTransfer, setOpenTransfer] = useState(false); + const [transferAmount, setTransferAmount] = useState(0); + const [checkInEnabled, setCheckInEnabled] = useState(false); + const [checkInLoading, setCheckInLoading] = useState(false); + const [canCheckIn, setCanCheckIn] = useState(true); + const [isModelsExpanded, setIsModelsExpanded] = useState(() => { + // Initialize from localStorage if available + const savedState = localStorage.getItem('modelsExpanded'); + return savedState ? JSON.parse(savedState) : false; + }); + const MODELS_DISPLAY_COUNT = 10; // 默认显示的模型数量 + const [notificationSettings, setNotificationSettings] = useState({ + warningType: 'email', + warningThreshold: 100000, + webhookUrl: '', + webhookSecret: '', + notificationEmail: '', + acceptUnsetModelRatioModel: false, + showIPInLogs: false, + }); + const [showWebhookDocs, setShowWebhookDocs] = useState(false); + + useEffect(() => { + let status = localStorage.getItem('status'); + if (status) { + status = JSON.parse(status); + setStatus(status); + if (status.turnstile_check) { + setTurnstileEnabled(true); + setTurnstileSiteKey(status.turnstile_site_key); + } + setCheckInEnabled(status.check_in_enabled === true); + setAffEnabled(status.aff_enabled === true); + } + getUserData().then((res) => { + console.log(userState); + }); + loadModels().then(); + if (status && status.aff_enabled === true) { + getAffLink().then(); + } + checkUserCanCheckIn().then(); + setTransferAmount(getQuotaPerUnit()); + }, []); + + // 监听checkInEnabled状态变化,确保状态同步 + useEffect(() => { + if (checkInEnabled) { + checkUserCanCheckIn(); + } + }, [checkInEnabled]); + + useEffect(() => { + let countdownInterval = null; + if (disableButton && countdown > 0) { + countdownInterval = setInterval(() => { + setCountdown(countdown - 1); + }, 1000); + } else if (countdown === 0) { + setDisableButton(false); + setCountdown(30); + } + return () => clearInterval(countdownInterval); // Clean up on unmount + }, [disableButton, countdown]); + + useEffect(() => { + if (userState?.user?.setting) { + const settings = JSON.parse(userState.user.setting); + setNotificationSettings({ + warningType: settings.notify_type || 'email', + warningThreshold: settings.quota_warning_threshold || 500000, + webhookUrl: settings.webhook_url || '', + webhookSecret: settings.webhook_secret || '', + notificationEmail: settings.notification_email || '', + acceptUnsetModelRatioModel: + settings.accept_unset_model_ratio_model || false, + showIPInLogs: settings.show_ip_in_logs || false, + }); + } + }, [userState?.user?.setting]); + + // Save models expanded state to localStorage whenever it changes + useEffect(() => { + localStorage.setItem('modelsExpanded', JSON.stringify(isModelsExpanded)); + }, [isModelsExpanded]); + + const handleInputChange = (name, value) => { + setInputs((inputs) => ({ ...inputs, [name]: value })); + }; + + const generateAccessToken = async () => { + const res = await API.get('/api/user/token'); + const { success, message, data } = res.data; + if (success) { + setSystemToken(data); + await copy(data); + showSuccess(t('令牌已重置并已复制到剪贴板')); + } else { + showError(message); + } + }; + + const getAffLink = async () => { + const res = await API.get('/api/user/aff'); + const { success, message, data } = res.data; + if (success) { + let link = `${window.location.origin}/register?aff=${data}`; + setAffLink(link); + } else { + showError(message); + } + }; + + const getUserData = async () => { + let res = await API.get(`/api/user/self`); + const { success, message, data } = res.data; + if (success) { + userDispatch({ type: 'login', payload: data }); + } else { + showError(message); + } + }; + + const loadModels = async () => { + let res = await API.get(`/api/user/models`); + const { success, message, data } = res.data; + if (success) { + if (data != null) { + setModels(data); + } + } else { + showError(message); + } + }; + + const handleAffLinkClick = async (e) => { + e.target.select(); + await copy(e.target.value); + showSuccess(t('邀请链接已复制到剪切板')); + }; + + const handleSystemTokenClick = async (e) => { + e.target.select(); + await copy(e.target.value); + showSuccess(t('系统令牌已复制到剪切板')); + }; + + const checkUserCanCheckIn = async () => { + if (!checkInEnabled) return; + + try { + const res = await API.get('/api/user/check_in_status'); + const { success, message, data } = res.data; + if (success) { + setCanCheckIn(data.can_check_in); + } else { + showError(message); + } + } catch (error) { + console.error('Failed to check user check-in status:', error); + } + }; + + const handleCheckIn = async () => { + if (!checkInEnabled || !canCheckIn) return; + + if (turnstileEnabled && turnstileToken === '') { + showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!'); + return; + } + + try { + setCheckInLoading(true); + const url = turnstileEnabled + ? `/api/user/check_in?turnstile=${turnstileToken}` + : '/api/user/check_in'; + const res = await API.post(url); + const { success, message, data } = res.data; + + if (success) { + const quotaPerUnit = status.quota_per_unit || 500000; + const dollarAmount = (data.quota / quotaPerUnit).toFixed(2); + showSuccess(t('签到成功!获得 ') + `$${dollarAmount}`); + setCanCheckIn(false); + getUserData(); // Refresh user data to get updated quota + } else { + showError(message); + } + } catch (error) { + showError(t('签到失败,请稍后再试')); + console.error('Check-in failed:', error); + } finally { + setCheckInLoading(false); + } + }; + + const deleteAccount = async () => { + if (inputs.self_account_deletion_confirmation !== userState.user.username) { + showError(t('请输入你的账户名以确认删除!')); + return; + } + + const res = await API.delete('/api/user/self'); + const { success, message } = res.data; + + if (success) { + showSuccess(t('账户已删除!')); + await API.get('/api/user/logout'); + userDispatch({ type: 'logout' }); + localStorage.removeItem('user'); + navigate('/login'); + } else { + showError(message); + } + }; + + const bindWeChat = async () => { + if (inputs.wechat_verification_code === '') return; + const res = await API.get( + `/api/oauth/wechat/bind?code=${inputs.wechat_verification_code}`, + ); + const { success, message } = res.data; + if (success) { + showSuccess(t('微信账户绑定成功!')); + setShowWeChatBindModal(false); + } else { + showError(message); + } + }; + + const changePassword = async () => { + if (inputs.set_new_password !== inputs.set_new_password_confirmation) { + showError(t('两次输入的密码不一致!')); + return; + } + const res = await API.put(`/api/user/self`, { + password: inputs.set_new_password, + }); + const { success, message } = res.data; + if (success) { + showSuccess(t('密码修改成功!')); + setShowWeChatBindModal(false); + } else { + showError(message); + } + setShowChangePasswordModal(false); + }; + + const transfer = async () => { + if (transferAmount < getQuotaPerUnit()) { + showError(t('划转金额最低为') + ' ' + renderQuota(getQuotaPerUnit())); + return; + } + const res = await API.post(`/api/user/aff_transfer`, { + quota: transferAmount, + }); + const { success, message } = res.data; + if (success) { + showSuccess(message); + setOpenTransfer(false); + getUserData().then(); + } else { + showError(message); + } + }; + + const sendVerificationCode = async () => { + if (inputs.email === '') { + showError(t('请输入邮箱!')); + return; + } + setDisableButton(true); + if (turnstileEnabled && turnstileToken === '') { + showInfo('请稍后几秒重试,Turnstile 正在检查用户环境!'); + return; + } + setLoading(true); + const res = await API.get( + `/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`, + ); + const { success, message } = res.data; + if (success) { + showSuccess(t('验证码发送成功,请检查邮箱!')); + } else { + showError(message); + } + setLoading(false); + }; + + const bindEmail = async () => { + if (inputs.email_verification_code === '') { + showError(t('请输入邮箱验证码!')); + return; + } + setLoading(true); + const res = await API.get( + `/api/oauth/email/bind?email=${inputs.email}&code=${inputs.email_verification_code}`, + ); + const { success, message } = res.data; + if (success) { + showSuccess(t('邮箱账户绑定成功!')); + setShowEmailBindModal(false); + userState.user.email = inputs.email; + } else { + showError(message); + } + setLoading(false); + }; + + const getDisplayName = () => { + if (userState.user) { + return userState.user.display_name || userState.user.username; + } else { + return 'null'; + } + }; + + const handleCancel = () => { + setOpenTransfer(false); + }; + + const copyText = async (text) => { + if (await copy(text)) { + showSuccess(t('已复制:') + text); + } else { + // setSearchKeyword(text); + Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text }); + } + }; + + const handleNotificationSettingChange = (type, value) => { + setNotificationSettings((prev) => ({ + ...prev, + [type]: value.target ? value.target.value : value, // 处理 Radio 事件对象 + })); + }; + + const saveNotificationSettings = async () => { + try { + const res = await API.put('/api/user/setting', { + notify_type: notificationSettings.warningType, + quota_warning_threshold: parseFloat( + notificationSettings.warningThreshold, + ), + webhook_url: notificationSettings.webhookUrl, + webhook_secret: notificationSettings.webhookSecret, + notification_email: notificationSettings.notificationEmail, + accept_unset_model_ratio_model: + notificationSettings.acceptUnsetModelRatioModel, + show_ip_in_logs: notificationSettings.showIPInLogs, + }); + + if (res.data.success) { + showSuccess(t('设置已更新')); + await getUserData(); + } else { + showError(res.data.message); + } + } catch (error) { + showError(t('更新设置失败')); + } + }; + + return ( +
+ + + {affEnabled && ( + +
+ + {t('可用额度')} + {renderQuotaWithPrompt(userState?.user?.aff_quota)} + + +
+
+ + {t('划转额度')} + {renderQuotaWithPrompt(transferAmount)}{' '} + {t('最低') + renderQuota(getQuotaPerUnit())} + +
+ setTransferAmount(value)} + disabled={false} + > +
+
+
+ )} +
+ + {typeof getDisplayName() === 'string' && + getDisplayName().slice(0, 1)} + + } + title={{getDisplayName()}} + description={ + isRoot() ? ( + {t('管理员')} + ) : ( + {t('普通用户')} + ) + } + > + } + headerExtraContent={ + <> + + {'ID: ' + userState?.user?.id} + {userState?.user?.group} + + + } + footer={ + <> +
+ + {t('可用模型')} + +
+
+ {models.length <= MODELS_DISPLAY_COUNT ? ( + + {models.map((model) => ( + { + copyText(model); + }} + > + {model} + + ))} + + ) : ( + <> + + + {models.map((model) => ( + { + copyText(model); + }} + > + {model} + + ))} + setIsModelsExpanded(false)} + > + {t('收起')} + + + + {!isModelsExpanded && ( + + {models + .slice(0, MODELS_DISPLAY_COUNT) + .map((model) => ( + { + copyText(model); + }} + > + {model} + + ))} + setIsModelsExpanded(true)} + > + {t('更多')} {models.length - MODELS_DISPLAY_COUNT}{' '} + {t('个模型')} + + + )} + + )} +
+ + } + > + + + {renderQuota(userState?.user?.quota)} + + + {renderQuota(userState?.user?.used_quota)} + + + {userState.user?.request_count} + + +
+ {checkInEnabled && ( + + {t('每日签到')} +
+ {t('签到可获得额外的 Token 奖励。每天只能签到一次,请按时签到哦!')} +
+ + {!canCheckIn && ( + + {t('已完成今日签到,明天再来哦!')} + + )} +
+ {turnstileEnabled && canCheckIn && ( +
+ { + setTurnstileToken(token); + }} + onExpire={() => { + setTurnstileToken(''); + }} + /> +
+ )} +
+
+ )} + + {affEnabled && ( + + {t('邀请链接')} + +
+ } + > + {t('邀请信息')} +
+ + + + {renderQuota(userState?.user?.aff_quota)} + + + + + {renderQuota(userState?.user?.aff_history_quota)} + + + {userState?.user?.aff_count} + + +
+ + )} + + {t('个人信息')} +
+ {t('邮箱')} +
+
+ +
+
+ +
+
+
+ {status.wechat_login && ( +
+ {t('微信')} +
+
+ +
+
+ +
+
+
+ )} + {status.github_oauth && ( +
+ {t('GitHub')} +
+
+ +
+
+ +
+
+
+ )} + {status.oidc_enabled && ( +
+ {t('OIDC')} +
+
+ +
+
+ +
+
+
+ )} + {status.telegram_oauth && ( +
+ {t('Telegram')} +
+
+ +
+
+ {userState.user.telegram_id !== '' ? ( + + ) : ( + + )} +
+
+
+ )} + {status.linuxdo_oauth && ( +
+ {t('LinuxDO')} +
+
+ +
+
+ +
+
+
+ )} + {status.idcflare_oauth && ( +
+ {t('IDC Flare')} +
+
+ +
+
+ +
+
+
+ )} +
+ + + + + + + {systemToken && ( + + )} + setShowWeChatBindModal(false)} + visible={showWeChatBindModal} + size={'small'} + > + +
+

+ 微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效) +

+
+ + handleInputChange('wechat_verification_code', v) + } + /> + +
+
+
+ + + +
+ {t('通知方式')} +
+ + handleNotificationSettingChange('warningType', value) + } + > + {t('邮件通知')} + {t('Webhook通知')} + +
+
+ {notificationSettings.warningType === 'webhook' && ( + <> +
+ + {t('Webhook地址')} + +
+ + handleNotificationSettingChange('webhookUrl', val) + } + placeholder={t( + '请输入Webhook地址,例如: https://example.com/webhook', + )} + /> + + {t( + '只支持https,系统将以 POST 方式发送通知,请确保地址可以接收 POST 请求', + )} + + +
+ setShowWebhookDocs(!showWebhookDocs) + } + > + {t('Webhook请求结构')}{' '} + {showWebhookDocs ? '▼' : '▶'} +
+ +
+                                {`{
+    "type": "quota_exceed",      // 通知类型
+    "title": "标题",             // 通知标题
+    "content": "通知内容",       // 通知内容,支持 {{value}} 变量占位符
+    "values": ["值1", "值2"],    // 按顺序替换content中的 {{value}} 占位符
+    "timestamp": 1739950503      // 时间戳
+}
+
+示例:
+{
+    "type": "quota_exceed",
+    "title": "额度预警通知",
+    "content": "您的额度即将用尽,当前剩余额度为 {{value}}",
+    "values": ["$0.99"],
+    "timestamp": 1739950503
+}`}
+                              
+
+
+
+
+
+ + {t('接口凭证(可选)')} + +
+ + handleNotificationSettingChange( + 'webhookSecret', + val, + ) + } + placeholder={t('请输入密钥')} + /> + + {t( + '密钥将以 Bearer 方式添加到请求头中,用于验证webhook请求的合法性', + )} + + + {t('Authorization: Bearer your-secret-key')} + +
+
+ + )} + {notificationSettings.warningType === 'email' && ( +
+ {t('通知邮箱')} +
+ + handleNotificationSettingChange( + 'notificationEmail', + val, + ) + } + placeholder={t('留空则使用账号绑定的邮箱')} + /> + + {t( + '设置用于接收额度预警的邮箱地址,不填则使用账号绑定的邮箱', + )} + +
+
+ )} +
+ + {t('额度预警阈值')}{' '} + {renderQuotaWithPrompt( + notificationSettings.warningThreshold, + )} + +
+ + handleNotificationSettingChange( + 'warningThreshold', + val, + ) + } + style={{ width: 200 }} + placeholder={t('请输入预警额度')} + data={[ + { value: 100000, label: '0.2$' }, + { value: 500000, label: '1$' }, + { value: 1000000, label: '5$' }, + { value: 5000000, label: '10$' }, + ]} + /> +
+ + {t( + '当剩余额度低于此数值时,系统将通过选择的方式发送通知', + )} + +
+
+ +
+ {t('接受未设置价格模型')} +
+ handleNotificationSettingChange('acceptUnsetModelRatioModel', e.target.checked)} + > + {t('接受未设置价格模型')} + + + {t('当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用')} + +
+
+
+ +
+ {t('在消费日志显示调用 IP')} +
+ + handleNotificationSettingChange('showIPInLogs', checked) + } + /> + + {t('启用后,您的消费日志中将显示请求的 IP 地址,用于安全监控和审计')} + +
+
+
+ +
+
+ +
+
+ setShowEmailBindModal(false)} + onOk={bindEmail} + visible={showEmailBindModal} + size={'small'} + centered={true} + maskClosable={false} + > + + {t('绑定邮箱地址')} + +
+ handleInputChange('email', value)} + name='email' + type='email' + /> + +
+
+ + handleInputChange('email_verification_code', value) + } + /> +
+ {turnstileEnabled ? ( + { + setTurnstileToken(token); + }} + /> + ) : ( + <> + )} +
+ setShowAccountDeleteModal(false)} + visible={showAccountDeleteModal} + size={'small'} + centered={true} + onOk={deleteAccount} + > +
+ +
+
+ + handleInputChange( + 'self_account_deletion_confirmation', + value, + ) + } + /> + {turnstileEnabled ? ( + { + setTurnstileToken(token); + }} + /> + ) : ( + <> + )} +
+
+ setShowChangePasswordModal(false)} + visible={showChangePasswordModal} + size={'small'} + centered={true} + onOk={changePassword} + > +
+ + handleInputChange('set_new_password', value) + } + /> + + handleInputChange('set_new_password_confirmation', value) + } + /> + {turnstileEnabled ? ( + { + setTurnstileToken(token); + }} + /> + ) : ( + <> + )} +
+
+
+ + + + ); +}; + +export default PersonalSetting; diff --git a/web/src/components/PrivateRoute.js b/web/src/components/PrivateRoute.js new file mode 100644 index 0000000000000000000000000000000000000000..ba0bf4841371386fdd4a6ad64a68b76ae45cf916 --- /dev/null +++ b/web/src/components/PrivateRoute.js @@ -0,0 +1,30 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import { Navigate } from 'react-router-dom'; + +import { history } from '../helpers'; + +function PrivateRoute({ children }) { + if (!localStorage.getItem('user')) { + return ; + } + return children; +} + +export { PrivateRoute }; diff --git a/web/src/components/RateLimitSetting.js b/web/src/components/RateLimitSetting.js new file mode 100644 index 0000000000000000000000000000000000000000..476979c1dd6016f843841af21c8e45d33140f789 --- /dev/null +++ b/web/src/components/RateLimitSetting.js @@ -0,0 +1,84 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { Card, Spin, Tabs } from '@douyinfe/semi-ui'; + +import { API, showError, showSuccess } from '../helpers'; +import SettingsChats from '../pages/Setting/Operation/SettingsChats.js'; +import { useTranslation } from 'react-i18next'; +import RequestRateLimit from '../pages/Setting/RateLimit/SettingsRequestRateLimit.js'; + +const RateLimitSetting = () => { + const { t } = useTranslation(); + let [inputs, setInputs] = useState({ + ModelRequestRateLimitEnabled: false, + ModelRequestRateLimitCount: 0, + ModelRequestRateLimitSuccessCount: 1000, + ModelRequestRateLimitDurationMinutes: 1, + }); + + let [loading, setLoading] = useState(false); + + const getOptions = async () => { + const res = await API.get('/api/option/'); + const { success, message, data } = res.data; + if (success) { + let newInputs = {}; + data.forEach((item) => { + if (item.key.endsWith('Enabled')) { + newInputs[item.key] = item.value === 'true' ? true : false; + } else { + newInputs[item.key] = item.value; + } + }); + + setInputs(newInputs); + } else { + showError(message); + } + }; + async function onRefresh() { + try { + setLoading(true); + await getOptions(); + // showSuccess('刷新成功'); + } catch (error) { + showError('刷新失败'); + } finally { + setLoading(false); + } + } + + useEffect(() => { + onRefresh(); + }, []); + + return ( + <> + + {/* AI请求速率限制 */} + + + + + + ); +}; + +export default RateLimitSetting; diff --git a/web/src/components/RedemptionsTable.js b/web/src/components/RedemptionsTable.js new file mode 100644 index 0000000000000000000000000000000000000000..0a3a5952fb36afdd14d5fd6d75cd74374751cf9e --- /dev/null +++ b/web/src/components/RedemptionsTable.js @@ -0,0 +1,686 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { + API, + copy, + showError, + showSuccess, + timestamp2string, +} from '../helpers'; + +import { ITEMS_PER_PAGE } from '../constants'; +import { renderQuota } from '../helpers/render'; +import { + Button, + Divider, + Form, + Modal, + Popconfirm, + Popover, + Table, + Tag, +} from '@douyinfe/semi-ui'; +import EditRedemption from '../pages/Redemption/EditRedemption'; +import { useTranslation } from 'react-i18next'; + +function renderTimestamp(timestamp) { + return <>{timestamp2string(timestamp)}; +} + +// 批量删除兑换码模态框组件 +const BatchDeleteByNameModal = ({ visible, onClose, onConfirm }) => { + const { t } = useTranslation(); + const [name, setName] = useState(''); + const [count, setCount] = useState(0); + const [confirming, setConfirming] = useState(false); + const [loading, setLoading] = useState(false); + + // 重置状态 + const resetState = () => { + setName(''); + setCount(0); + setConfirming(false); + setLoading(false); + }; + + // 关闭模态框 + const handleClose = () => { + resetState(); + onClose(); + }; + + // 当模态框显示状态变化时重置状态 + useEffect(() => { + if (!visible) { + resetState(); + } + }, [visible]); + + // 根据名称查询兑换码数量 + const checkRedemptionCount = async () => { + if (!name.trim()) return; + + setLoading(true); + try { + const res = await API.get(`/api/redemption/count-by-name?name=${encodeURIComponent(name.trim())}`); + if (res.data.success) { + setCount(res.data.data); + setConfirming(true); + } else { + showError(res.data.message); + } + } catch (error) { + showError(error.message); + } finally { + setLoading(false); + } + }; + + // 确认删除 + const handleConfirm = async () => { + if (count === 0) return; + + setLoading(true); + try { + const res = await API.delete(`/api/redemption/delete-by-name?name=${encodeURIComponent(name.trim())}`); + if (res.data.success) { + showSuccess(t('已成功删除 {{count}} 个兑换码', { count })); + onConfirm(); + } else { + showError(res.data.message); + } + } catch (error) { + showError(error.message); + } finally { + handleClose(); + } + }; + + return ( + + + + + ) : ( + <> + + + + ) + } + > + {confirming ? ( +
+

{t('将删除名称为 "{{name}}" 的所有兑换码,共 {{count}} 个', { name, count })}

+

{t('此操作不可逆,请确认')}

+
+ ) : ( +
+ + + )} +
+ ); +}; + +const RedemptionsTable = () => { + const { t } = useTranslation(); + + const renderStatus = (status) => { + switch (status) { + case 1: + return ( + + {t('未使用')} + + ); + case 2: + return ( + + {t('已禁用')} + + ); + case 3: + return ( + + {t('已使用')} + + ); + default: + return ( + + {t('未知状态')} + + ); + } + }; + + const renderType = (isGift) => { + return isGift ? + {t('礼品码')} : + {t('兑换码')}; + }; + + const renderMaxUses = (maxUses) => { + return maxUses === -1 ? t('无限制') : maxUses; + }; + + const columns = [ + { + title: t('ID'), + dataIndex: 'id', + }, + { + title: t('名称'), + dataIndex: 'name', + }, + { + title: t('状态'), + dataIndex: 'status', + key: 'status', + render: (text, record, index) => { + return
{renderStatus(text)}
; + }, + }, + { + title: t('额度'), + dataIndex: 'quota', + render: (text, record, index) => { + return
{renderQuota(parseInt(text))}
; + }, + }, + { + title: t('创建时间'), + dataIndex: 'created_time', + render: (text, record, index) => { + return
{renderTimestamp(text)}
; + }, + }, + { + title: t('兑换人ID'), + dataIndex: 'used_user_id', + render: (text, record, index) => { + return
{text === 0 ? t('无') : text}
; + }, + }, + { + title: t('类型'), + dataIndex: 'is_gift', + render: (text, record) => renderType(record.is_gift), + }, + { + title: t('最大使用次数'), + dataIndex: 'max_uses', + render: (text, record) => record.is_gift ? renderMaxUses(record.max_uses) : '-', + }, + { + title: t('已使用次数'), + dataIndex: 'used_count', + render: (text, record) => record.is_gift ? text : '-', + }, + { + title: '', + dataIndex: 'operate', + render: (text, record, index) => ( +
+ + + + + { + manageRedemption(record.id, 'delete', record).then(() => { + removeRecord(record.key); + }); + }} + > + + + {record.status === 1 ? ( + + ) : ( + + )} + +
+ ), + }, + ]; + + const [redemptions, setRedemptions] = useState([]); + const [loading, setLoading] = useState(true); + const [activePage, setActivePage] = useState(1); + const [searchKeyword, setSearchKeyword] = useState(''); + const [searching, setSearching] = useState(false); + const [tokenCount, setTokenCount] = useState(ITEMS_PER_PAGE); + const [selectedKeys, setSelectedKeys] = useState([]); + const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); + const [editingRedemption, setEditingRedemption] = useState({ + id: undefined, + }); + const [showEdit, setShowEdit] = useState(false); + const [showBatchDeleteByName, setShowBatchDeleteByName] = useState(false); + + const closeEdit = () => { + setShowEdit(false); + }; + + // 批量禁用所选兑换码 + const disableSelectedRedemptions = async () => { + if (selectedKeys.length === 0) { + showError(t('请至少选择一个兑换码!')); + return; + } + + setLoading(true); + try { + // 获取所有选中的 ID + const ids = selectedKeys.map(item => item.id); + const res = await API.put('/api/redemption/batch-disable', { ids }); + + if (res.data.success) { + showSuccess(t('成功禁用 {{count}} 个兑换码!', { count: res.data.data })); + await refresh(); + } else { + showError(res.data.message); + } + } catch (error) { + showError(error.message); + } finally { + setLoading(false); + } + }; + + // 删除所有已禁用的兑换码 + const deleteAllDisabledRedemptions = async () => { + Modal.confirm({ + title: t('删除所有已禁用的兑换码'), + content: t('此操作将删除所有已禁用/已使用的兑换码,不可恢复,是否继续?'), + okType: 'danger', + onOk: async () => { + setLoading(true); + try { + const res = await API.delete('/api/redemption/delete-disabled'); + + if (res.data.success) { + showSuccess(t('成功删除 {{count}} 个已禁用的兑换码!', { count: res.data.data })); + await refresh(); + } else { + showError(res.data.message); + } + } catch (error) { + showError(error.message); + } finally { + setLoading(false); + } + } + }); + }; + + const setRedemptionFormat = (redeptions) => { + setRedemptions(redeptions); + }; + + const loadRedemptions = async (startIdx, pageSize) => { + const res = await API.get( + `/api/redemption/?p=${startIdx}&page_size=${pageSize}`, + ); + const { success, message, data } = res.data; + if (success) { + const newPageData = data.items; + setActivePage(data.page); + setTokenCount(data.total); + setRedemptionFormat(newPageData); + } else { + showError(message); + } + setLoading(false); + }; + + const removeRecord = (key) => { + let newDataSource = [...redemptions]; + if (key != null) { + let idx = newDataSource.findIndex((data) => data.key === key); + + if (idx > -1) { + newDataSource.splice(idx, 1); + setRedemptions(newDataSource); + } + } + }; + + const copyText = async (text) => { + if (await copy(text)) { + showSuccess(t('已复制到剪贴板!')); + } else { + // setSearchKeyword(text); + Modal.error({ title: t('无法复制到剪贴板,请手动复制'), content: text }); + } + }; + + const onPaginationChange = (e, { activePage }) => { + (async () => { + if (activePage === Math.ceil(redemptions.length / pageSize) + 1) { + await loadRedemptions(activePage - 1, pageSize); + } + setActivePage(activePage); + })(); + }; + + useEffect(() => { + loadRedemptions(0, pageSize) + .then() + .catch((reason) => { + showError(reason); + }); + }, []); + + const refresh = async () => { + await loadRedemptions(activePage - 1, pageSize); + }; + + const manageRedemption = async (id, action, record) => { + let data = { id }; + let res; + switch (action) { + case 'delete': + res = await API.delete(`/api/redemption/${id}/`); + break; + case 'enable': + data.status = 1; + res = await API.put('/api/redemption/?status_only=true', data); + break; + case 'disable': + data.status = 2; + res = await API.put('/api/redemption/?status_only=true', data); + break; + } + const { success, message } = res.data; + if (success) { + showSuccess(t('操作成功完成!')); + let redemption = res.data.data; + let newRedemptions = [...redemptions]; + // let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx; + if (action === 'delete') { + } else { + record.status = redemption.status; + } + setRedemptions(newRedemptions); + } else { + showError(message); + } + }; + + const searchRedemptions = async (keyword, page, pageSize) => { + if (searchKeyword === '') { + await loadRedemptions(page, pageSize); + return; + } + setSearching(true); + const res = await API.get( + `/api/redemption/search?keyword=${keyword}&p=${page}&page_size=${pageSize}`, + ); + const { success, message, data } = res.data; + if (success) { + const newPageData = data.items; + setActivePage(data.page); + setTokenCount(data.total); + setRedemptionFormat(newPageData); + } else { + showError(message); + } + setSearching(false); + }; + + const handleKeywordChange = async (value) => { + setSearchKeyword(value.trim()); + }; + + const sortRedemption = (key) => { + if (redemptions.length === 0) return; + setLoading(true); + let sortedRedemptions = [...redemptions]; + sortedRedemptions.sort((a, b) => { + return ('' + a[key]).localeCompare(b[key]); + }); + if (sortedRedemptions[0].id === redemptions[0].id) { + sortedRedemptions.reverse(); + } + setRedemptions(sortedRedemptions); + setLoading(false); + }; + + const handlePageChange = (page) => { + setActivePage(page); + if (searchKeyword === '') { + loadRedemptions(page, pageSize).then(); + } else { + searchRedemptions(searchKeyword, page, pageSize).then(); + } + }; + + let pageData = redemptions; + const rowSelection = { + onSelect: (record, selected) => {}, + onSelectAll: (selected, selectedRows) => {}, + onChange: (selectedRowKeys, selectedRows) => { + setSelectedKeys(selectedRows); + }, + }; + + const handleRow = (record, index) => { + if (record.status !== 1 || (record.is_gift && record.used_count >= record.max_uses && record.max_uses !== -1)) { + return { + style: { + background: 'var(--semi-color-disabled-border)', + }, + }; + } else { + return {}; + } + }; + + return ( + <> + + + {/* 批量删除兑换码模态框 */} + setShowBatchDeleteByName(false)} + onConfirm={refresh} + /> + +
{ + searchRedemptions(searchKeyword, activePage, pageSize).then(); + }} + > + + + +
+ + + + + +
+ +
+ t('第 {{start}} - {{end}} 条,共 {{total}} 条', { + start: page.currentStart, + end: page.currentEnd, + total: tokenCount, + }), + onPageSizeChange: (size) => { + setPageSize(size); + setActivePage(1); + if (searchKeyword === '') { + loadRedemptions(1, size).then(); + } else { + searchRedemptions(searchKeyword, 1, size).then(); + } + }, + onPageChange: handlePageChange, + }} + loading={loading} + rowSelection={rowSelection} + onRow={handleRow} + >
+ + ); +}; + +export default RedemptionsTable; diff --git a/web/src/components/RegisterForm.js b/web/src/components/RegisterForm.js new file mode 100644 index 0000000000000000000000000000000000000000..a7f98a7c05b051ae81db86c12a8db9f99b2549a2 --- /dev/null +++ b/web/src/components/RegisterForm.js @@ -0,0 +1,249 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; +import { API, showError, showInfo, showSuccess } from '../helpers'; +import { Button, Form } from '@douyinfe/semi-ui'; +import Text from '@douyinfe/semi-ui/lib/es/typography/text'; +import { useTranslation } from 'react-i18next'; +import { VALIDATION_MESSAGES } from '../utils/authConstants'; + +// Shared components and hooks +import { useAuthForm } from '../hooks/useAuthForm'; +import { useTurnstile } from '../hooks/useTurnstile'; +import AuthFormLayout from './shared/AuthFormLayout'; +import ThirdPartyAuth from './shared/ThirdPartyAuth'; +import WeChatLoginModal from './shared/WeChatLoginModal'; +import TurnstileWrapper from './shared/TurnstileWrapper'; + +const RegisterForm = () => { + const { t } = useTranslation(); + const [showEmailVerification, setShowEmailVerification] = useState(false); + const [loading, setLoading] = useState(false); + + // Use shared hooks + const { + inputs, + setInputs, + handleChange, + status, + setStatus, + showWeChatLoginModal, + setShowWeChatLoginModal, + navigate, + processAffCode, + onWeChatLoginClicked, + onSubmitWeChatVerificationCode, + onTelegramLoginClicked, + } = useAuthForm({ + username: '', + password: '', + password2: '', + email: '', + verification_code: '', + wechat_verification_code: '', + }); + + const { + turnstileEnabled, + turnstileSiteKey, + turnstileToken, + setTurnstileToken, + validateTurnstile, + } = useTurnstile(); + + const { username, password, password2 } = inputs; + + // Update email verification when status changes + useEffect(() => { + setShowEmailVerification(status.email_verification); + }, [status]); + + const handleSubmit = async (e) => { + if (password.length < 8) { + showInfo(VALIDATION_MESSAGES.PASSWORD_TOO_SHORT); + return; + } + if (password !== password2) { + showInfo(VALIDATION_MESSAGES.PASSWORD_MISMATCH); + return; + } + if (username && password) { + if (!validateTurnstile()) { + showInfo(VALIDATION_MESSAGES.TURNSTILE_WAIT); + return; + } + setLoading(true); + + // Handle AFF code if enabled - 直接构建请求数据 + let requestData = { ...inputs }; + let statusFromStorage = localStorage.getItem('status'); + if (statusFromStorage) { + statusFromStorage = JSON.parse(statusFromStorage); + if (statusFromStorage.aff_enabled === true) { + let affCode = localStorage.getItem('aff'); + if (affCode) { + requestData.aff_code = affCode; + } + } + } + + const res = await API.post( + `/api/user/register?turnstile=${turnstileToken}`, + requestData, + ); + const { success, message } = res.data; + if (success) { + navigate('/login'); + showSuccess(VALIDATION_MESSAGES.REGISTER_SUCCESS); + } else { + showError(message); + } + setLoading(false); + } + }; + + const sendVerificationCode = async () => { + if (inputs.email === '') return; + if (!validateTurnstile()) { + showInfo(VALIDATION_MESSAGES.TURNSTILE_WAIT); + return; + } + setLoading(true); + const res = await API.get( + `/api/verification?email=${inputs.email}&turnstile=${turnstileToken}`, + ); + const { success, message } = res.data; + if (success) { + showSuccess(VALIDATION_MESSAGES.VERIFICATION_CODE_SENT); + } else { + showError(message); + } + setLoading(false); + }; + + const handleWeChatSubmit = () => { + onSubmitWeChatVerificationCode(turnstileToken); + }; + + return ( + +
+ handleChange('username', value)} + /> + handleChange('password', value)} + /> + handleChange('password2', value)} + /> + {showEmailVerification && ( + <> + handleChange('email', value)} + name='email' + type='email' + suffix={ + + } + /> + + handleChange('verification_code', value) + } + name='verification_code' + /> + + )} + + + +
+ + {t('已有账户?')} + {t('点击登录')} + +
+ + + + setShowWeChatLoginModal(false)} + status={status} + inputs={inputs} + handleChange={handleChange} + /> + + +
+ ); +}; + +export default RegisterForm; diff --git a/web/src/components/SetupCheck.js b/web/src/components/SetupCheck.js new file mode 100644 index 0000000000000000000000000000000000000000..aed961cf4ff8df934e074f2f04ab7b077b0be0a0 --- /dev/null +++ b/web/src/components/SetupCheck.js @@ -0,0 +1,36 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useContext, useEffect } from 'react'; +import { Navigate, useLocation } from 'react-router-dom'; +import { StatusContext } from '../context/Status'; + +const SetupCheck = ({ children }) => { + const [statusState] = useContext(StatusContext); + const location = useLocation(); + + useEffect(() => { + if (statusState?.status?.setup === false && location.pathname !== '/setup') { + window.location.href = '/setup'; + } + }, [statusState?.status?.setup, location.pathname]); + + return children; +}; + +export default SetupCheck; \ No newline at end of file diff --git a/web/src/components/SiderBar.js b/web/src/components/SiderBar.js new file mode 100644 index 0000000000000000000000000000000000000000..d973038c7905008bfa787aea6f305117bf64dcf3 --- /dev/null +++ b/web/src/components/SiderBar.js @@ -0,0 +1,559 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useContext, useEffect, useMemo, useState } from 'react'; +import { Link, useNavigate, useLocation } from 'react-router-dom'; +import { UserContext } from '../context/User'; +import { StatusContext } from '../context/Status'; +import { useTranslation } from 'react-i18next'; + +import { + API, + getLogo, + getSystemName, + isAdmin, + isMobile, + showError, +} from '../helpers'; +import '../index.css'; + +import { + IconCalendarClock, + IconChecklistStroked, + IconComment, + IconCommentStroked, + IconCreditCard, + IconGift, + IconHistogram, + IconImage, + IconKey, + IconLayers, + IconMail, + IconSetting, + IconUser, +} from '@douyinfe/semi-icons'; +import { + Nav, + Divider, +} from '@douyinfe/semi-ui'; +import { useSetTheme, useTheme } from '../context/Theme/index.js'; +import { StyleContext } from '../context/Style/index.js'; +import Text from '@douyinfe/semi-ui/lib/es/typography/text'; + +// 自定义侧边栏按钮样式 +const navItemStyle = { + borderRadius: '6px', + margin: '4px 8px', +}; + +// 自定义侧边栏按钮悬停样式 +const navItemHoverStyle = { + backgroundColor: 'var(--semi-color-primary-light-default)', + color: 'var(--semi-color-primary)', +}; + +// 自定义侧边栏按钮选中样式 +const navItemSelectedStyle = { + backgroundColor: 'var(--semi-color-primary-light-default)', + color: 'var(--semi-color-primary)', + fontWeight: '600', +}; + +// 自定义图标样式 +const iconStyle = (itemKey, selectedKeys) => { + return { + fontSize: '18px', + color: selectedKeys.includes(itemKey) + ? 'var(--semi-color-primary)' + : 'var(--semi-color-text-2)', + }; +}; + +// Define routerMap as a constant outside the component +const routerMap = { + home: '/', + channel: '/admin/channels', + token: '/app/tokens', + redemption: '/admin/coupons', + topup: '/app/wallet', + user: '/admin/users', + log: '/app/logs/api-usage', + midjourney: '/app/logs/drawing', + setting: '/admin/settings', + about: '/about', + detail: '/app/dashboard', + pricing: '/pricing', + task: '/app/logs/tasks', + playground: '/playground', + personal: '/app/me', + message: '/admin/messages', +}; + +const SiderBar = () => { + const { t } = useTranslation(); + const [styleState, styleDispatch] = useContext(StyleContext); + const [statusState, statusDispatch] = useContext(StatusContext); + const defaultIsCollapsed = + localStorage.getItem('default_collapse_sidebar') === 'true'; + + const [selectedKeys, setSelectedKeys] = useState(['home']); + const [isCollapsed, setIsCollapsed] = useState(defaultIsCollapsed); + const [chatItems, setChatItems] = useState([]); + const [openedKeys, setOpenedKeys] = useState([]); + const theme = useTheme(); + const setTheme = useSetTheme(); + const location = useLocation(); + const [routerMapState, setRouterMapState] = useState(routerMap); + + // 预先计算所有可能的图标样式 + const allItemKeys = useMemo(() => { + const keys = [ + 'home', + 'channel', + 'token', + 'redemption', + 'topup', + 'user', + 'log', + 'midjourney', + 'setting', + 'about', + 'chat', + 'detail', + 'pricing', + 'task', + 'playground', + 'personal', + 'message', + ]; + // 添加聊天项的keys + for (let i = 0; i < chatItems.length; i++) { + keys.push('chat' + i); + } + return keys; + }, [chatItems]); + + // 使用useMemo一次性计算所有图标样式 + const iconStyles = useMemo(() => { + const styles = {}; + allItemKeys.forEach((key) => { + styles[key] = iconStyle(key, selectedKeys); + }); + return styles; + }, [allItemKeys, selectedKeys]); + + const workspaceItems = useMemo( + () => [ + { + text: t('数据看板'), + itemKey: 'detail', + to: '/app/dashboard', + icon: , + className: + localStorage.getItem('enable_data_export') === 'true' + ? '' + : 'tableHiddle', + }, + { + text: t('API令牌'), + itemKey: 'token', + to: '/app/tokens', + icon: , + }, + { + text: t('使用日志'), + itemKey: 'log', + to: '/app/logs/api-usage', + icon: , + }, + { + text: t('绘图日志'), + itemKey: 'midjourney', + to: '/app/logs/drawing', + icon: , + className: + localStorage.getItem('enable_drawing') === 'true' + ? '' + : 'tableHiddle', + }, + { + text: t('任务日志'), + itemKey: 'task', + to: '/app/logs/tasks', + icon: , + className: + localStorage.getItem('enable_task') === 'true' ? '' : 'tableHiddle', + }, + ], + [ + localStorage.getItem('enable_data_export'), + localStorage.getItem('enable_drawing'), + localStorage.getItem('enable_task'), + t, + ], + ); + + const financeItems = useMemo( + () => [ + { + text: t('计费与订阅'), + itemKey: 'topup', + to: '/app/wallet', + icon: , + }, + { + text: t('个人设置'), + itemKey: 'personal', + to: '/app/me', + icon: , + }, + ], + [t], + ); + + const adminItems = useMemo( + () => [ + { + text: t('渠道'), + itemKey: 'channel', + to: '/admin/channels', + icon: , + className: isAdmin() ? '' : 'tableHiddle', + }, + { + text: t('兑换码'), + itemKey: 'redemption', + to: '/admin/coupons', + icon: , + className: isAdmin() ? '' : 'tableHiddle', + }, + { + text: t('消息管理'), + itemKey: 'message', + to: '/admin/messages', + icon: , + className: isAdmin() ? '' : 'tableHiddle', + }, + { + text: t('用户管理'), + itemKey: 'user', + to: '/admin/users', + icon: , + }, + { + text: t('系统设置'), + itemKey: 'setting', + to: '/admin/settings', + icon: , + }, + ], + [isAdmin(), t], + ); + + const chatMenuItems = useMemo( + () => [ + { + text: 'Playground', + itemKey: 'playground', + to: '/playground', + icon: , + }, + // Only include chat menu if there are chat items + ...(chatItems.length > 0 + ? [ + { + text: t('聊天'), + itemKey: 'chat', + items: chatItems, + icon: , + }, + ] + : []), + ], + [chatItems, t], + ); + + // Function to update router map with chat routes + const updateRouterMapWithChats = (chats) => { + const newRouterMap = { ...routerMap }; + + if (Array.isArray(chats) && chats.length > 0) { + for (let i = 0; i < chats.length; i++) { + newRouterMap['chat' + i] = '/chat/' + i; + } + } + + setRouterMapState(newRouterMap); + return newRouterMap; + }; + + // Update the useEffect for chat items + useEffect(() => { + let chats = localStorage.getItem('chats'); + if (chats) { + try { + chats = JSON.parse(chats); + if (Array.isArray(chats)) { + let chatItems = []; + for (let i = 0; i < chats.length; i++) { + let chat = {}; + for (let key in chats[i]) { + chat.text = key; + chat.itemKey = 'chat' + i; + chat.to = '/chat/' + i; + } + chatItems.push(chat); + } + setChatItems(chatItems); + + // Update router map with chat routes + updateRouterMapWithChats(chats); + } + } catch (e) { + console.error(e); + showError('聊天数据解析失败'); + } + } + }, []); + + // Update the useEffect for route selection + useEffect(() => { + const currentPath = location.pathname; + let matchingKey = Object.keys(routerMapState).find( + (key) => routerMapState[key] === currentPath, + ); + + // Handle chat routes + if (!matchingKey && currentPath.startsWith('/chat/')) { + const chatIndex = currentPath.split('/').pop(); + if (!isNaN(chatIndex)) { + matchingKey = 'chat' + chatIndex; + } else { + matchingKey = 'chat'; + } + } + + // If we found a matching key, update the selected keys + if (matchingKey) { + setSelectedKeys([matchingKey]); + } + }, [location.pathname, routerMapState]); + + useEffect(() => { + setIsCollapsed(styleState.siderCollapsed); + }, [styleState.siderCollapsed]); + + // Custom divider style + const dividerStyle = { + margin: '8px 0', + opacity: 0.6, + }; + + // Custom group label style + const groupLabelStyle = { + padding: '8px 16px', + color: 'var(--semi-color-text-2)', + fontSize: '12px', + fontWeight: 'bold', + textTransform: 'uppercase', + letterSpacing: '0.5px', + }; + + return ( + <> + + + ); +}; + +export default SiderBar; diff --git a/web/src/components/SystemSetting.js b/web/src/components/SystemSetting.js new file mode 100644 index 0000000000000000000000000000000000000000..c9c74f0e8c8af2f0b7680af9edefc4711c69d5c6 --- /dev/null +++ b/web/src/components/SystemSetting.js @@ -0,0 +1,1318 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState, useRef } from 'react'; +import { + Button, + Form, + Row, + Col, + Typography, + Modal, + Banner, + TagInput, + Spin, + Card, + Select, +} from '@douyinfe/semi-ui'; +const { Text } = Typography; +import { + removeTrailingSlash, + showError, + showSuccess, + verifyJSON, +} from '../helpers/utils'; +import { API } from '../helpers/api'; + +const SystemSetting = () => { + let [inputs, setInputs] = useState({ + PasswordLoginEnabled: '', + PasswordRegisterEnabled: '', + EmailVerificationEnabled: '', + GitHubOAuthEnabled: '', + GitHubClientId: '', + GitHubClientSecret: '', + 'oidc.enabled': '', + 'oidc.client_id': '', + 'oidc.client_secret': '', + 'oidc.well_known': '', + 'oidc.authorization_endpoint': '', + 'oidc.token_endpoint': '', + 'oidc.user_info_endpoint': '', + Notice: '', + SMTPServer: '', + SMTPPort: '', + SMTPAccount: '', + SMTPFrom: '', + SMTPToken: '', + ServerAddress: '', + WorkerUrl: '', + WorkerValidKey: '', + EpayId: '', + EpayKey: '', + Price: 7.3, + MinTopUp: 1, + TopupGroupRatio: '', + PayAddress: '', + CustomCallbackAddress: '', + Footer: '', + WeChatAuthEnabled: '', + WeChatServerAddress: '', + WeChatServerToken: '', + WeChatAccountQRCodeImageURL: '', + TurnstileCheckEnabled: '', + TurnstileSiteKey: '', + TurnstileSecretKey: '', + RegisterEnabled: '', + EmailDomainRestrictionEnabled: '', + EmailAliasRestrictionEnabled: '', + SMTPSSLEnabled: '', + EmailDomainWhitelist: [], + // telegram login + TelegramOAuthEnabled: '', + TelegramBotToken: '', + TelegramBotName: '', + LinuxDOOAuthEnabled: '', + LinuxDOClientId: '', + LinuxDOClientSecret: '', + LinuxDOMinimumTrustLevel: '', + IDCFlareOAuthEnabled: '', + IDCFlareClientId: '', + IDCFlareClientSecret: '', + IDCFlareMinimumTrustLevel: '', + // reverse proxy settings + ReverseProxyEnabled: '', + ReverseProxyProvider: '', + }); + + const [originInputs, setOriginInputs] = useState({}); + const [loading, setLoading] = useState(false); + const [isLoaded, setIsLoaded] = useState(false); + const formApiRef = useRef(null); + const [emailDomainWhitelist, setEmailDomainWhitelist] = useState([]); + const [showPasswordLoginConfirmModal, setShowPasswordLoginConfirmModal] = + useState(false); + const [linuxDOOAuthEnabled, setLinuxDOOAuthEnabled] = useState(false); + const [idcFlareOAuthEnabled, setIDCFlareOAuthEnabled] = useState(false); + const [emailToAdd, setEmailToAdd] = useState(''); + + const getOptions = async () => { + setLoading(true); + const res = await API.get('/api/option/'); + const { success, message, data } = res.data; + if (success) { + let newInputs = {}; + data.forEach((item) => { + switch (item.key) { + case 'TopupGroupRatio': + item.value = JSON.stringify(JSON.parse(item.value), null, 2); + break; + case 'EmailDomainWhitelist': + setEmailDomainWhitelist(item.value ? item.value.split(',') : []); + break; + case 'PasswordLoginEnabled': + case 'PasswordRegisterEnabled': + case 'EmailVerificationEnabled': + case 'GitHubOAuthEnabled': + case 'WeChatAuthEnabled': + case 'TelegramOAuthEnabled': + case 'RegisterEnabled': + case 'TurnstileCheckEnabled': + case 'EmailDomainRestrictionEnabled': + case 'EmailAliasRestrictionEnabled': + case 'SMTPSSLEnabled': + case 'LinuxDOOAuthEnabled': + case 'IDCFlareOAuthEnabled': + case 'ReverseProxyEnabled': + case 'oidc.enabled': + item.value = item.value === 'true'; + break; + case 'Price': + case 'MinTopUp': + item.value = parseFloat(item.value); + break; + default: + break; + } + newInputs[item.key] = item.value; + }); + setInputs(newInputs); + setOriginInputs(newInputs); + if (formApiRef.current) { + formApiRef.current.setValues(newInputs); + } + setIsLoaded(true); + } else { + showError(message); + } + setLoading(false); + }; + + useEffect(() => { + getOptions(); + }, []); + + const updateOptions = async (options) => { + setLoading(true); + try { + // 分离 checkbox 类型的选项和其他选项 + const checkboxOptions = options.filter((opt) => + opt.key.toLowerCase().endsWith('enabled'), + ); + const otherOptions = options.filter( + (opt) => !opt.key.toLowerCase().endsWith('enabled'), + ); + + // 处理 checkbox 类型的选项 + for (const opt of checkboxOptions) { + const res = await API.put('/api/option/', { + key: opt.key, + value: opt.value.toString(), + }); + if (!res.data.success) { + showError(res.data.message); + return; + } + } + + // 处理其他选项 + if (otherOptions.length > 0) { + const requestQueue = otherOptions.map((opt) => + API.put('/api/option/', { + key: opt.key, + value: + typeof opt.value === 'boolean' ? opt.value.toString() : opt.value, + }), + ); + + const results = await Promise.all(requestQueue); + + // 检查所有请求是否成功 + const errorResults = results.filter((res) => !res.data.success); + errorResults.forEach((res) => { + showError(res.data.message); + }); + } + + showSuccess('更新成功'); + // 更新本地状态 + const newInputs = { ...inputs }; + options.forEach((opt) => { + newInputs[opt.key] = opt.value; + }); + setInputs(newInputs); + } catch (error) { + showError('更新失败'); + } + setLoading(false); + }; + + const handleFormChange = (values) => { + setInputs(values); + }; + + const submitServerAddress = async () => { + let ServerAddress = removeTrailingSlash(inputs.ServerAddress); + await updateOptions([{ key: 'ServerAddress', value: ServerAddress }]); + }; + + const submitWorker = async () => { + let WorkerUrl = removeTrailingSlash(inputs.WorkerUrl); + await updateOptions([ + { key: 'WorkerUrl', value: WorkerUrl }, + { key: 'WorkerValidKey', value: inputs.WorkerValidKey }, + ]); + }; + + const submitPayAddress = async () => { + if (inputs.ServerAddress === '') { + showError('请先填写服务器地址'); + return; + } + if (originInputs['TopupGroupRatio'] !== inputs.TopupGroupRatio) { + if (!verifyJSON(inputs.TopupGroupRatio)) { + showError('充值分组倍率不是合法的 JSON 字符串'); + return; + } + } + + const options = [ + { key: 'PayAddress', value: removeTrailingSlash(inputs.PayAddress) }, + ]; + + if (inputs.EpayId !== '') { + options.push({ key: 'EpayId', value: inputs.EpayId }); + } + if (inputs.EpayKey !== undefined && inputs.EpayKey !== '') { + options.push({ key: 'EpayKey', value: inputs.EpayKey }); + } + if (inputs.Price !== '') { + options.push({ key: 'Price', value: inputs.Price.toString() }); + } + if (inputs.MinTopUp !== '') { + options.push({ key: 'MinTopUp', value: inputs.MinTopUp.toString() }); + } + if (inputs.CustomCallbackAddress !== '') { + options.push({ + key: 'CustomCallbackAddress', + value: inputs.CustomCallbackAddress, + }); + } + if (originInputs['TopupGroupRatio'] !== inputs.TopupGroupRatio) { + options.push({ key: 'TopupGroupRatio', value: inputs.TopupGroupRatio }); + } + + await updateOptions(options); + }; + + const submitSMTP = async () => { + const options = []; + + if (originInputs['SMTPServer'] !== inputs.SMTPServer) { + options.push({ key: 'SMTPServer', value: inputs.SMTPServer }); + } + if (originInputs['SMTPAccount'] !== inputs.SMTPAccount) { + options.push({ key: 'SMTPAccount', value: inputs.SMTPAccount }); + } + if (originInputs['SMTPFrom'] !== inputs.SMTPFrom) { + options.push({ key: 'SMTPFrom', value: inputs.SMTPFrom }); + } + if ( + originInputs['SMTPPort'] !== inputs.SMTPPort && + inputs.SMTPPort !== '' + ) { + options.push({ key: 'SMTPPort', value: inputs.SMTPPort }); + } + if ( + originInputs['SMTPToken'] !== inputs.SMTPToken && + inputs.SMTPToken !== '' + ) { + options.push({ key: 'SMTPToken', value: inputs.SMTPToken }); + } + + if (options.length > 0) { + await updateOptions(options); + } + }; + + const submitEmailDomainWhitelist = async () => { + if (Array.isArray(emailDomainWhitelist)) { + await updateOptions([ + { + key: 'EmailDomainWhitelist', + value: emailDomainWhitelist.join(','), + }, + ]); + } else { + showError('邮箱域名白名单格式不正确'); + } + }; + + const handleAddEmail = () => { + if (emailToAdd && emailToAdd.trim() !== '') { + const domain = emailToAdd.trim(); + + // 验证域名格式 + const domainRegex = /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/; + if (!domainRegex.test(domain)) { + showError('邮箱域名格式不正确,请输入有效的域名,如 gmail.com'); + return; + } + + // 检查是否已存在 + if (emailDomainWhitelist.includes(domain)) { + showError('该域名已存在于白名单中'); + return; + } + + setEmailDomainWhitelist([...emailDomainWhitelist, domain]); + setEmailToAdd(''); + showSuccess('已添加到白名单'); + } + }; + + const submitWeChat = async () => { + const options = []; + + if (originInputs['WeChatServerAddress'] !== inputs.WeChatServerAddress) { + options.push({ + key: 'WeChatServerAddress', + value: removeTrailingSlash(inputs.WeChatServerAddress), + }); + } + if ( + originInputs['WeChatAccountQRCodeImageURL'] !== + inputs.WeChatAccountQRCodeImageURL + ) { + options.push({ + key: 'WeChatAccountQRCodeImageURL', + value: inputs.WeChatAccountQRCodeImageURL, + }); + } + if ( + originInputs['WeChatServerToken'] !== inputs.WeChatServerToken && + inputs.WeChatServerToken !== '' + ) { + options.push({ + key: 'WeChatServerToken', + value: inputs.WeChatServerToken, + }); + } + + if (options.length > 0) { + await updateOptions(options); + } + }; + + const submitGitHubOAuth = async () => { + const options = []; + + if (originInputs['GitHubClientId'] !== inputs.GitHubClientId) { + options.push({ key: 'GitHubClientId', value: inputs.GitHubClientId }); + } + if ( + originInputs['GitHubClientSecret'] !== inputs.GitHubClientSecret && + inputs.GitHubClientSecret !== '' + ) { + options.push({ + key: 'GitHubClientSecret', + value: inputs.GitHubClientSecret, + }); + } + + if (options.length > 0) { + await updateOptions(options); + } + }; + + const submitOIDCSettings = async () => { + if (inputs['oidc.well_known'] !== '') { + if ( + !inputs['oidc.well_known'].startsWith('http://') && + !inputs['oidc.well_known'].startsWith('https://') + ) { + showError('Well-Known URL 必须以 http:// 或 https:// 开头'); + return; + } + try { + const res = await API.get(inputs['oidc.well_known']); + inputs['oidc.authorization_endpoint'] = + res.data['authorization_endpoint']; + inputs['oidc.token_endpoint'] = res.data['token_endpoint']; + inputs['oidc.user_info_endpoint'] = res.data['userinfo_endpoint']; + showSuccess('获取 OIDC 配置成功!'); + } catch (err) { + console.error(err); + showError( + '获取 OIDC 配置失败,请检查网络状况和 Well-Known URL 是否正确', + ); + return; + } + } + + const options = []; + + if (originInputs['oidc.well_known'] !== inputs['oidc.well_known']) { + options.push({ + key: 'oidc.well_known', + value: inputs['oidc.well_known'], + }); + } + if (originInputs['oidc.client_id'] !== inputs['oidc.client_id']) { + options.push({ key: 'oidc.client_id', value: inputs['oidc.client_id'] }); + } + if ( + originInputs['oidc.client_secret'] !== inputs['oidc.client_secret'] && + inputs['oidc.client_secret'] !== '' + ) { + options.push({ + key: 'oidc.client_secret', + value: inputs['oidc.client_secret'], + }); + } + if ( + originInputs['oidc.authorization_endpoint'] !== + inputs['oidc.authorization_endpoint'] + ) { + options.push({ + key: 'oidc.authorization_endpoint', + value: inputs['oidc.authorization_endpoint'], + }); + } + if (originInputs['oidc.token_endpoint'] !== inputs['oidc.token_endpoint']) { + options.push({ + key: 'oidc.token_endpoint', + value: inputs['oidc.token_endpoint'], + }); + } + if ( + originInputs['oidc.user_info_endpoint'] !== + inputs['oidc.user_info_endpoint'] + ) { + options.push({ + key: 'oidc.user_info_endpoint', + value: inputs['oidc.user_info_endpoint'], + }); + } + + if (options.length > 0) { + await updateOptions(options); + } + }; + + const submitTelegramSettings = async () => { + const options = [ + { key: 'TelegramBotToken', value: inputs.TelegramBotToken }, + { key: 'TelegramBotName', value: inputs.TelegramBotName }, + ]; + await updateOptions(options); + }; + + const submitTurnstile = async () => { + const options = []; + + if (originInputs['TurnstileSiteKey'] !== inputs.TurnstileSiteKey) { + options.push({ key: 'TurnstileSiteKey', value: inputs.TurnstileSiteKey }); + } + if ( + originInputs['TurnstileSecretKey'] !== inputs.TurnstileSecretKey && + inputs.TurnstileSecretKey !== '' + ) { + options.push({ + key: 'TurnstileSecretKey', + value: inputs.TurnstileSecretKey, + }); + } + + if (options.length > 0) { + await updateOptions(options); + } + }; + + const submitLinuxDOOAuth = async () => { + const options = []; + + if (originInputs['LinuxDOClientId'] !== inputs.LinuxDOClientId) { + options.push({ key: 'LinuxDOClientId', value: inputs.LinuxDOClientId }); + } + if ( + originInputs['LinuxDOClientSecret'] !== inputs.LinuxDOClientSecret && + inputs.LinuxDOClientSecret !== '' + ) { + options.push({ + key: 'LinuxDOClientSecret', + value: inputs.LinuxDOClientSecret, + }); + } + if (originInputs['LinuxDOMinimumTrustLevel'] !== inputs.LinuxDOMinimumTrustLevel) { + options.push({ + key: 'LinuxDOMinimumTrustLevel', + value: inputs.LinuxDOMinimumTrustLevel.toString(), + }); + } + + if (options.length > 0) { + await updateOptions(options); + } + }; + + const submitIDCFlareOAuth = async () => { + const options = []; + + if (originInputs['IDCFlareClientId'] !== inputs.IDCFlareClientId) { + options.push({ key: 'IDCFlareClientId', value: inputs.IDCFlareClientId }); + } + if ( + originInputs['IDCFlareClientSecret'] !== inputs.IDCFlareClientSecret && + inputs.IDCFlareClientSecret !== '' + ) { + options.push({ + key: 'IDCFlareClientSecret', + value: inputs.IDCFlareClientSecret, + }); + } + if (originInputs['IDCFlareMinimumTrustLevel'] !== inputs.IDCFlareMinimumTrustLevel) { + options.push({ + key: 'IDCFlareMinimumTrustLevel', + value: inputs.IDCFlareMinimumTrustLevel.toString(), + }); + } + + if (options.length > 0) { + await updateOptions(options); + } + }; + + const submitReverseProxy = async () => { + const options = []; + + if (originInputs['ReverseProxyEnabled'] !== inputs.ReverseProxyEnabled) { + options.push({ key: 'ReverseProxyEnabled', value: inputs.ReverseProxyEnabled }); + } + if (originInputs['ReverseProxyProvider'] !== inputs.ReverseProxyProvider) { + options.push({ key: 'ReverseProxyProvider', value: inputs.ReverseProxyProvider }); + } + + if (options.length > 0) { + await updateOptions(options); + } + }; + + const handleCheckboxChange = async (optionKey, event) => { + const value = event.target.checked; + + if (optionKey === 'PasswordLoginEnabled' && !value) { + setShowPasswordLoginConfirmModal(true); + } else { + await updateOptions([{ key: optionKey, value }]); + } + if (optionKey === 'LinuxDOOAuthEnabled') { + setLinuxDOOAuthEnabled(value); + } + if (optionKey === 'IDCFlareOAuthEnabled') { + setIDCFlareOAuthEnabled(value); + } + }; + + const handlePasswordLoginConfirm = async () => { + await updateOptions([{ key: 'PasswordLoginEnabled', value: false }]); + setShowPasswordLoginConfirmModal(false); + }; + + return ( +
+ {isLoaded ? ( +
(formApiRef.current = api)} + > + {({ formState, values, formApi }) => ( +
+ + + + + + + + + + 用以支持系统在反向代理后运行时正确识别客户端IP地址 + + handleCheckboxChange('ReverseProxyEnabled', e) + } + > + 系统在反向代理后运行 + + {inputs.ReverseProxyEnabled && ( + + + + Nginx / OpenResty (通用) + Cloudflare + + + + )} + + + + + + + (支持{' '} + + new-api-worker + + ) + + + + + + + + + + + + + + + + + (当前仅支持易支付接口,默认使用上方服务器地址作为回调地址!) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + handleCheckboxChange('PasswordLoginEnabled', e) + } + > + 允许通过密码进行登录 + + + handleCheckboxChange('PasswordRegisterEnabled', e) + } + > + 允许通过密码进行注册 + + + handleCheckboxChange('EmailVerificationEnabled', e) + } + > + 通过密码注册时需要进行邮箱验证 + + + handleCheckboxChange('RegisterEnabled', e) + } + > + 允许新用户注册 + + + handleCheckboxChange('TurnstileCheckEnabled', e) + } + > + 启用 Turnstile 用户校验 + + + + + handleCheckboxChange('GitHubOAuthEnabled', e) + } + > + 允许通过 GitHub 账户登录 & 注册 + + + handleCheckboxChange('LinuxDOOAuthEnabled', e) + } + > + 允许通过 Linux DO 账户登录 & 注册 + + + handleCheckboxChange('IDCFlareOAuthEnabled', e) + } + > + 允许通过 IDC Flare 账户登录 & 注册 + + + handleCheckboxChange('WeChatAuthEnabled', e) + } + > + 允许通过微信登录 & 注册 + + + handleCheckboxChange('TelegramOAuthEnabled', e) + } + > + 允许通过 Telegram 进行登录 + + + handleCheckboxChange('oidc.enabled', e) + } + > + 允许通过 OIDC 进行登录 + + + + + + + + + 用以防止恶意用户利用临时邮箱批量注册 + + + + handleCheckboxChange( + 'EmailDomainRestrictionEnabled', + e, + ) + } + > + 启用邮箱域名白名单 + + + + + handleCheckboxChange( + 'EmailAliasRestrictionEnabled', + e, + ) + } + > + 启用邮箱别名限制 + + + + + setEmailToAdd(value)} + style={{ marginTop: 16 }} + suffix={ + + } + onEnterPress={handleAddEmail} + /> + + + + + + 用以支持系统的邮件发送 + + + + + + + + + + + + + + + + + + + + + handleCheckboxChange('SMTPSSLEnabled', e) + } + > + 启用SMTP SSL + + + + + + + + + + 用以支持通过 OIDC 登录,例如 Okta、Auth0 等兼容 OIDC 协议的 + IdP + + + + 若你的 OIDC Provider 支持 Discovery Endpoint,你可以仅填写 + OIDC Well-Known URL,系统会自动获取 OIDC 配置 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 用以支持通过 GitHub 进行登录注册 + + + + + + + + + + + + + + + + 用以支持通过 Linux DO 进行登录注册 + + 点击此处 + + 管理你的 LinuxDO OAuth App + + + + + + + + + + + + + + + + + + + + + 用以支持通过 IDC Flare 进行登录注册 + + 点击此处 + + 管理你的 IDC Flare OAuth App + + + + + + + + + + + + + + + + + + + + 用以支持通过微信进行登录注册 + + + + + + + + + + + + + + + + + + 用以支持通过 Telegram 进行登录注册 + + + + + + + + + + + + + + + 用以支持用户校验 + + + + + + + + + + + + + { + setShowPasswordLoginConfirmModal(false); + formApiRef.current.setValue('PasswordLoginEnabled', true); + }} + okText='确认' + cancelText='取消' + > +

您确定要取消密码登录功能吗?这可能会影响用户的登录方式。

+
+
+ )} +
+ ) : ( +
+ +
+ )} +
+ ); +}; + +export default SystemSetting; diff --git a/web/src/components/TaskLogsTable.js b/web/src/components/TaskLogsTable.js new file mode 100644 index 0000000000000000000000000000000000000000..0b675907fbe3de3cb0ade4d45c0a8a7eba22681e --- /dev/null +++ b/web/src/components/TaskLogsTable.js @@ -0,0 +1,530 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { Label } from 'semantic-ui-react'; +import { + API, + copy, + isAdmin, + showError, + showSuccess, + timestamp2string, +} from '../helpers'; + +import { + Table, + Tag, + Form, + Button, + Layout, + Modal, + Typography, + Progress, + Card, +} from '@douyinfe/semi-ui'; +import { ITEMS_PER_PAGE } from '../constants'; + +const colors = [ + 'amber', + 'blue', + 'cyan', + 'green', + 'grey', + 'indigo', + 'light-blue', + 'lime', + 'orange', + 'pink', + 'purple', + 'red', + 'teal', + 'violet', + 'yellow', +]; + +const renderTimestamp = (timestampInSeconds) => { + const date = new Date(timestampInSeconds * 1000); // 从秒转换为毫秒 + + const year = date.getFullYear(); // 获取年份 + const month = ('0' + (date.getMonth() + 1)).slice(-2); // 获取月份,从0开始需要+1,并保证两位数 + const day = ('0' + date.getDate()).slice(-2); // 获取日期,并保证两位数 + const hours = ('0' + date.getHours()).slice(-2); // 获取小时,并保证两位数 + const minutes = ('0' + date.getMinutes()).slice(-2); // 获取分钟,并保证两位数 + const seconds = ('0' + date.getSeconds()).slice(-2); // 获取秒钟,并保证两位数 + + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; // 格式化输出 +}; + +function renderDuration(submit_time, finishTime) { + // 确保startTime和finishTime都是有效的时间戳 + if (!submit_time || !finishTime) return 'N/A'; + + // 将时间戳转换为Date对象 + const start = new Date(submit_time); + const finish = new Date(finishTime); + + // 计算时间差(毫秒) + const durationMs = finish - start; + + // 将时间差转换为秒,并保留一位小数 + const durationSec = (durationMs / 1000).toFixed(1); + + // 设置颜色:大于60秒则为红色,小于等于60秒则为绿色 + const color = durationSec > 60 ? 'red' : 'green'; + + // 返回带有样式的颜色标签 + return ( + + {durationSec} 秒 + + ); +} + +const LogsTable = () => { + const [isModalOpen, setIsModalOpen] = useState(false); + const [modalContent, setModalContent] = useState(''); + const isAdminUser = isAdmin(); + const columns = [ + { + title: '提交时间', + dataIndex: 'submit_time', + render: (text, record, index) => { + return
{text ? renderTimestamp(text) : '-'}
; + }, + }, + { + title: '结束时间', + dataIndex: 'finish_time', + render: (text, record, index) => { + return
{text ? renderTimestamp(text) : '-'}
; + }, + }, + { + title: '进度', + dataIndex: 'progress', + width: 50, + render: (text, record, index) => { + return ( +
+ { + // 转换例如100%为数字100,如果text未定义,返回0 + isNaN(text.replace('%', '')) ? ( + text + ) : ( + + ) + } +
+ ); + }, + }, + { + title: '花费时间', + dataIndex: 'finish_time', // 以finish_time作为dataIndex + key: 'finish_time', + render: (finish, record) => { + // 假设record.start_time是存在的,并且finish是完成时间的时间戳 + return <>{finish ? renderDuration(record.submit_time, finish) : '-'}; + }, + }, + { + title: '渠道', + dataIndex: 'channel_id', + className: isAdminUser ? 'tableShow' : 'tableHiddle', + render: (text, record, index) => { + return ( +
+ { + copyText(text); // 假设copyText是用于文本复制的函数 + }} + > + {' '} + {text}{' '} + +
+ ); + }, + }, + { + title: '平台', + dataIndex: 'platform', + render: (text, record, index) => { + return
{renderPlatform(text)}
; + }, + }, + { + title: '类型', + dataIndex: 'action', + render: (text, record, index) => { + return
{renderType(text)}
; + }, + }, + { + title: '任务ID(点击查看详情)', + dataIndex: 'task_id', + render: (text, record, index) => { + return ( + { + setModalContent(JSON.stringify(record, null, 2)); + setIsModalOpen(true); + }} + > +
{text}
+
+ ); + }, + }, + { + title: '任务状态', + dataIndex: 'status', + render: (text, record, index) => { + return
{renderStatus(text)}
; + }, + }, + + { + title: '失败原因', + dataIndex: 'fail_reason', + render: (text, record, index) => { + // 如果text未定义,返回替代文本,例如空字符串''或其他 + if (!text) { + return '无'; + } + + return ( + { + setModalContent(text); + setIsModalOpen(true); + }} + > + {text} + + ); + }, + }, + ]; + + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(true); + const [activePage, setActivePage] = useState(1); + const [logCount, setLogCount] = useState(ITEMS_PER_PAGE); + const [logType] = useState(0); + + let now = new Date(); + // 初始化start_timestamp为前一天 + let zeroNow = new Date(now.getFullYear(), now.getMonth(), now.getDate()); + const [inputs, setInputs] = useState({ + channel_id: '', + task_id: '', + start_timestamp: timestamp2string(zeroNow.getTime() / 1000), + end_timestamp: '', + }); + const { channel_id, task_id, start_timestamp, end_timestamp } = inputs; + + const handleInputChange = (value, name) => { + setInputs((inputs) => ({ ...inputs, [name]: value })); + }; + + const setLogsFormat = (logs) => { + for (let i = 0; i < logs.length; i++) { + logs[i].timestamp2string = timestamp2string(logs[i].created_at); + logs[i].key = '' + logs[i].id; + } + // data.key = '' + data.id + setLogs(logs); + setLogCount(logs.length + ITEMS_PER_PAGE); + // console.log(logCount); + }; + + const loadLogs = async (startIdx) => { + setLoading(true); + + let url = ''; + let localStartTimestamp = parseInt(Date.parse(start_timestamp) / 1000); + let localEndTimestamp = parseInt(Date.parse(end_timestamp) / 1000); + if (isAdminUser) { + url = `/api/task/?p=${startIdx}&channel_id=${channel_id}&task_id=${task_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; + } else { + url = `/api/task/self?p=${startIdx}&task_id=${task_id}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`; + } + const res = await API.get(url); + let { success, message, data } = res.data; + if (success) { + if (startIdx === 0) { + setLogsFormat(data); + } else { + let newLogs = [...logs]; + newLogs.splice(startIdx * ITEMS_PER_PAGE, data.length, ...data); + setLogsFormat(newLogs); + } + } else { + showError(message); + } + setLoading(false); + }; + + const pageData = logs.slice( + (activePage - 1) * ITEMS_PER_PAGE, + activePage * ITEMS_PER_PAGE, + ); + + const handlePageChange = (page) => { + setActivePage(page); + if (page === Math.ceil(logs.length / ITEMS_PER_PAGE) + 1) { + // In this case we have to load more data and then append them. + loadLogs(page - 1).then((r) => {}); + } + }; + + const refresh = async () => { + // setLoading(true); + setActivePage(1); + await loadLogs(0); + }; + + const copyText = async (text) => { + if (await copy(text)) { + showSuccess('已复制:' + text); + } else { + // setSearchKeyword(text); + Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text }); + } + }; + + useEffect(() => { + refresh().then(); + }, [logType]); + + const renderType = (type) => { + switch (type) { + case 'MUSIC': + return ( + + ); + case 'LYRICS': + return ( + + ); + + default: + return ( + + ); + } + }; + + const renderPlatform = (type) => { + switch (type) { + case 'suno': + return ( + + ); + default: + return ( + + ); + } + }; + + const renderStatus = (type) => { + switch (type) { + case 'SUCCESS': + return ( + + ); + case 'NOT_START': + return ( + + ); + case 'SUBMITTED': + return ( + + ); + case 'IN_PROGRESS': + return ( + + ); + case 'FAILURE': + return ( + + ); + case 'QUEUED': + return ( + + ); + case 'UNKNOWN': + return ( + + ); + case '': + return ( + + ); + default: + return ( + + ); + } + }; + + return ( + <> + +
+ <> + {isAdminUser && ( + handleInputChange(value, 'channel_id')} + /> + )} + handleInputChange(value, 'task_id')} + /> + + handleInputChange(value, 'start_timestamp')} + /> + handleInputChange(value, 'end_timestamp')} + /> + + + + + + + setIsModalOpen(false)} + onCancel={() => setIsModalOpen(false)} + closable={null} + bodyStyle={{ height: '400px', overflow: 'auto' }} // 设置模态框内容区域样式 + width={800} // 设置模态框宽度 + > +

{modalContent}

+
+ + + ); +}; + +export default LogsTable; diff --git a/web/src/components/TokensTable.js b/web/src/components/TokensTable.js new file mode 100644 index 0000000000000000000000000000000000000000..f327bee39df0d72d4979920ae4c1c1e00602b0f6 --- /dev/null +++ b/web/src/components/TokensTable.js @@ -0,0 +1,629 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { + API, + copy, + showError, + showSuccess, + timestamp2string, +} from '../helpers'; + +import { ITEMS_PER_PAGE } from '../constants'; +import { renderGroup, renderQuota } from '../helpers/render'; +import { + Button, + Divider, + Dropdown, + Form, + Modal, + Popconfirm, + Popover, + Space, + SplitButtonGroup, + Table, + Tag, +} from '@douyinfe/semi-ui'; + +import { IconTreeTriangleDown } from '@douyinfe/semi-icons'; +import EditToken from '../pages/Token/EditToken'; +import { useTranslation } from 'react-i18next'; + +function renderTimestamp(timestamp) { + return <>{timestamp2string(timestamp)}; +} + +const TokensTable = () => { + const { t } = useTranslation(); + + const renderStatus = (status, model_limits_enabled = false) => { + switch (status) { + case 1: + if (model_limits_enabled) { + return ( + + {t('已启用:限制模型')} + + ); + } else { + return ( + + {t('已启用')} + + ); + } + case 2: + return ( + + {t('已禁用')} + + ); + case 3: + return ( + + {t('已过期')} + + ); + case 4: + return ( + + {t('已耗尽')} + + ); + default: + return ( + + {t('未知状态')} + + ); + } + }; + + const columns = [ + { + title: t('名称'), + dataIndex: 'name', + }, + { + title: t('状态'), + dataIndex: 'status', + key: 'status', + render: (text, record, index) => { + return ( +
+ + {renderStatus(text, record.model_limits_enabled)} + {renderGroup(record.group)} + +
+ ); + }, + }, + { + title: t('已用额度'), + dataIndex: 'used_quota', + render: (text, record, index) => { + return
{renderQuota(parseInt(text))}
; + }, + }, + { + title: t('剩余额度'), + dataIndex: 'remain_quota', + render: (text, record, index) => { + return ( +
+ {record.unlimited_quota ? ( + + {t('无限制')} + + ) : ( + + {renderQuota(parseInt(text))} + + )} +
+ ); + }, + }, + { + title: t('创建时间'), + dataIndex: 'created_time', + render: (text, record, index) => { + return
{renderTimestamp(text)}
; + }, + }, + { + title: t('过期时间'), + dataIndex: 'expired_time', + render: (text, record, index) => { + return ( +
+ {record.expired_time === -1 ? t('永不过期') : renderTimestamp(text)} +
+ ); + }, + }, + { + title: '', + dataIndex: 'operate', + render: (text, record, index) => { + let chats = localStorage.getItem('chats'); + let chatsArray = []; + let shouldUseCustom = true; + + if (shouldUseCustom) { + try { + // console.log(chats); + chats = JSON.parse(chats); + // check chats is array + if (Array.isArray(chats)) { + for (let i = 0; i < chats.length; i++) { + let chat = {}; + chat.node = 'item'; + // c is a map + // chat.key = chats[i].name; + // console.log(chats[i]) + for (let key in chats[i]) { + if (chats[i].hasOwnProperty(key)) { + chat.key = i; + chat.name = key; + chat.onClick = () => { + onOpenLink(key, chats[i][key], record); + }; + } + } + chatsArray.push(chat); + } + } + } catch (e) { + console.log(e); + showError(t('聊天链接配置错误,请联系管理员')); + } + } + return ( +
+ + + + + {chatsArray.length > 0 && ( + + + + + + + )} + { + manageToken(record.id, 'delete', record).then(() => { + removeRecord(record.key); + }); + }} + > + + + {record.status === 1 ? ( + + ) : ( + + )} + +
+ ); + }, + }, + ]; + + const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); + const [showEdit, setShowEdit] = useState(false); + const [tokens, setTokens] = useState([]); + const [selectedKeys, setSelectedKeys] = useState([]); + const [tokenCount, setTokenCount] = useState(pageSize); + const [loading, setLoading] = useState(true); + const [activePage, setActivePage] = useState(1); + const [searchKeyword, setSearchKeyword] = useState(''); + const [searchToken, setSearchToken] = useState(''); + const [searching, setSearching] = useState(false); + const [chats, setChats] = useState([]); + const [editingToken, setEditingToken] = useState({ + id: undefined, + }); + + const closeEdit = () => { + setShowEdit(false); + setTimeout(() => { + setEditingToken({ + id: undefined, + }); + }, 500); + }; + + const setTokensFormat = (tokens) => { + setTokens(tokens); + if (tokens.length >= pageSize) { + setTokenCount(tokens.length + pageSize); + } else { + setTokenCount(tokens.length); + } + }; + + let pageData = tokens.slice( + (activePage - 1) * pageSize, + activePage * pageSize, + ); + const loadTokens = async (startIdx) => { + setLoading(true); + const res = await API.get(`/api/token/?p=${startIdx}&size=${pageSize}`); + const { success, message, data } = res.data; + if (success) { + if (startIdx === 0) { + setTokensFormat(data); + } else { + let newTokens = [...tokens]; + newTokens.splice(startIdx * pageSize, data.length, ...data); + setTokensFormat(newTokens); + } + } else { + showError(message); + } + setLoading(false); + }; + + const refresh = async () => { + await loadTokens(activePage - 1); + }; + + const copyText = async (text) => { + if (await copy(text)) { + showSuccess(t('已复制到剪贴板!')); + } else { + Modal.error({ + title: t('无法复制到剪贴板,请手动复制'), + content: text, + size: 'large', + }); + } + }; + + const onOpenLink = async (type, url, record) => { + // console.log(type, url, key); + let status = localStorage.getItem('status'); + let serverAddress = ''; + if (status) { + status = JSON.parse(status); + serverAddress = status.server_address; + } + if (serverAddress === '') { + serverAddress = window.location.origin; + } + let encodedServerAddress = encodeURIComponent(serverAddress); + url = url.replaceAll('{address}', encodedServerAddress); + url = url.replaceAll('{key}', 'sk-' + record.key); + + window.open(url, '_blank'); + }; + + useEffect(() => { + loadTokens(0) + .then() + .catch((reason) => { + showError(reason); + }); + }, [pageSize]); + + const removeRecord = (key) => { + let newDataSource = [...tokens]; + if (key != null) { + let idx = newDataSource.findIndex((data) => data.key === key); + + if (idx > -1) { + newDataSource.splice(idx, 1); + setTokensFormat(newDataSource); + } + } + }; + + const manageToken = async (id, action, record) => { + setLoading(true); + let data = { id }; + let res; + switch (action) { + case 'delete': + res = await API.delete(`/api/token/${id}/`); + break; + case 'enable': + data.status = 1; + res = await API.put('/api/token/?status_only=true', data); + break; + case 'disable': + data.status = 2; + res = await API.put('/api/token/?status_only=true', data); + break; + } + const { success, message } = res.data; + if (success) { + showSuccess('操作成功完成!'); + let token = res.data.data; + let newTokens = [...tokens]; + // let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx; + if (action === 'delete') { + } else { + record.status = token.status; + // newTokens[realIdx].status = token.status; + } + setTokensFormat(newTokens); + } else { + showError(message); + } + setLoading(false); + }; + + const searchTokens = async () => { + if (searchKeyword === '' && searchToken === '') { + // if keyword is blank, load files instead. + await loadTokens(0); + setActivePage(1); + return; + } + setSearching(true); + const res = await API.get( + `/api/token/search?keyword=${searchKeyword}&token=${searchToken}`, + ); + const { success, message, data } = res.data; + if (success) { + setTokensFormat(data); + setActivePage(1); + } else { + showError(message); + } + setSearching(false); + }; + + const handleKeywordChange = async (value) => { + setSearchKeyword(value.trim()); + }; + + const handleSearchTokenChange = async (value) => { + setSearchToken(value.trim()); + }; + + const sortToken = (key) => { + if (tokens.length === 0) return; + setLoading(true); + let sortedTokens = [...tokens]; + sortedTokens.sort((a, b) => { + return ('' + a[key]).localeCompare(b[key]); + }); + if (sortedTokens[0].id === tokens[0].id) { + sortedTokens.reverse(); + } + setTokens(sortedTokens); + setLoading(false); + }; + + const handlePageChange = (page) => { + setActivePage(page); + if (page === Math.ceil(tokens.length / pageSize) + 1) { + // In this case we have to load more data and then append them. + loadTokens(page - 1).then((r) => {}); + } + }; + + const rowSelection = { + onSelect: (record, selected) => {}, + onSelectAll: (selected, selectedRows) => {}, + onChange: (selectedRowKeys, selectedRows) => { + setSelectedKeys(selectedRows); + }, + }; + + const handleRow = (record, index) => { + if (record.status !== 1) { + return { + style: { + background: 'var(--semi-color-disabled-border)', + }, + }; + } else { + return {}; + } + }; + + return ( + <> + +
+ + + + + +
+ + +
+ +
+ t('第 {{start}} - {{end}} 条,共 {{total}} 条', { + start: page.currentStart, + end: page.currentEnd, + total: tokens.length, + }), + onPageSizeChange: (size) => { + setPageSize(size); + setActivePage(1); + }, + onPageChange: handlePageChange, + }} + loading={loading} + rowSelection={rowSelection} + onRow={handleRow} + >
+ + ); +}; + +export default TokensTable; diff --git a/web/src/components/UsersTable.js b/web/src/components/UsersTable.js new file mode 100644 index 0000000000000000000000000000000000000000..81c072e2e15f62df27cece82c4ba4a1de46aeca2 --- /dev/null +++ b/web/src/components/UsersTable.js @@ -0,0 +1,535 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React, { useEffect, useState } from 'react'; +import { API, showError, showSuccess } from '../helpers'; +import { + Button, + Form, + Popconfirm, + Space, + Table, + Tag, + Tooltip, +} from '@douyinfe/semi-ui'; +import { ITEMS_PER_PAGE } from '../constants'; +import { renderGroup, renderNumber, renderQuota } from '../helpers/render'; +import AddUser from '../pages/User/AddUser'; +import EditUser from '../pages/User/EditUser'; +import { useTranslation } from 'react-i18next'; + +const UsersTable = () => { + const { t } = useTranslation(); + + function renderRole(role) { + switch (role) { + case 1: + return {t('普通用户')}; + case 10: + return ( + + {t('管理员')} + + ); + case 100: + return ( + + {t('超级管理员')} + + ); + default: + return ( + + {t('未知身份')} + + ); + } + } + const columns = [ + { + title: 'ID', + dataIndex: 'id', + }, + { + title: t('用户名'), + dataIndex: 'username', + }, + { + title: t('分组'), + dataIndex: 'group', + render: (text, record, index) => { + return
{renderGroup(text)}
; + }, + }, + { + title: t('统计信息'), + dataIndex: 'info', + render: (text, record, index) => { + return ( +
+ + + + {renderQuota(record.quota)} + + + + + {renderQuota(record.used_quota)} + + + + + {renderNumber(record.request_count)} + + + +
+ ); + }, + }, + { + title: t('邀请信息'), + dataIndex: 'invite', + render: (text, record, index) => { + return ( +
+ + + + {renderNumber(record.aff_count)} + + + + + {renderQuota(record.aff_history_quota)} + + + + {record.inviter_id === 0 ? ( + + {t('无')} + + ) : ( + + {record.inviter_id} + + )} + + +
+ ); + }, + }, + { + title: t('角色'), + dataIndex: 'role', + render: (text, record, index) => { + return
{renderRole(text)}
; + }, + }, + { + title: t('状态'), + dataIndex: 'status', + render: (text, record, index) => { + return ( +
+ {record.DeletedAt !== null ? ( + {t('已注销')} + ) : ( + renderStatus(text) + )} +
+ ); + }, + }, + { + title: '', + dataIndex: 'operate', + render: (text, record, index) => ( +
+ {record.DeletedAt !== null ? ( + <> + ) : ( + <> + { + manageUser(record.id, 'promote', record); + }} + > + + + { + manageUser(record.id, 'demote', record); + }} + > + + + {record.status === 1 ? ( + + ) : ( + + )} + + { + manageUser(record.id, 'delete', record).then(() => { + removeRecord(record.id); + }); + }} + > + + + + )} +
+ ), + }, + ]; + + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [activePage, setActivePage] = useState(1); + const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); + const [searchKeyword, setSearchKeyword] = useState(''); + const [searching, setSearching] = useState(false); + const [searchGroup, setSearchGroup] = useState(''); + const [groupOptions, setGroupOptions] = useState([]); + const [userCount, setUserCount] = useState(ITEMS_PER_PAGE); + const [showAddUser, setShowAddUser] = useState(false); + const [showEditUser, setShowEditUser] = useState(false); + const [editingUser, setEditingUser] = useState({ + id: undefined, + }); + + const removeRecord = (key) => { + let newDataSource = [...users]; + if (key != null) { + let idx = newDataSource.findIndex((data) => data.id === key); + + if (idx > -1) { + // update deletedAt + newDataSource[idx].DeletedAt = new Date(); + setUsers(newDataSource); + } + } + }; + + const setUserFormat = (users) => { + for (let i = 0; i < users.length; i++) { + users[i].key = users[i].id; + } + setUsers(users); + }; + + const loadUsers = async (startIdx, pageSize) => { + const res = await API.get(`/api/user/?p=${startIdx}&page_size=${pageSize}`); + const { success, message, data } = res.data; + if (success) { + const newPageData = data.items; + setActivePage(data.page); + setUserCount(data.total); + setUserFormat(newPageData); + } else { + showError(message); + } + setLoading(false); + }; + + useEffect(() => { + loadUsers(0, pageSize) + .then() + .catch((reason) => { + showError(reason); + }); + fetchGroups().then(); + }, []); + + const manageUser = async (userId, action, record) => { + const res = await API.post('/api/user/manage', { + id: userId, + action, + }); + const { success, message } = res.data; + if (success) { + showSuccess('操作成功完成!'); + let user = res.data.data; + let newUsers = [...users]; + if (action === 'delete') { + } else { + record.status = user.status; + record.role = user.role; + } + setUsers(newUsers); + } else { + showError(message); + } + }; + + const renderStatus = (status) => { + switch (status) { + case 1: + return {t('已激活')}; + case 2: + return ( + + {t('已封禁')} + + ); + default: + return ( + + {t('未知状态')} + + ); + } + }; + + const searchUsers = async ( + startIdx, + pageSize, + searchKeyword, + searchGroup, + ) => { + if (searchKeyword === '' && searchGroup === '') { + // if keyword is blank, load files instead. + await loadUsers(startIdx, pageSize); + return; + } + setSearching(true); + const res = await API.get( + `/api/user/search?keyword=${searchKeyword}&group=${searchGroup}&p=${startIdx + 1}&page_size=${pageSize}`, + ); + const { success, message, data } = res.data; + if (success) { + const newPageData = data.items; + setActivePage(data.page); + setUserCount(data.total); + setUserFormat(newPageData); + } else { + showError(message); + } + setSearching(false); + }; + + const handleKeywordChange = async (value) => { + setSearchKeyword(value.trim()); + }; + + const handlePageChange = (page) => { + setActivePage(page); + if (searchKeyword === '' && searchGroup === '') { + loadUsers(page - 1, pageSize).then(); + } else { + searchUsers(page - 1, pageSize, searchKeyword, searchGroup).then(); + } + }; + + const closeAddUser = () => { + setShowAddUser(false); + }; + + const closeEditUser = () => { + setShowEditUser(false); + setEditingUser({ + id: undefined, + }); + }; + + const refresh = async () => { + setActivePage(1); + if (searchKeyword === '') { + await loadUsers(0, pageSize); + } else { + await searchUsers(0, pageSize, searchKeyword, searchGroup); + } + }; + + const fetchGroups = async () => { + try { + let res = await API.get(`/api/group/`); + // add 'all' option + // res.data.data.unshift('all'); + if (res === undefined) { + return; + } + setGroupOptions( + res.data.data.map((group) => ({ + label: group, + value: group, + })), + ); + } catch (error) { + showError(error.message); + } + }; + + const handlePageSizeChange = async (size) => { + localStorage.setItem('page-size', size + ''); + setPageSize(size); + setActivePage(1); + loadUsers(activePage, size) + .then() + .catch((reason) => { + showError(reason); + }); + }; + + return ( + <> + + +
{ + setActivePage(1); + searchUsers(0, pageSize, searchKeyword, searchGroup); + }} + labelPosition='left' + > +
+ + + handleKeywordChange(value)} + /> + + + { + setSearchGroup(value); + setActivePage(1); + searchUsers(0, pageSize, searchKeyword, value); + }} + /> + + + +
+
+ + + t('第 {{start}} - {{end}} 条,共 {{total}} 条', { + start: page.currentStart, + end: page.currentEnd, + total: users.length, + }), + currentPage: activePage, + pageSize: pageSize, + total: userCount, + pageSizeOpts: [10, 20, 50, 100], + showSizeChanger: true, + onPageSizeChange: (size) => { + handlePageSizeChange(size); + }, + onPageChange: handlePageChange, + }} + loading={loading} + /> + + ); +}; + +export default UsersTable; diff --git a/web/src/components/WeChatIcon.js b/web/src/components/WeChatIcon.js new file mode 100644 index 0000000000000000000000000000000000000000..664a11d2d187fa2007e2591740b3bb9d19d87f79 --- /dev/null +++ b/web/src/components/WeChatIcon.js @@ -0,0 +1,54 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React from 'react'; +import { Icon } from '@douyinfe/semi-ui'; + +const WeChatIcon = () => { + function CustomIcon() { + return ( + + + + + ); + } + + return ( +
+ } /> +
+ ); +}; + +export default WeChatIcon; diff --git a/web/src/components/custom/TextInput.js b/web/src/components/custom/TextInput.js new file mode 100644 index 0000000000000000000000000000000000000000..b25441a7ce48f8aca18834c97381897e8650fbc7 --- /dev/null +++ b/web/src/components/custom/TextInput.js @@ -0,0 +1,46 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import { Input, Typography } from '@douyinfe/semi-ui'; +import React from 'react'; + +const TextInput = ({ + label, + name, + value, + onChange, + placeholder, + type = 'text', +}) => { + return ( + <> +
+ {label} +
+ onChange(value)} + value={value} + autoComplete='new-password' + /> + + ); +}; + +export default TextInput; diff --git a/web/src/components/custom/TextNumberInput.js b/web/src/components/custom/TextNumberInput.js new file mode 100644 index 0000000000000000000000000000000000000000..c65bf81567566dc3966cb4e5a84c0b8dd9c044f5 --- /dev/null +++ b/web/src/components/custom/TextNumberInput.js @@ -0,0 +1,39 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import { Input, InputNumber, Typography } from '@douyinfe/semi-ui'; +import React from 'react'; + +const TextNumberInput = ({ label, name, value, onChange, placeholder }) => { + return ( + <> +
+ {label} +
+ onChange(value)} + value={value} + autoComplete='new-password' + /> + + ); +}; + +export default TextNumberInput; diff --git a/web/src/components/fetchTokenKeys.js b/web/src/components/fetchTokenKeys.js new file mode 100644 index 0000000000000000000000000000000000000000..db2b0b7ffeb4272d2cf5045e1beeeb1c542d622b --- /dev/null +++ b/web/src/components/fetchTokenKeys.js @@ -0,0 +1,86 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +// src/hooks/useTokenKeys.js +import { useEffect, useState } from 'react'; +import { API, showError } from '../helpers'; + +async function fetchTokenKeys() { + try { + const response = await API.get('/api/token/?p=0&size=100'); + const { success, data } = response.data; + if (success) { + const activeTokens = data.filter((token) => token.status === 1); + return activeTokens.map((token) => token.key); + } else { + throw new Error('Failed to fetch token keys'); + } + } catch (error) { + console.error('Error fetching token keys:', error); + return []; + } +} + +function getServerAddress() { + let status = localStorage.getItem('status'); + let serverAddress = ''; + + if (status) { + try { + status = JSON.parse(status); + serverAddress = status.server_address || ''; + } catch (error) { + console.error('Failed to parse status from localStorage:', error); + } + } + + if (!serverAddress) { + serverAddress = window.location.origin; + } + + return serverAddress; +} + +export function useTokenKeys(id) { + const [keys, setKeys] = useState([]); + // const [chatLink, setChatLink] = useState(''); + const [serverAddress, setServerAddress] = useState(''); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const loadAllData = async () => { + const fetchedKeys = await fetchTokenKeys(); + if (fetchedKeys.length === 0) { + showError('当前没有可用的启用令牌,请确认是否有令牌处于启用状态!'); + setTimeout(() => { + window.location.href = '/app/tokens'; + }, 1500); // 延迟 1.5 秒后跳转 + } + setKeys(fetchedKeys); + setIsLoading(false); + // setChatLink(link); + + const address = getServerAddress(); + setServerAddress(address); + }; + + loadAllData(); + }, []); + + return { keys, serverAddress, isLoading }; +} diff --git a/web/src/components/shared/AuthFormLayout.js b/web/src/components/shared/AuthFormLayout.js new file mode 100644 index 0000000000000000000000000000000000000000..57796eaa2750f5e8a06e929885020cc340d56fb2 --- /dev/null +++ b/web/src/components/shared/AuthFormLayout.js @@ -0,0 +1,51 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import React from 'react'; +import { Card, Layout } from '@douyinfe/semi-ui'; +import Title from '@douyinfe/semi-ui/lib/es/typography/title'; + +const AuthFormLayout = ({ title, children }) => { + return ( +
+ + + +
+
+ + + {title} + + {children} + +
+
+
+
+
+ ); +}; + +export default AuthFormLayout; \ No newline at end of file diff --git a/web/src/components/shared/ModelMappingEditor.js b/web/src/components/shared/ModelMappingEditor.js new file mode 100644 index 0000000000000000000000000000000000000000..c542e53b48a4ea0bf041e58744a5d7a42b5fe6eb --- /dev/null +++ b/web/src/components/shared/ModelMappingEditor.js @@ -0,0 +1,354 @@ +/* +Copyright (c) 2025 Tethys Plex + +This file is part of Veloera. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +import { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Button, + Input, + Typography, + TextArea, +} from '@douyinfe/semi-ui'; +import { + IconPlusCircle, + IconMinusCircle, +} from '@douyinfe/semi-icons'; + +const MODEL_MAPPING_EXAMPLE = { + 'gpt-3.5-turbo': 'gpt-3.5-turbo-0125', +}; + +// Unsafe keys to prevent prototype pollution +const UNSAFE_KEYS = new Set(['__proto__', 'prototype', 'constructor']); + +/** + * ModelMappingEditor 组件 - 用于可视化编辑模型映射配置 + * + * @param {Object} props + * @param {string} props.value - 当前的 JSON 字符串值 + * @param {Function} props.onChange - 值变化时的回调函数 + * @param {string} props.placeholder - 占位符文本 + * @param {Function} props.onRealtimeChange - 实时变化回调(可选) + */ +const ModelMappingEditor = ({ value, onChange, placeholder, onRealtimeChange }) => { + const { t } = useTranslation(); + const [mappingPairs, setMappingPairs] = useState([]); + const [mode, setMode] = useState('visual'); // 'visual' or 'json' + const [jsonValue, setJsonValue] = useState(''); + const [jsonError, setJsonError] = useState(''); + + // 用于标记是否是内部更新,避免循环更新 + const isInternalUpdateRef = useRef(false); + + // Parse JSON value to key-value pairs + const parseJsonToMappings = (jsonStr) => { + if (!jsonStr || jsonStr.trim() === '') { + return []; + } + try { + const parsed = JSON.parse(jsonStr); + if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { + const unsafe = UNSAFE_KEYS; + return Object.entries(parsed) + .map(([key, value]) => [String(key).trim(), value]) + .filter(([k]) => k !== '' && !unsafe.has(k)) + .map(([key, value]) => ({ + id: Date.now() + Math.random(), + key, + value: value == null ? '' : String(value).trim(), + })); + } + return []; + } catch { + return []; + } + }; + + // Convert key-value pairs to JSON string + const mappingsToJson = (pairs) => { + if (!pairs || pairs.length === 0) { + return ''; + } + const obj = Object.create(null); + const unsafe = UNSAFE_KEYS; + pairs.forEach(pair => { + if (pair.key && pair.key.trim() !== '') { + const k = String(pair.key).trim(); + if (!unsafe.has(k)) { + const v = pair.value == null ? '' : String(pair.value).trim(); + obj[k] = v; + } + } + }); + return Object.keys(obj).length > 0 ? JSON.stringify(obj, null, 2) : ''; + }; + + // Initialize component state from value prop + useEffect(() => { + // 只有在非内部更新时才同步外部状态 + if (!isInternalUpdateRef.current) { + const pairs = parseJsonToMappings(value); + setMappingPairs(pairs.length > 0 ? pairs : [{ id: Date.now() + Math.random(), key: '', value: '' }]); + setJsonValue(value || ''); + setJsonError(''); + } + isInternalUpdateRef.current = false; + }, [value]); + + // Add new mapping pair + const addMappingPair = () => { + const newPairs = [...mappingPairs, { id: Date.now() + Math.random(), key: '', value: '' }]; + setMappingPairs(newPairs); + }; + + // Remove mapping pair + const removeMappingPair = (index) => { + const newPairs = mappingPairs.filter((_, i) => i !== index); + const finalPairs = newPairs.length > 0 ? newPairs : [{ id: Date.now() + Math.random(), key: '', value: '' }]; + setMappingPairs(finalPairs); + + // 立即更新父组件 + const jsonStr = mappingsToJson(finalPairs); + isInternalUpdateRef.current = true; + onChange(jsonStr); + + // 触发实时同步 + if (onRealtimeChange) { + onRealtimeChange(jsonStr); + } + }; + + // Update mapping pair - 只更新本地状态,不触发父组件更新 + const updateMappingPair = (index, field, value) => { + if (index < 0 || index >= mappingPairs.length) return; + if (field !== 'key' && field !== 'value') return; + const newPairs = [...mappingPairs]; + newPairs[index] = { ...newPairs[index], [field]: value }; + setMappingPairs(newPairs); + + // 触发实时同步回调,但不更新父组件的 model_mapping 状态 + if (onRealtimeChange) { + const jsonStr = mappingsToJson(newPairs); + onRealtimeChange(jsonStr); + } + }; + + // 在失去焦点时同步到父组件 + const handleInputBlur = () => { + const jsonStr = mappingsToJson(mappingPairs); + isInternalUpdateRef.current = true; + onChange(jsonStr); + }; + + // Handle mode switch + const switchMode = (newMode) => { + if (newMode === 'json' && mode === 'visual') { + // Switching from visual to JSON + const jsonStr = mappingsToJson(mappingPairs); + setJsonValue(jsonStr); + setJsonError(''); + } else if (newMode === 'visual' && mode === 'json') { + // Switching from JSON to visual + if (jsonValue.trim() === '') { + setMappingPairs([{ id: Date.now() + Math.random(), key: '', value: '' }]); + setJsonError(''); + } else { + const pairs = parseJsonToMappings(jsonValue); + setMappingPairs(pairs.length > 0 ? pairs : [{ id: Date.now() + Math.random(), key: '', value: '' }]); + setJsonError(''); + } + } + setMode(newMode); + }; + + // Handle JSON input change + const handleJsonChange = (newValue) => { + setJsonValue(newValue); + + // Validate JSON + if (newValue.trim() === '') { + setJsonError(''); + // 触发实时同步 + if (onRealtimeChange) { + onRealtimeChange(''); + } + return; + } + + try { + const parsed = JSON.parse(newValue); + if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { + // Filter out unsafe keys before calling onRealtimeChange + const unsafe = UNSAFE_KEYS; + const filtered = Object.fromEntries( + Object.entries(parsed).filter(([k]) => !unsafe.has(k)) + ); + setJsonError(''); + // 触发实时同步 + if (onRealtimeChange) { + onRealtimeChange(JSON.stringify(filtered, null, 2)); + } + } else { + setJsonError(t('请输入有效的JSON对象格式')); + } + } catch (error) { + setJsonError(t('JSON格式错误: {{message}}', { message: error.message })); + } + }; + + // 在 JSON 模式失去焦点时同步到父组件 + const handleJsonBlur = () => { + if (!jsonError && jsonValue.trim() !== '') { + try { + const parsed = JSON.parse(jsonValue); + if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { + const sanitized = mappingsToJson(parseJsonToMappings(jsonValue)); + isInternalUpdateRef.current = true; + onChange(sanitized); + setJsonValue(sanitized); + } + } catch (error) { + // 忽略错误,不更新父组件 + } + } else if (jsonValue.trim() === '') { + isInternalUpdateRef.current = true; + onChange(''); + } + }; + + // Fill template + const fillTemplate = () => { + const templateJson = JSON.stringify(MODEL_MAPPING_EXAMPLE, null, 2); + if (mode === 'visual') { + const pairs = parseJsonToMappings(templateJson); + setMappingPairs(pairs); + } else { + setJsonValue(templateJson); + } + + // 立即更新父组件和触发实时同步 + isInternalUpdateRef.current = true; + onChange(templateJson); + if (onRealtimeChange) { + onRealtimeChange(templateJson); + } + }; + + return ( +
+
+
+ + +
+ + {t('填入模板')} + +
+ + {mode === 'visual' ? ( +
+
+ {placeholder} +
+ + {mappingPairs.map((pair, index) => ( +
+ updateMappingPair(index, 'key', value)} + onBlur={handleInputBlur} + style={{ flex: 1, marginRight: 8 }} + /> + + updateMappingPair(index, 'value', value)} + onBlur={handleInputBlur} + style={{ flex: 1, marginRight: 8 }} + /> +
+ ))} + + +
+ ) : ( +
+