Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 49320936f8 | |||
| e268e69597 | |||
| 10e78ac60e | |||
| 44b2b859da | |||
| bfef0c5580 | |||
| 1e8055031a | |||
| 8e33ff8d90 | |||
| a619000340 | |||
| 78278ce96d | |||
| 76483d828e | |||
| 816a92c609 | |||
| 83e4d4363f | |||
| 1103449a4f | |||
| 56c7a7f066 | |||
| caa59c4c50 | |||
| 2546dfbe5d | |||
| 5fea202a7d | |||
| 7dce1d776b | |||
| 346af4d338 | |||
| abd5d3b96f | |||
| 49bd298d37 | |||
| 714a28ac29 | |||
| 0cf81c04c8 | |||
| 4186e9c990 | |||
| d8f68a6056 | |||
| 11bf50e722 | |||
| 32a84311aa | |||
| 6eaa2b2461 | |||
| 9f00f00546 | |||
| bd94d23343 | |||
| 5f1c14e2c0 | |||
| cdc12d5092 | |||
| e5967fd874 | |||
| e2f1d80697 | |||
| 28bc89ac7c |
@@ -1,4 +1,4 @@
|
|||||||
name: Auto I18N
|
name: Auto I18N Weekly
|
||||||
|
|
||||||
env:
|
env:
|
||||||
TRANSLATION_API_KEY: ${{ secrets.TRANSLATE_API_KEY }}
|
TRANSLATION_API_KEY: ${{ secrets.TRANSLATE_API_KEY }}
|
||||||
@@ -7,14 +7,15 @@ env:
|
|||||||
TRANSLATION_BASE_LOCALE: ${{ vars.AUTO_I18N_BASE_LOCALE || 'en-us'}}
|
TRANSLATION_BASE_LOCALE: ${{ vars.AUTO_I18N_BASE_LOCALE || 'en-us'}}
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
schedule:
|
||||||
types: [opened, synchronize, reopened]
|
# Runs at 00:00 UTC every Sunday.
|
||||||
|
# This corresponds to 08:00 AM UTC+8 (Beijing time) every Sunday.
|
||||||
|
- cron: "0 0 * * 0"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
auto-i18n:
|
auto-i18n:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == 'CherryHQ/cherry-studio'
|
|
||||||
name: Auto I18N
|
name: Auto I18N
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -24,45 +25,69 @@ jobs:
|
|||||||
- name: 🐈⬛ Checkout
|
- name: 🐈⬛ Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.ref }}
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: 📦 Setting Node.js
|
- name: 📦 Setting Node.js
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
package-manager-cache: false
|
|
||||||
|
|
||||||
- name: 📦 Install dependencies in isolated directory
|
- name: 📦 Install corepack
|
||||||
|
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||||
|
|
||||||
|
- name: 📂 Get yarn cache directory path
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: 💾 Cache yarn dependencies
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
node_modules
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
|
- name: 📦 Install dependencies
|
||||||
run: |
|
run: |
|
||||||
# 在临时目录安装依赖
|
yarn install
|
||||||
mkdir -p /tmp/translation-deps
|
|
||||||
cd /tmp/translation-deps
|
|
||||||
echo '{"dependencies": {"@cherrystudio/openai": "^6.5.0", "cli-progress": "^3.12.0", "tsx": "^4.20.3", "@biomejs/biome": "2.2.4"}}' > package.json
|
|
||||||
npm install --no-package-lock
|
|
||||||
|
|
||||||
# 设置 NODE_PATH 让项目能找到这些依赖
|
|
||||||
echo "NODE_PATH=/tmp/translation-deps/node_modules" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: 🏃♀️ Translate
|
- name: 🏃♀️ Translate
|
||||||
run: npx tsx scripts/sync-i18n.ts && npx tsx scripts/auto-translate-i18n.ts
|
run: yarn sync:i18n && yarn auto:i18n
|
||||||
|
|
||||||
- name: 🔍 Format
|
- name: 🔍 Format
|
||||||
run: cd /tmp/translation-deps && npx biome format --config-path /home/runner/work/cherry-studio/cherry-studio/biome.jsonc --write /home/runner/work/cherry-studio/cherry-studio/src/renderer/src/i18n/
|
run: yarn format
|
||||||
|
|
||||||
- name: 🔄 Commit changes
|
- name: 🔍 Check for changes
|
||||||
|
id: git_status
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "action@github.com"
|
# Check if there are any uncommitted changes
|
||||||
git config --local user.name "GitHub Action"
|
|
||||||
git add .
|
|
||||||
git reset -- package.json yarn.lock # 不提交 package.json 和 yarn.lock 的更改
|
git reset -- package.json yarn.lock # 不提交 package.json 和 yarn.lock 的更改
|
||||||
if git diff --cached --quiet; then
|
git diff --exit-code --quiet || echo "::set-output name=has_changes::true"
|
||||||
echo "No changes to commit"
|
git status --porcelain
|
||||||
else
|
|
||||||
git commit -m "fix(i18n): Auto update translations for PR #${{ github.event.pull_request.number }}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: 🚀 Push changes
|
- name: 📅 Set current date for PR title
|
||||||
uses: ad-m/github-push-action@master
|
id: set_date
|
||||||
|
run: echo "CURRENT_DATE=$(date +'%b %d, %Y')" >> $GITHUB_ENV # e.g., "Jun 06, 2024"
|
||||||
|
|
||||||
|
- name: 🚀 Create Pull Request if changes exist
|
||||||
|
if: steps.git_status.outputs.has_changes == 'true'
|
||||||
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }} # Use the built-in GITHUB_TOKEN for bot actions
|
||||||
branch: ${{ github.event.pull_request.head.ref }}
|
commit-message: "feat(bot): Weekly automated script run"
|
||||||
|
title: "🤖 Weekly Automated Update: ${{ env.CURRENT_DATE }}"
|
||||||
|
body: |
|
||||||
|
This PR includes changes generated by the weekly auto i18n.
|
||||||
|
Review the changes before merging.
|
||||||
|
|
||||||
|
---
|
||||||
|
_Generated by the automated weekly workflow_
|
||||||
|
branch: "auto-i18n-weekly-${{ github.run_id }}" # Unique branch name
|
||||||
|
base: "main" # Or 'develop', set your base branch
|
||||||
|
delete-branch: true # Delete the branch after merging or closing the PR
|
||||||
|
|
||||||
|
- name: 📢 Notify if no changes
|
||||||
|
if: steps.git_status.outputs.has_changes != 'true'
|
||||||
|
run: echo "Bot script ran, but no changes were detected. No PR created."
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ on:
|
|||||||
types: [opened]
|
types: [opened]
|
||||||
schedule:
|
schedule:
|
||||||
# Run every day at 8:30 Beijing Time (00:30 UTC)
|
# Run every day at 8:30 Beijing Time (00:30 UTC)
|
||||||
- cron: '30 0 * * *'
|
- cron: "30 0 * * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -54,9 +54,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
if: steps.check_time.outputs.should_delay == 'false'
|
if: steps.check_time.outputs.should_delay == 'false'
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: 22
|
||||||
|
|
||||||
- name: Process issue with Claude
|
- name: Process issue with Claude
|
||||||
if: steps.check_time.outputs.should_delay == 'false'
|
if: steps.check_time.outputs.should_delay == 'false'
|
||||||
@@ -121,9 +121,9 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: 22
|
||||||
|
|
||||||
- name: Process pending issues with Claude
|
- name: Process pending issues with Claude
|
||||||
uses: anthropics/claude-code-action@main
|
uses: anthropics/claude-code-action@main
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
contents: none
|
contents: none
|
||||||
steps:
|
steps:
|
||||||
- name: Close needs-more-info issues
|
- name: Close needs-more-info issues
|
||||||
uses: actions/stale@v9
|
uses: actions/stale@v10
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
only-labels: 'needs-more-info'
|
only-labels: 'needs-more-info'
|
||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
days-before-pr-close: -1
|
days-before-pr-close: -1
|
||||||
|
|
||||||
- name: Close inactive issues
|
- name: Close inactive issues
|
||||||
uses: actions/stale@v9
|
uses: actions/stale@v10
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-stale: ${{ env.daysBeforeStale }}
|
days-before-stale: ${{ env.daysBeforeStale }}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name: Nightly Build
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 17 * * *' # 1:00 BJ Time
|
- cron: "0 17 * * *" # 1:00 BJ Time
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -56,9 +56,9 @@ jobs:
|
|||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
|
|
||||||
- name: macos-latest dependencies fix
|
- name: macos-latest dependencies fix
|
||||||
if: matrix.os == 'macos-latest'
|
if: matrix.os == 'macos-latest'
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
brew install python-setuptools
|
brew install python-setuptools
|
||||||
|
|
||||||
- name: Install corepack
|
- name: Install corepack
|
||||||
run: corepack enable && corepack prepare yarn@4.6.0 --activate
|
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
- name: Get yarn cache directory path
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
@@ -208,7 +208,7 @@ jobs:
|
|||||||
echo "总计: $(find renamed-artifacts -type f | wc -l) 个文件"
|
echo "总计: $(find renamed-artifacts -type f | wc -l) 个文件"
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: cherry-studio-nightly-${{ steps.date.outputs.date }}-${{ matrix.os }}
|
name: cherry-studio-nightly-${{ steps.date.outputs.date }}-${{ matrix.os }}
|
||||||
path: renamed-artifacts/*
|
path: renamed-artifacts/*
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ jobs:
|
|||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
|
|
||||||
- name: Install corepack
|
- name: Install corepack
|
||||||
run: corepack enable && corepack prepare yarn@4.6.0 --activate
|
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
- name: Get yarn cache directory path
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
description: 'Release tag (e.g. v1.0.0)'
|
description: "Release tag (e.g. v1.0.0)"
|
||||||
required: true
|
required: true
|
||||||
default: 'v1.0.0'
|
default: "v1.0.0"
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- v*.*.*
|
- v*.*.*
|
||||||
@@ -47,9 +47,9 @@ jobs:
|
|||||||
npm version "$VERSION" --no-git-tag-version --allow-same-version
|
npm version "$VERSION" --no-git-tag-version --allow-same-version
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
|
|
||||||
- name: macos-latest dependencies fix
|
- name: macos-latest dependencies fix
|
||||||
if: matrix.os == 'macos-latest'
|
if: matrix.os == 'macos-latest'
|
||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
brew install python-setuptools
|
brew install python-setuptools
|
||||||
|
|
||||||
- name: Install corepack
|
- name: Install corepack
|
||||||
run: corepack enable && corepack prepare yarn@4.6.0 --activate
|
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
- name: Get yarn cache directory path
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
@@ -127,5 +127,5 @@ jobs:
|
|||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
makeLatest: false
|
makeLatest: false
|
||||||
tag: ${{ steps.get-tag.outputs.tag }}
|
tag: ${{ steps.get-tag.outputs.tag }}
|
||||||
artifacts: 'dist/*.exe,dist/*.zip,dist/*.dmg,dist/*.AppImage,dist/*.snap,dist/*.deb,dist/*.rpm,dist/*.tar.gz,dist/latest*.yml,dist/rc*.yml,dist/beta*.yml,dist/*.blockmap'
|
artifacts: "dist/*.exe,dist/*.zip,dist/*.dmg,dist/*.AppImage,dist/*.snap,dist/*.deb,dist/*.rpm,dist/*.tar.gz,dist/latest*.yml,dist/rc*.yml,dist/beta*.yml,dist/*.blockmap"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ This file provides guidance to AI coding assistants when working with code in th
|
|||||||
- **Keep it clear**: Write code that is easy to read, maintain, and explain.
|
- **Keep it clear**: Write code that is easy to read, maintain, and explain.
|
||||||
- **Match the house style**: Reuse existing patterns, naming, and conventions.
|
- **Match the house style**: Reuse existing patterns, naming, and conventions.
|
||||||
- **Search smart**: Prefer `ast-grep` for semantic queries; fall back to `rg`/`grep` when needed.
|
- **Search smart**: Prefer `ast-grep` for semantic queries; fall back to `rg`/`grep` when needed.
|
||||||
- **Build with HeroUI**: Use HeroUI for every new UI component; never add `antd` or `styled-components`.
|
|
||||||
- **Log centrally**: Route all logging through `loggerService` with the right context—no `console.log`.
|
- **Log centrally**: Route all logging through `loggerService` with the right context—no `console.log`.
|
||||||
- **Research via subagent**: Lean on `subagent` for external docs, APIs, news, and references.
|
- **Research via subagent**: Lean on `subagent` for external docs, APIs, news, and references.
|
||||||
- **Always propose before executing**: Before making any changes, clearly explain your planned approach and wait for explicit user approval to ensure alignment and prevent unwanted modifications.
|
- **Always propose before executing**: Before making any changes, clearly explain your planned approach and wait for explicit user approval to ensure alignment and prevent unwanted modifications.
|
||||||
@@ -41,7 +40,6 @@ This file provides guidance to AI coding assistants when working with code in th
|
|||||||
- **Services** (`src/main/services/`): MCPService, KnowledgeService, WindowService, etc.
|
- **Services** (`src/main/services/`): MCPService, KnowledgeService, WindowService, etc.
|
||||||
- **Build System**: Electron-Vite with experimental rolldown-vite, yarn workspaces.
|
- **Build System**: Electron-Vite with experimental rolldown-vite, yarn workspaces.
|
||||||
- **State Management**: Redux Toolkit (`src/renderer/src/store/`) for predictable state.
|
- **State Management**: Redux Toolkit (`src/renderer/src/store/`) for predictable state.
|
||||||
- **UI Components**: HeroUI (`@heroui/*`) for all new UI elements.
|
|
||||||
|
|
||||||
### Logging
|
### Logging
|
||||||
```typescript
|
```typescript
|
||||||
|
|||||||
+2
-2
@@ -18,13 +18,13 @@ yarn
|
|||||||
|
|
||||||
### Setup Node.js
|
### Setup Node.js
|
||||||
|
|
||||||
Download and install [Node.js v20.x.x](https://nodejs.org/en/download)
|
Download and install [Node.js v22.x.x](https://nodejs.org/en/download)
|
||||||
|
|
||||||
### Setup Yarn
|
### Setup Yarn
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
corepack enable
|
corepack enable
|
||||||
corepack prepare yarn@4.6.0 --activate
|
corepack prepare yarn@4.9.1 --activate
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install Dependencies
|
### Install Dependencies
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ The Test Plan is divided into the RC channel and the Beta channel, with the foll
|
|||||||
|
|
||||||
Users can enable the "Test Plan" and select the version channel in the software's `Settings` > `About`. Please note that the versions in the "Test Plan" cannot guarantee data consistency, so be sure to back up your data before using them.
|
Users can enable the "Test Plan" and select the version channel in the software's `Settings` > `About`. Please note that the versions in the "Test Plan" cannot guarantee data consistency, so be sure to back up your data before using them.
|
||||||
|
|
||||||
|
After enabling the RC channel or Beta channel, if a stable version is released, users will still be upgraded to the stable version.
|
||||||
|
|
||||||
Users are welcome to submit issues or provide feedback through other channels for any bugs encountered during testing. Your feedback is very important to us.
|
Users are welcome to submit issues or provide feedback through other channels for any bugs encountered during testing. Your feedback is very important to us.
|
||||||
|
|
||||||
## Developer Guide
|
## Developer Guide
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
用户可以在软件的`设置`-`关于`中,开启“测试计划”并选择版本通道。请注意“测试计划”的版本无法保证数据的一致性,请使用前一定要备份数据。
|
用户可以在软件的`设置`-`关于`中,开启“测试计划”并选择版本通道。请注意“测试计划”的版本无法保证数据的一致性,请使用前一定要备份数据。
|
||||||
|
|
||||||
|
用户选择RC版通道或Beta版通道后,若发布了正式版,仍旧会升级到正式版。
|
||||||
|
|
||||||
用户在测试过程中发现的BUG,欢迎提交issue或通过其他渠道反馈。用户的反馈对我们非常重要。
|
用户在测试过程中发现的BUG,欢迎提交issue或通过其他渠道反馈。用户的反馈对我们非常重要。
|
||||||
|
|
||||||
## 开发者指南
|
## 开发者指南
|
||||||
|
|||||||
+43
-98
@@ -21,6 +21,8 @@ files:
|
|||||||
- "**/*"
|
- "**/*"
|
||||||
- "!**/{.vscode,.yarn,.yarn-lock,.github,.cursorrules,.prettierrc}"
|
- "!**/{.vscode,.yarn,.yarn-lock,.github,.cursorrules,.prettierrc}"
|
||||||
- "!electron.vite.config.{js,ts,mjs,cjs}}"
|
- "!electron.vite.config.{js,ts,mjs,cjs}}"
|
||||||
|
- "!.*"
|
||||||
|
- "!components.json"
|
||||||
- "!**/{.eslintignore,.eslintrc.js,.eslintrc.json,.eslintcache,root.eslint.config.js,eslint.config.js,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,eslint.config.mjs,dev-app-update.yml,CHANGELOG.md,README.md,biome.jsonc}"
|
- "!**/{.eslintignore,.eslintrc.js,.eslintrc.json,.eslintcache,root.eslint.config.js,eslint.config.js,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,eslint.config.mjs,dev-app-update.yml,CHANGELOG.md,README.md,biome.jsonc}"
|
||||||
- "!**/{.env,.env.*,.npmrc,pnpm-lock.yaml}"
|
- "!**/{.env,.env.*,.npmrc,pnpm-lock.yaml}"
|
||||||
- "!**/{tsconfig.json,tsconfig.tsbuildinfo,tsconfig.node.json,tsconfig.web.json}"
|
- "!**/{tsconfig.json,tsconfig.tsbuildinfo,tsconfig.node.json,tsconfig.web.json}"
|
||||||
@@ -133,116 +135,59 @@ artifactBuildCompleted: scripts/artifact-build-completed.js
|
|||||||
releaseInfo:
|
releaseInfo:
|
||||||
releaseNotes: |
|
releaseNotes: |
|
||||||
<!--LANG:en-->
|
<!--LANG:en-->
|
||||||
What's New in v1.7.0-beta.3
|
What's New in v1.7.0-beta.4
|
||||||
|
|
||||||
|
Major Changes:
|
||||||
|
- UI Framework Upgrade: Improved performance and user experience with new design system
|
||||||
|
- App Menu i18n: Menu now supports multiple languages and syncs with app language settings
|
||||||
|
|
||||||
New Features:
|
New Features:
|
||||||
- Enhanced Tool Permission System: Real-time tool approval interface with improved UX
|
- AWS Bedrock API Key: Support Bedrock API key authentication with Extended Thinking (reasoning) capability
|
||||||
- Plugin Management System: Support for Claude Agent plugins (agents, commands, skills)
|
- SophNet Provider: Added support for SophNet LLM provider
|
||||||
- Skill Tool: Add skill execution capabilities for agents
|
- Auto Session Rename: Agent sessions automatically rename based on conversation topics
|
||||||
- Mobile App Data Restore: Support restoring data to mobile applications
|
- TopP Parameter: Added TopP parameter support for more precise model control
|
||||||
- OpenMinerU Preprocessor: Knowledge base now supports open-source MinerU for document processing
|
- Reasoning Effort Control: Quick access to reasoning effort settings in input bar
|
||||||
- HuggingFace Provider: Added HuggingFace as AI provider
|
|
||||||
- Claude Haiku 4.5: Support for the latest Claude Haiku 4.5 model
|
|
||||||
- Ling Series Models: Added support for Ling-1T and related models
|
|
||||||
- Intel OVMS Painting: New painting provider using Intel OpenVINO Model Server
|
|
||||||
- Automatic Update Checks: Implement periodic update checking with configurable intervals
|
|
||||||
- HuggingChat Mini App: New mini app for HuggingChat integration
|
|
||||||
|
|
||||||
Improvements:
|
Improvements:
|
||||||
- Agent Creation: New agents are now automatically activated upon creation
|
- Topics & Sessions: Enhanced UI with better styling and smoother interactions
|
||||||
- Lazy Loading: Optimize page load performance with route lazy loading
|
- Quick Panel: Improved option visibility and control
|
||||||
- UI Enhancements: Improved agent item styling and layout consistency
|
- Painting Models: Smarter model initialization with better defaults
|
||||||
- Navigation: Better navbar layout for fullscreen mode on macOS
|
- System Shutdown: Better handling of shutdown events to prevent data loss
|
||||||
- Settings Tab: Enhanced context slider consistency
|
- Smaller Package Size: Optimized build configuration for faster downloads
|
||||||
- Backup Manager: Unified footer layout for local and S3 backup managers
|
|
||||||
- Menu System: Enhanced application menu with improved help section
|
|
||||||
- Proxy Rules: Comprehensive proxy bypass rule matching
|
|
||||||
- German Language: Added German language support
|
|
||||||
- MCP Confirmation: Added confirmation modal when activating protocol-installed MCP servers
|
|
||||||
- Translation: Enhanced translation script with concurrency and validation
|
|
||||||
- Electron & Vite: Updated to Electron 38 and Vite 4.0.1
|
|
||||||
|
|
||||||
Claude Code Tool Improvements:
|
|
||||||
- GlobTool: Now counts lines instead of files in output for better clarity
|
|
||||||
- ReadTool: Automatically removes system reminder tags from output
|
|
||||||
- TodoWriteTool: Improved rendering behavior
|
|
||||||
- Environment Variables: Updated model-related environment variable names
|
|
||||||
|
|
||||||
Bug Fixes:
|
Bug Fixes:
|
||||||
- Fixed session model not being used when sending messages
|
- Fixed Perplexity provider support and API host formatting
|
||||||
- Fixed tool approval UI and shared workspace plugin inconsistencies
|
- Fixed CherryAI provider support and API host formatting
|
||||||
- Fixed API server readiness notification to renderer
|
- Fixed i18n translations for painting image size options
|
||||||
- Fixed grouped items not respecting saved tag order
|
- Fixed agent session message token usage tracking
|
||||||
- Fixed assistant/agent activation when creating new ones
|
- Fixed prompt stream handling on completion or error
|
||||||
- Fixed Dashscope Anthropic API host and migrated old configs
|
- Fixed message API initialization issues
|
||||||
- Fixed Qwen3 thinking mode control for Ollama
|
|
||||||
- Fixed disappeared MCP button
|
|
||||||
- Fixed create assistant causing blank screen
|
|
||||||
- Fixed up-down button visibility in some cases
|
|
||||||
- Fixed hooks preventing save on composing enter key
|
|
||||||
- Fixed Azure GPT-image-1 and OpenRouter Gemini-image
|
|
||||||
- Fixed Silicon reasoning issues
|
|
||||||
- Fixed topic branch incomplete copy with two-pass ID mapping
|
|
||||||
- Fixed deep research model search context restrictions
|
|
||||||
- Fixed model capability checking logic
|
|
||||||
- Fixed reranker API error response capture
|
|
||||||
- Fixed right-click paste file content into inputbar
|
|
||||||
- Fixed minimax-m2 support in aiCore
|
|
||||||
|
|
||||||
<!--LANG:zh-CN-->
|
<!--LANG:zh-CN-->
|
||||||
v1.7.0-beta.3 新特性
|
v1.7.0-beta.4 新特性
|
||||||
|
|
||||||
|
重大变更:
|
||||||
|
- UI 框架升级:采用新设计系统,提升性能和用户体验
|
||||||
|
- 应用菜单国际化:菜单支持多语言,并自动同步应用语言设置
|
||||||
|
|
||||||
新功能:
|
新功能:
|
||||||
- 增强工具权限系统:实时工具审批界面,改进用户体验
|
- AWS Bedrock API 密钥:支持 Bedrock API 密钥身份验证,并支持扩展思考(推理)能力
|
||||||
- 插件管理系统:支持 Claude Agent 插件(agents、commands、skills)
|
- SophNet 提供商:添加 SophNet LLM 提供商支持
|
||||||
- 技能工具:为 Agent 添加技能执行能力
|
- 自动会话重命名:Agent 会话根据对话主题自动重命名
|
||||||
- 移动应用数据恢复:支持将数据恢复到移动应用程序
|
- TopP 参数:添加 TopP 参数支持,更精确控制模型输出
|
||||||
- OpenMinerU 预处理器:知识库现支持使用开源 MinerU 处理文档
|
|
||||||
- HuggingFace 提供商:添加 HuggingFace 作为 AI 提供商
|
|
||||||
- Claude Haiku 4.5:支持最新的 Claude Haiku 4.5 模型
|
|
||||||
- Ling 系列模型:添加 Ling-1T 及相关模型支持
|
|
||||||
- Intel OVMS 绘图:使用 Intel OpenVINO 模型服务器的新绘图提供商
|
|
||||||
- 自动更新检查:实现可配置间隔的定期更新检查
|
|
||||||
- HuggingChat 小程序:新增 HuggingChat 集成小程序
|
|
||||||
|
|
||||||
改进:
|
改进:
|
||||||
- Agent 创建:新创建的 Agent 现在会自动激活
|
- 主题和会话:增强 UI,改进样式和交互体验
|
||||||
- 懒加载:通过路由懒加载优化页面加载性能
|
- 快速面板:改进选项可见性和控制
|
||||||
- UI 增强:改进 Agent 项目样式和布局一致性
|
- 绘图模型:更智能的模型初始化和更好的默认值
|
||||||
- 导航:改进 macOS 全屏模式下的导航栏布局
|
- 系统关机:更好地处理关机事件,防止数据丢失
|
||||||
- 设置选项卡:增强上下文滑块一致性
|
- 更小的安装包:优化构建配置,下载更快
|
||||||
- 备份管理器:统一本地和 S3 备份管理器的页脚布局
|
|
||||||
- 菜单系统:增强应用菜单,改进帮助部分
|
|
||||||
- 代理规则:全面的代理绕过规则匹配
|
|
||||||
- 德语支持:添加德语语言支持
|
|
||||||
- MCP 确认:添加激活协议安装的 MCP 服务器时的确认模态框
|
|
||||||
- 翻译:增强翻译脚本的并发和验证功能
|
|
||||||
- Electron & Vite:更新至 Electron 38 和 Vite 4.0.1
|
|
||||||
|
|
||||||
Claude Code 工具改进:
|
|
||||||
- GlobTool:现在计算行数而不是文件数,提供更清晰的输出
|
|
||||||
- ReadTool:自动从输出中移除系统提醒标签
|
|
||||||
- TodoWriteTool:改进渲染行为
|
|
||||||
- 环境变量:更新模型相关的环境变量名称
|
|
||||||
|
|
||||||
问题修复:
|
问题修复:
|
||||||
- 修复发送消息时未使用会话模型
|
- 修复 Perplexity 提供商支持和 API 主机格式化
|
||||||
- 修复工具审批 UI 和共享工作区插件不一致
|
- 修复 CherryAI 提供商支持和 API 主机格式化
|
||||||
- 修复 API 服务器就绪通知到渲染器
|
- 修复绘图图像大小选项的 i18n 翻译
|
||||||
- 修复分组项目不遵守已保存标签顺序
|
- 修复 Agent 会话消息的 token 使用量跟踪
|
||||||
- 修复创建新的助手/Agent 时的激活问题
|
- 修复完成或错误时的提示流处理
|
||||||
- 修复 Dashscope Anthropic API 主机并迁移旧配置
|
- 修复消息 API 初始化问题
|
||||||
- 修复 Ollama 的 Qwen3 思考模式控制
|
|
||||||
- 修复 MCP 按钮消失
|
|
||||||
- 修复创建助手导致空白屏幕
|
|
||||||
- 修复某些情况下上下按钮可见性
|
|
||||||
- 修复钩子在输入法输入时阻止保存
|
|
||||||
- 修复 Azure GPT-image-1 和 OpenRouter Gemini-image
|
|
||||||
- 修复 Silicon 推理问题
|
|
||||||
- 修复主题分支不完整复制,采用两阶段 ID 映射
|
|
||||||
- 修复深度研究模型搜索上下文限制
|
|
||||||
- 修复模型能力检查逻辑
|
|
||||||
- 修复 reranker API 错误响应捕获
|
|
||||||
- 修复右键粘贴文件内容到输入栏
|
|
||||||
- 修复 aiCore 中的 minimax-m2 支持
|
|
||||||
<!--LANG:END-->
|
<!--LANG:END-->
|
||||||
|
|||||||
+6
-4
@@ -82,6 +82,7 @@
|
|||||||
"@libsql/client": "0.14.0",
|
"@libsql/client": "0.14.0",
|
||||||
"@libsql/win32-x64-msvc": "^0.4.7",
|
"@libsql/win32-x64-msvc": "^0.4.7",
|
||||||
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
|
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
|
||||||
|
"@paymoapp/electron-shutdown-handler": "^1.1.2",
|
||||||
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"font-list": "^2.0.0",
|
"font-list": "^2.0.0",
|
||||||
@@ -113,9 +114,9 @@
|
|||||||
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||||
"@anthropic-ai/sdk": "^0.41.0",
|
"@anthropic-ai/sdk": "^0.41.0",
|
||||||
"@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch",
|
"@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch",
|
||||||
"@aws-sdk/client-bedrock": "^3.840.0",
|
"@aws-sdk/client-bedrock": "^3.910.0",
|
||||||
"@aws-sdk/client-bedrock-runtime": "^3.840.0",
|
"@aws-sdk/client-bedrock-runtime": "^3.910.0",
|
||||||
"@aws-sdk/client-s3": "^3.840.0",
|
"@aws-sdk/client-s3": "^3.910.0",
|
||||||
"@biomejs/biome": "2.2.4",
|
"@biomejs/biome": "2.2.4",
|
||||||
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18",
|
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18",
|
||||||
"@cherrystudio/embedjs": "^0.1.31",
|
"@cherrystudio/embedjs": "^0.1.31",
|
||||||
@@ -146,7 +147,6 @@
|
|||||||
"@eslint/js": "^9.22.0",
|
"@eslint/js": "^9.22.0",
|
||||||
"@google/genai": "patch:@google/genai@npm%3A1.0.1#~/.yarn/patches/@google-genai-npm-1.0.1-e26f0f9af7.patch",
|
"@google/genai": "patch:@google/genai@npm%3A1.0.1#~/.yarn/patches/@google-genai-npm-1.0.1-e26f0f9af7.patch",
|
||||||
"@hello-pangea/dnd": "^18.0.1",
|
"@hello-pangea/dnd": "^18.0.1",
|
||||||
"@heroui/react": "^2.8.3",
|
|
||||||
"@kangfenmao/keyv-storage": "^0.1.0",
|
"@kangfenmao/keyv-storage": "^0.1.0",
|
||||||
"@langchain/community": "^1.0.0",
|
"@langchain/community": "^1.0.0",
|
||||||
"@langchain/core": "patch:@langchain/core@npm%3A1.0.2#~/.yarn/patches/@langchain-core-npm-1.0.2-183ef83fe4.patch",
|
"@langchain/core": "patch:@langchain/core@npm%3A1.0.2#~/.yarn/patches/@langchain-core-npm-1.0.2-183ef83fe4.patch",
|
||||||
@@ -348,6 +348,7 @@
|
|||||||
"striptags": "^3.2.0",
|
"striptags": "^3.2.0",
|
||||||
"styled-components": "^6.1.11",
|
"styled-components": "^6.1.11",
|
||||||
"swr": "^2.3.6",
|
"swr": "^2.3.6",
|
||||||
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss": "^4.1.13",
|
"tailwindcss": "^4.1.13",
|
||||||
"tar": "^7.4.3",
|
"tar": "^7.4.3",
|
||||||
"tiny-pinyin": "^1.3.2",
|
"tiny-pinyin": "^1.3.2",
|
||||||
@@ -373,6 +374,7 @@
|
|||||||
"zod": "^4.1.5"
|
"zod": "^4.1.5"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
"@smithy/types": "4.7.1",
|
||||||
"@codemirror/language": "6.11.3",
|
"@codemirror/language": "6.11.3",
|
||||||
"@codemirror/lint": "6.8.5",
|
"@codemirror/lint": "6.8.5",
|
||||||
"@codemirror/view": "6.38.1",
|
"@codemirror/view": "6.38.1",
|
||||||
|
|||||||
@@ -470,3 +470,6 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// resources/scripts should be maintained manually
|
||||||
|
export const HOME_CHERRY_DIR = '.cherrystudio'
|
||||||
|
|||||||
@@ -31,3 +31,16 @@ export type WebviewKeyEvent = {
|
|||||||
shift: boolean
|
shift: boolean
|
||||||
alt: boolean
|
alt: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WebSocketStatusResponse {
|
||||||
|
isRunning: boolean
|
||||||
|
port?: number
|
||||||
|
ip?: string
|
||||||
|
clientConnected: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebSocketCandidatesResponse {
|
||||||
|
host: string
|
||||||
|
interface: string
|
||||||
|
priority: number
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ import { sortedObjectByKeys } from './sort'
|
|||||||
// ========== SCRIPT CONFIGURATION AREA - MODIFY SETTINGS HERE ==========
|
// ========== SCRIPT CONFIGURATION AREA - MODIFY SETTINGS HERE ==========
|
||||||
const SCRIPT_CONFIG = {
|
const SCRIPT_CONFIG = {
|
||||||
// 🔧 Concurrency Control Configuration
|
// 🔧 Concurrency Control Configuration
|
||||||
MAX_CONCURRENT_TRANSLATIONS: 5, // Max concurrent requests (Make sure the concurrency level does not exceed your provider's limits.)
|
MAX_CONCURRENT_TRANSLATIONS: process.env.TRANSLATION_MAX_CONCURRENT_REQUESTS
|
||||||
TRANSLATION_DELAY_MS: 100, // Delay between requests to avoid rate limiting (Recommended: 100-500ms, Range: 0-5000ms)
|
? parseInt(process.env.TRANSLATION_MAX_CONCURRENT_REQUESTS)
|
||||||
|
: 5, // Max concurrent requests (Make sure the concurrency level does not exceed your provider's limits.)
|
||||||
|
TRANSLATION_DELAY_MS: process.env.TRANSLATION_DELAY_MS ? parseInt(process.env.TRANSLATION_DELAY_MS) : 500, // Delay between requests to avoid rate limiting (Recommended: 100-500ms, Range: 0-5000ms)
|
||||||
|
|
||||||
// 🔑 API Configuration
|
// 🔑 API Configuration
|
||||||
API_KEY: process.env.TRANSLATION_API_KEY || '', // API key from environment variable
|
API_KEY: process.env.TRANSLATION_API_KEY || '', // API key from environment variable
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ const swaggerOptions: swaggerJSDoc.Options = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
apis: ['./src/main/apiServer/routes/*.ts', './src/main/apiServer/app.ts']
|
apis: ['./src/main/apiServer/routes/**/*.ts', './src/main/apiServer/app.ts']
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupOpenAPIDocumentation(app: Express) {
|
export function setupOpenAPIDocumentation(app: Express) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { appMenuService } from './services/AppMenuService'
|
|||||||
import { configManager } from './services/ConfigManager'
|
import { configManager } from './services/ConfigManager'
|
||||||
import mcpService from './services/MCPService'
|
import mcpService from './services/MCPService'
|
||||||
import { nodeTraceService } from './services/NodeTraceService'
|
import { nodeTraceService } from './services/NodeTraceService'
|
||||||
|
import powerMonitorService from './services/PowerMonitorService'
|
||||||
import {
|
import {
|
||||||
CHERRY_STUDIO_PROTOCOL,
|
CHERRY_STUDIO_PROTOCOL,
|
||||||
handleProtocolUrl,
|
handleProtocolUrl,
|
||||||
@@ -30,6 +31,7 @@ import {
|
|||||||
import selectionService, { initSelectionService } from './services/SelectionService'
|
import selectionService, { initSelectionService } from './services/SelectionService'
|
||||||
import { registerShortcuts } from './services/ShortcutService'
|
import { registerShortcuts } from './services/ShortcutService'
|
||||||
import { TrayService } from './services/TrayService'
|
import { TrayService } from './services/TrayService'
|
||||||
|
import { versionService } from './services/VersionService'
|
||||||
import { windowService } from './services/WindowService'
|
import { windowService } from './services/WindowService'
|
||||||
import { initWebviewHotkeys } from './services/WebviewService'
|
import { initWebviewHotkeys } from './services/WebviewService'
|
||||||
|
|
||||||
@@ -110,6 +112,10 @@ if (!app.requestSingleInstanceLock()) {
|
|||||||
// Some APIs can only be used after this event occurs.
|
// Some APIs can only be used after this event occurs.
|
||||||
|
|
||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
|
// Record current version for tracking
|
||||||
|
// A preparation for v2 data refactoring
|
||||||
|
versionService.recordCurrentVersion()
|
||||||
|
|
||||||
initWebviewHotkeys()
|
initWebviewHotkeys()
|
||||||
// Set app user model id for windows
|
// Set app user model id for windows
|
||||||
electronApp.setAppUserModelId(import.meta.env.VITE_MAIN_BUNDLE_ID || 'com.kangfenmao.CherryStudio')
|
electronApp.setAppUserModelId(import.meta.env.VITE_MAIN_BUNDLE_ID || 'com.kangfenmao.CherryStudio')
|
||||||
@@ -127,6 +133,7 @@ if (!app.requestSingleInstanceLock()) {
|
|||||||
appMenuService?.setupApplicationMenu()
|
appMenuService?.setupApplicationMenu()
|
||||||
|
|
||||||
nodeTraceService.init()
|
nodeTraceService.init()
|
||||||
|
powerMonitorService.init()
|
||||||
|
|
||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
const mainWindow = windowService.getMainWindow()
|
const mainWindow = windowService.getMainWindow()
|
||||||
|
|||||||
+12
-2
@@ -50,6 +50,7 @@ import * as NutstoreService from './services/NutstoreService'
|
|||||||
import ObsidianVaultService from './services/ObsidianVaultService'
|
import ObsidianVaultService from './services/ObsidianVaultService'
|
||||||
import { ocrService } from './services/ocr/OcrService'
|
import { ocrService } from './services/ocr/OcrService'
|
||||||
import OvmsManager from './services/OvmsManager'
|
import OvmsManager from './services/OvmsManager'
|
||||||
|
import powerMonitorService from './services/PowerMonitorService'
|
||||||
import { proxyManager } from './services/ProxyManager'
|
import { proxyManager } from './services/ProxyManager'
|
||||||
import { pythonService } from './services/PythonService'
|
import { pythonService } from './services/PythonService'
|
||||||
import { FileServiceManager } from './services/remotefile/FileServiceManager'
|
import { FileServiceManager } from './services/remotefile/FileServiceManager'
|
||||||
@@ -115,8 +116,17 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
const appUpdater = new AppUpdater()
|
const appUpdater = new AppUpdater()
|
||||||
const notificationService = new NotificationService()
|
const notificationService = new NotificationService()
|
||||||
|
|
||||||
// Initialize Python service with main window
|
// Register shutdown handlers
|
||||||
pythonService.setMainWindow(mainWindow)
|
powerMonitorService.registerShutdownHandler(() => {
|
||||||
|
appUpdater.setAutoUpdate(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
powerMonitorService.registerShutdownHandler(() => {
|
||||||
|
const mw = windowService.getMainWindow()
|
||||||
|
if (mw && !mw.isDestroyed()) {
|
||||||
|
mw.webContents.send(IpcChannel.App_SaveData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const checkMainWindow = () => {
|
const checkMainWindow = () => {
|
||||||
if (!mainWindow || mainWindow.isDestroyed()) {
|
if (!mainWindow || mainWindow.isDestroyed()) {
|
||||||
|
|||||||
@@ -7,16 +7,33 @@ import { app, Menu, shell } from 'electron'
|
|||||||
|
|
||||||
import { configManager } from './ConfigManager'
|
import { configManager } from './ConfigManager'
|
||||||
export class AppMenuService {
|
export class AppMenuService {
|
||||||
|
private languageChangeCallback?: (newLanguage: string) => void
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Subscribe to language change events
|
||||||
|
this.languageChangeCallback = () => {
|
||||||
|
this.setupApplicationMenu()
|
||||||
|
}
|
||||||
|
configManager.subscribe('language', this.languageChangeCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy(): void {
|
||||||
|
// Clean up subscription to prevent memory leaks
|
||||||
|
if (this.languageChangeCallback) {
|
||||||
|
configManager.unsubscribe('language', this.languageChangeCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public setupApplicationMenu(): void {
|
public setupApplicationMenu(): void {
|
||||||
const locale = locales[configManager.getLanguage()]
|
const locale = locales[configManager.getLanguage()]
|
||||||
const { common } = locale.translation
|
const { appMenu } = locale.translation
|
||||||
|
|
||||||
const template: MenuItemConstructorOptions[] = [
|
const template: MenuItemConstructorOptions[] = [
|
||||||
{
|
{
|
||||||
label: app.name,
|
label: app.name,
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: common.about + ' ' + app.name,
|
label: appMenu.about + ' ' + app.name,
|
||||||
click: () => {
|
click: () => {
|
||||||
// Emit event to navigate to About page
|
// Emit event to navigate to About page
|
||||||
const mainWindow = windowService.getMainWindow()
|
const mainWindow = windowService.getMainWindow()
|
||||||
@@ -27,50 +44,78 @@ export class AppMenuService {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'services' },
|
{ role: 'services', label: appMenu.services },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'hide' },
|
{ role: 'hide', label: `${appMenu.hide} ${app.name}` },
|
||||||
{ role: 'hideOthers' },
|
{ role: 'hideOthers', label: appMenu.hideOthers },
|
||||||
{ role: 'unhide' },
|
{ role: 'unhide', label: appMenu.unhide },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'quit' }
|
{ role: 'quit', label: `${appMenu.quit} ${app.name}` }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'fileMenu'
|
label: appMenu.file,
|
||||||
|
submenu: [{ role: 'close', label: appMenu.close }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'editMenu'
|
label: appMenu.edit,
|
||||||
|
submenu: [
|
||||||
|
{ role: 'undo', label: appMenu.undo },
|
||||||
|
{ role: 'redo', label: appMenu.redo },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'cut', label: appMenu.cut },
|
||||||
|
{ role: 'copy', label: appMenu.copy },
|
||||||
|
{ role: 'paste', label: appMenu.paste },
|
||||||
|
{ role: 'delete', label: appMenu.delete },
|
||||||
|
{ role: 'selectAll', label: appMenu.selectAll }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'viewMenu'
|
label: appMenu.view,
|
||||||
|
submenu: [
|
||||||
|
{ role: 'reload', label: appMenu.reload },
|
||||||
|
{ role: 'forceReload', label: appMenu.forceReload },
|
||||||
|
{ role: 'toggleDevTools', label: appMenu.toggleDevTools },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'resetZoom', label: appMenu.resetZoom },
|
||||||
|
{ role: 'zoomIn', label: appMenu.zoomIn },
|
||||||
|
{ role: 'zoomOut', label: appMenu.zoomOut },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'togglefullscreen', label: appMenu.toggleFullscreen }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'windowMenu'
|
label: appMenu.window,
|
||||||
|
submenu: [
|
||||||
|
{ role: 'minimize', label: appMenu.minimize },
|
||||||
|
{ role: 'zoom', label: appMenu.zoom },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'front', label: appMenu.front }
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'help',
|
label: appMenu.help,
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Website',
|
label: appMenu.website,
|
||||||
click: () => {
|
click: () => {
|
||||||
shell.openExternal('https://cherry-ai.com')
|
shell.openExternal('https://cherry-ai.com')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Documentation',
|
label: appMenu.documentation,
|
||||||
click: () => {
|
click: () => {
|
||||||
shell.openExternal('https://cherry-ai.com/docs')
|
shell.openExternal('https://cherry-ai.com/docs')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Feedback',
|
label: appMenu.feedback,
|
||||||
click: () => {
|
click: () => {
|
||||||
shell.openExternal('https://github.com/CherryHQ/cherry-studio/issues/new/choose')
|
shell.openExternal('https://github.com/CherryHQ/cherry-studio/issues/new/choose')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Releases',
|
label: appMenu.releases,
|
||||||
click: () => {
|
click: () => {
|
||||||
shell.openExternal('https://github.com/CherryHQ/cherry-studio/releases')
|
shell.openExternal('https://github.com/CherryHQ/cherry-studio/releases')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { getBinaryName } from '@main/utils/process'
|
|||||||
import type { TerminalConfig, TerminalConfigWithCommand } from '@shared/config/constant'
|
import type { TerminalConfig, TerminalConfigWithCommand } from '@shared/config/constant'
|
||||||
import {
|
import {
|
||||||
codeTools,
|
codeTools,
|
||||||
|
HOME_CHERRY_DIR,
|
||||||
MACOS_TERMINALS,
|
MACOS_TERMINALS,
|
||||||
MACOS_TERMINALS_WITH_COMMANDS,
|
MACOS_TERMINALS_WITH_COMMANDS,
|
||||||
terminalApps,
|
terminalApps,
|
||||||
@@ -66,7 +67,7 @@ class CodeToolsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getBunPath() {
|
public async getBunPath() {
|
||||||
const dir = path.join(os.homedir(), '.cherrystudio', 'bin')
|
const dir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||||
const bunName = await getBinaryName('bun')
|
const bunName = await getBinaryName('bun')
|
||||||
const bunPath = path.join(dir, bunName)
|
const bunPath = path.join(dir, bunName)
|
||||||
return bunPath
|
return bunPath
|
||||||
@@ -362,7 +363,7 @@ class CodeToolsService {
|
|||||||
|
|
||||||
private async isPackageInstalled(cliTool: string): Promise<boolean> {
|
private async isPackageInstalled(cliTool: string): Promise<boolean> {
|
||||||
const executableName = await this.getCliExecutableName(cliTool)
|
const executableName = await this.getCliExecutableName(cliTool)
|
||||||
const binDir = path.join(os.homedir(), '.cherrystudio', 'bin')
|
const binDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||||
const executablePath = path.join(binDir, executableName + (isWin ? '.exe' : ''))
|
const executablePath = path.join(binDir, executableName + (isWin ? '.exe' : ''))
|
||||||
|
|
||||||
// Ensure bin directory exists
|
// Ensure bin directory exists
|
||||||
@@ -389,7 +390,7 @@ class CodeToolsService {
|
|||||||
logger.info(`${cliTool} is installed, getting current version`)
|
logger.info(`${cliTool} is installed, getting current version`)
|
||||||
try {
|
try {
|
||||||
const executableName = await this.getCliExecutableName(cliTool)
|
const executableName = await this.getCliExecutableName(cliTool)
|
||||||
const binDir = path.join(os.homedir(), '.cherrystudio', 'bin')
|
const binDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||||
const executablePath = path.join(binDir, executableName + (isWin ? '.exe' : ''))
|
const executablePath = path.join(binDir, executableName + (isWin ? '.exe' : ''))
|
||||||
|
|
||||||
const { stdout } = await execAsync(`"${executablePath}" --version`, {
|
const { stdout } = await execAsync(`"${executablePath}" --version`, {
|
||||||
@@ -500,7 +501,7 @@ class CodeToolsService {
|
|||||||
try {
|
try {
|
||||||
const packageName = await this.getPackageName(cliTool)
|
const packageName = await this.getPackageName(cliTool)
|
||||||
const bunPath = await this.getBunPath()
|
const bunPath = await this.getBunPath()
|
||||||
const bunInstallPath = path.join(os.homedir(), '.cherrystudio')
|
const bunInstallPath = path.join(os.homedir(), HOME_CHERRY_DIR)
|
||||||
const registryUrl = await this.getNpmRegistryUrl()
|
const registryUrl = await this.getNpmRegistryUrl()
|
||||||
|
|
||||||
const installEnvPrefix = isWin
|
const installEnvPrefix = isWin
|
||||||
@@ -550,7 +551,7 @@ class CodeToolsService {
|
|||||||
const packageName = await this.getPackageName(cliTool)
|
const packageName = await this.getPackageName(cliTool)
|
||||||
const bunPath = await this.getBunPath()
|
const bunPath = await this.getBunPath()
|
||||||
const executableName = await this.getCliExecutableName(cliTool)
|
const executableName = await this.getCliExecutableName(cliTool)
|
||||||
const binDir = path.join(os.homedir(), '.cherrystudio', 'bin')
|
const binDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||||
const executablePath = path.join(binDir, executableName + (isWin ? '.exe' : ''))
|
const executablePath = path.join(binDir, executableName + (isWin ? '.exe' : ''))
|
||||||
|
|
||||||
logger.debug(`Package name: ${packageName}`)
|
logger.debug(`Package name: ${packageName}`)
|
||||||
@@ -652,7 +653,7 @@ class CodeToolsService {
|
|||||||
baseCommand = `${baseCommand} ${configParams}`
|
baseCommand = `${baseCommand} ${configParams}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const bunInstallPath = path.join(os.homedir(), '.cherrystudio')
|
const bunInstallPath = path.join(os.homedir(), HOME_CHERRY_DIR)
|
||||||
|
|
||||||
if (isInstalled) {
|
if (isInstalled) {
|
||||||
// If already installed, run executable directly (with optional update message)
|
// If already installed, run executable directly (with optional update message)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
ToolListChangedNotificationSchema
|
ToolListChangedNotificationSchema
|
||||||
} from '@modelcontextprotocol/sdk/types.js'
|
} from '@modelcontextprotocol/sdk/types.js'
|
||||||
import { nanoid } from '@reduxjs/toolkit'
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
|
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
||||||
import type { MCPProgressEvent } from '@shared/config/types'
|
import type { MCPProgressEvent } from '@shared/config/types'
|
||||||
import { IpcChannel } from '@shared/IpcChannel'
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
import { defaultAppHeaders } from '@shared/utils'
|
import { defaultAppHeaders } from '@shared/utils'
|
||||||
@@ -715,7 +716,7 @@ class McpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getInstallInfo() {
|
public async getInstallInfo() {
|
||||||
const dir = path.join(os.homedir(), '.cherrystudio', 'bin')
|
const dir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||||
const uvName = await getBinaryName('uv')
|
const uvName = await getBinaryName('uv')
|
||||||
const bunName = await getBinaryName('bun')
|
const bunName = await getBinaryName('bun')
|
||||||
const uvPath = path.join(dir, uvName)
|
const uvPath = path.join(dir, uvName)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { homedir } from 'node:os'
|
|||||||
import { promisify } from 'node:util'
|
import { promisify } from 'node:util'
|
||||||
|
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
||||||
import * as fs from 'fs-extra'
|
import * as fs from 'fs-extra'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
|
|
||||||
@@ -145,7 +146,7 @@ class OvmsManager {
|
|||||||
*/
|
*/
|
||||||
public async runOvms(): Promise<{ success: boolean; message?: string }> {
|
public async runOvms(): Promise<{ success: boolean; message?: string }> {
|
||||||
const homeDir = homedir()
|
const homeDir = homedir()
|
||||||
const ovmsDir = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms')
|
const ovmsDir = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms')
|
||||||
const configPath = path.join(ovmsDir, 'models', 'config.json')
|
const configPath = path.join(ovmsDir, 'models', 'config.json')
|
||||||
const runBatPath = path.join(ovmsDir, 'run.bat')
|
const runBatPath = path.join(ovmsDir, 'run.bat')
|
||||||
|
|
||||||
@@ -195,7 +196,7 @@ class OvmsManager {
|
|||||||
*/
|
*/
|
||||||
public async getOvmsStatus(): Promise<'not-installed' | 'not-running' | 'running'> {
|
public async getOvmsStatus(): Promise<'not-installed' | 'not-running' | 'running'> {
|
||||||
const homeDir = homedir()
|
const homeDir = homedir()
|
||||||
const ovmsPath = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms', 'ovms.exe')
|
const ovmsPath = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms', 'ovms.exe')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if OVMS executable exists
|
// Check if OVMS executable exists
|
||||||
@@ -273,7 +274,7 @@ class OvmsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const homeDir = homedir()
|
const homeDir = homedir()
|
||||||
const configPath = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms', 'models', 'config.json')
|
const configPath = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms', 'models', 'config.json')
|
||||||
try {
|
try {
|
||||||
if (!(await fs.pathExists(configPath))) {
|
if (!(await fs.pathExists(configPath))) {
|
||||||
logger.warn(`Config file does not exist: ${configPath}`)
|
logger.warn(`Config file does not exist: ${configPath}`)
|
||||||
@@ -304,7 +305,7 @@ class OvmsManager {
|
|||||||
|
|
||||||
private async applyModelPath(modelDirPath: string): Promise<boolean> {
|
private async applyModelPath(modelDirPath: string): Promise<boolean> {
|
||||||
const homeDir = homedir()
|
const homeDir = homedir()
|
||||||
const patchDir = path.join(homeDir, '.cherrystudio', 'ovms', 'patch')
|
const patchDir = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'patch')
|
||||||
if (!(await fs.pathExists(patchDir))) {
|
if (!(await fs.pathExists(patchDir))) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -355,7 +356,7 @@ class OvmsManager {
|
|||||||
logger.info(`Adding model: ${modelName} with ID: ${modelId}, Source: ${modelSource}, Task: ${task}`)
|
logger.info(`Adding model: ${modelName} with ID: ${modelId}, Source: ${modelSource}, Task: ${task}`)
|
||||||
|
|
||||||
const homeDir = homedir()
|
const homeDir = homedir()
|
||||||
const ovdndDir = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms')
|
const ovdndDir = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms')
|
||||||
const pathModel = path.join(ovdndDir, 'models', modelId)
|
const pathModel = path.join(ovdndDir, 'models', modelId)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -468,7 +469,7 @@ class OvmsManager {
|
|||||||
*/
|
*/
|
||||||
public async checkModelExists(modelId: string): Promise<boolean> {
|
public async checkModelExists(modelId: string): Promise<boolean> {
|
||||||
const homeDir = homedir()
|
const homeDir = homedir()
|
||||||
const ovmsDir = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms')
|
const ovmsDir = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms')
|
||||||
const configPath = path.join(ovmsDir, 'models', 'config.json')
|
const configPath = path.join(ovmsDir, 'models', 'config.json')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -495,7 +496,7 @@ class OvmsManager {
|
|||||||
*/
|
*/
|
||||||
public async updateModelConfig(modelName: string, modelId: string): Promise<boolean> {
|
public async updateModelConfig(modelName: string, modelId: string): Promise<boolean> {
|
||||||
const homeDir = homedir()
|
const homeDir = homedir()
|
||||||
const ovmsDir = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms')
|
const ovmsDir = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms')
|
||||||
const configPath = path.join(ovmsDir, 'models', 'config.json')
|
const configPath = path.join(ovmsDir, 'models', 'config.json')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -548,7 +549,7 @@ class OvmsManager {
|
|||||||
*/
|
*/
|
||||||
public async getModels(): Promise<ModelConfig[]> {
|
public async getModels(): Promise<ModelConfig[]> {
|
||||||
const homeDir = homedir()
|
const homeDir = homedir()
|
||||||
const ovmsDir = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms')
|
const ovmsDir = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms')
|
||||||
const configPath = path.join(ovmsDir, 'models', 'config.json')
|
const configPath = path.join(ovmsDir, 'models', 'config.json')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { isLinux, isMac, isWin } from '@main/constant'
|
||||||
|
import ElectronShutdownHandler from '@paymoapp/electron-shutdown-handler'
|
||||||
|
import { BrowserWindow } from 'electron'
|
||||||
|
import { powerMonitor } from 'electron'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('PowerMonitorService')
|
||||||
|
|
||||||
|
type ShutdownHandler = () => void | Promise<void>
|
||||||
|
|
||||||
|
export class PowerMonitorService {
|
||||||
|
private static instance: PowerMonitorService
|
||||||
|
private initialized = false
|
||||||
|
private shutdownHandlers: ShutdownHandler[] = []
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
// Private constructor to prevent direct instantiation
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): PowerMonitorService {
|
||||||
|
if (!PowerMonitorService.instance) {
|
||||||
|
PowerMonitorService.instance = new PowerMonitorService()
|
||||||
|
}
|
||||||
|
return PowerMonitorService.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a shutdown handler to be called when system shutdown is detected
|
||||||
|
* @param handler - The handler function to be called on shutdown
|
||||||
|
*/
|
||||||
|
public registerShutdownHandler(handler: ShutdownHandler): void {
|
||||||
|
this.shutdownHandlers.push(handler)
|
||||||
|
logger.info('Shutdown handler registered', { totalHandlers: this.shutdownHandlers.length })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize power monitor to listen for shutdown events
|
||||||
|
*/
|
||||||
|
public init(): void {
|
||||||
|
if (this.initialized) {
|
||||||
|
logger.warn('PowerMonitorService already initialized')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWin) {
|
||||||
|
this.initWindowsShutdownHandler()
|
||||||
|
} else if (isMac || isLinux) {
|
||||||
|
this.initElectronPowerMonitor()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initialized = true
|
||||||
|
logger.info('PowerMonitorService initialized', { platform: process.platform })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute all registered shutdown handlers
|
||||||
|
*/
|
||||||
|
private async executeShutdownHandlers(): Promise<void> {
|
||||||
|
logger.info('Executing shutdown handlers', { count: this.shutdownHandlers.length })
|
||||||
|
for (const handler of this.shutdownHandlers) {
|
||||||
|
try {
|
||||||
|
await handler()
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error executing shutdown handler', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize shutdown handler for Windows using @paymoapp/electron-shutdown-handler
|
||||||
|
*/
|
||||||
|
private initWindowsShutdownHandler(): void {
|
||||||
|
try {
|
||||||
|
const zeroMemoryWindow = new BrowserWindow({ show: false })
|
||||||
|
// Set the window handle for the shutdown handler
|
||||||
|
ElectronShutdownHandler.setWindowHandle(zeroMemoryWindow.getNativeWindowHandle())
|
||||||
|
|
||||||
|
// Listen for shutdown event
|
||||||
|
ElectronShutdownHandler.on('shutdown', async () => {
|
||||||
|
logger.info('System shutdown event detected (Windows)')
|
||||||
|
// Execute all registered shutdown handlers
|
||||||
|
await this.executeShutdownHandlers()
|
||||||
|
// Release the shutdown block to allow the system to shut down
|
||||||
|
ElectronShutdownHandler.releaseShutdown()
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info('Windows shutdown handler registered')
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to initialize Windows shutdown handler', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize power monitor for macOS and Linux using Electron's powerMonitor
|
||||||
|
*/
|
||||||
|
private initElectronPowerMonitor(): void {
|
||||||
|
try {
|
||||||
|
powerMonitor.on('shutdown', async () => {
|
||||||
|
logger.info('System shutdown event detected', { platform: process.platform })
|
||||||
|
// Execute all registered shutdown handlers
|
||||||
|
await this.executeShutdownHandlers()
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info('Electron powerMonitor shutdown listener registered')
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to initialize Electron powerMonitor', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default export as singleton instance
|
||||||
|
export default PowerMonitorService.getInstance()
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { randomUUID } from 'node:crypto'
|
import { randomUUID } from 'node:crypto'
|
||||||
|
|
||||||
import type { BrowserWindow } from 'electron'
|
|
||||||
import { ipcMain } from 'electron'
|
import { ipcMain } from 'electron'
|
||||||
|
|
||||||
|
import { windowService } from './WindowService'
|
||||||
|
|
||||||
interface PythonExecutionRequest {
|
interface PythonExecutionRequest {
|
||||||
id: string
|
id: string
|
||||||
script: string
|
script: string
|
||||||
@@ -21,7 +22,6 @@ interface PythonExecutionResponse {
|
|||||||
*/
|
*/
|
||||||
export class PythonService {
|
export class PythonService {
|
||||||
private static instance: PythonService | null = null
|
private static instance: PythonService | null = null
|
||||||
private mainWindow: BrowserWindow | null = null
|
|
||||||
private pendingRequests = new Map<string, { resolve: (value: string) => void; reject: (error: Error) => void }>()
|
private pendingRequests = new Map<string, { resolve: (value: string) => void; reject: (error: Error) => void }>()
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
@@ -51,10 +51,6 @@ export class PythonService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public setMainWindow(mainWindow: BrowserWindow) {
|
|
||||||
this.mainWindow = mainWindow
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute Python code by sending request to renderer PyodideService
|
* Execute Python code by sending request to renderer PyodideService
|
||||||
*/
|
*/
|
||||||
@@ -63,8 +59,8 @@ export class PythonService {
|
|||||||
context: Record<string, any> = {},
|
context: Record<string, any> = {},
|
||||||
timeout: number = 60000
|
timeout: number = 60000
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (!this.mainWindow) {
|
if (!windowService.getMainWindow()) {
|
||||||
throw new Error('Main window not set in PythonService')
|
throw new Error('Main window not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -95,7 +91,7 @@ export class PythonService {
|
|||||||
|
|
||||||
// Send request to renderer
|
// Send request to renderer
|
||||||
const request: PythonExecutionRequest = { id: requestId, script, context, timeout }
|
const request: PythonExecutionRequest = { id: requestId, script, context, timeout }
|
||||||
this.mainWindow?.webContents.send('python-execution-request', request)
|
windowService.getMainWindow()?.webContents.send('python-execution-request', request)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { Attributes, SpanEntity, TokenUsage, TraceCache } from '@mcp-trace/
|
|||||||
import { convertSpanToSpanEntity } from '@mcp-trace/trace-core'
|
import { convertSpanToSpanEntity } from '@mcp-trace/trace-core'
|
||||||
import { SpanStatusCode } from '@opentelemetry/api'
|
import { SpanStatusCode } from '@opentelemetry/api'
|
||||||
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
||||||
|
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
@@ -18,7 +19,7 @@ class SpanCacheService implements TraceCache {
|
|||||||
pri
|
pri
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.fileDir = path.join(os.homedir(), '.cherrystudio', 'trace')
|
this.fileDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'trace')
|
||||||
}
|
}
|
||||||
|
|
||||||
createSpan: (span: ReadableSpan) => void = (span: ReadableSpan) => {
|
createSpan: (span: ReadableSpan) => void = (span: ReadableSpan) => {
|
||||||
|
|||||||
@@ -0,0 +1,285 @@
|
|||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { app } from 'electron'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('VersionService')
|
||||||
|
|
||||||
|
type OS = 'win' | 'mac' | 'linux' | 'unknown'
|
||||||
|
type Environment = 'prod' | 'dev'
|
||||||
|
type Packaged = 'packaged' | 'unpackaged'
|
||||||
|
type Mode = 'install' | 'portable'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version record stored in version.log
|
||||||
|
*/
|
||||||
|
interface VersionRecord {
|
||||||
|
version: string
|
||||||
|
os: OS
|
||||||
|
environment: Environment
|
||||||
|
packaged: Packaged
|
||||||
|
mode: Mode
|
||||||
|
timestamp: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for tracking application version history
|
||||||
|
* Stores version information in userData/version.log for data migration and diagnostics
|
||||||
|
*/
|
||||||
|
class VersionService {
|
||||||
|
private readonly VERSION_LOG_FILE = 'version.log'
|
||||||
|
private versionLogPath: string | null = null
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Lazy initialization of path since app.getPath may not be available during construction
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the full path to version.log file
|
||||||
|
* @returns {string} Full path to version log file
|
||||||
|
*/
|
||||||
|
private getVersionLogPath(): string {
|
||||||
|
if (!this.versionLogPath) {
|
||||||
|
this.versionLogPath = path.join(app.getPath('userData'), this.VERSION_LOG_FILE)
|
||||||
|
}
|
||||||
|
return this.versionLogPath
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets current operating system identifier
|
||||||
|
* @returns {OS} OS identifier
|
||||||
|
*/
|
||||||
|
private getCurrentOS(): OS {
|
||||||
|
switch (process.platform) {
|
||||||
|
case 'win32':
|
||||||
|
return 'win'
|
||||||
|
case 'darwin':
|
||||||
|
return 'mac'
|
||||||
|
case 'linux':
|
||||||
|
return 'linux'
|
||||||
|
default:
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets current environment (production or development)
|
||||||
|
* @returns {Environment} Environment identifier
|
||||||
|
*/
|
||||||
|
private getCurrentEnvironment(): Environment {
|
||||||
|
return import.meta.env.MODE === 'production' ? 'prod' : 'dev'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets packaging status
|
||||||
|
* @returns {Packaged} Packaging status
|
||||||
|
*/
|
||||||
|
private getPackagedStatus(): Packaged {
|
||||||
|
return app.isPackaged ? 'packaged' : 'unpackaged'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets installation mode (install or portable)
|
||||||
|
* @returns {Mode} Installation mode
|
||||||
|
*/
|
||||||
|
private getInstallMode(): Mode {
|
||||||
|
return process.env.PORTABLE_EXECUTABLE_DIR !== undefined ? 'portable' : 'install'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates version log line for current application state
|
||||||
|
* @returns {string} Pipe-separated version record line
|
||||||
|
*/
|
||||||
|
private generateCurrentVersionLine(): string {
|
||||||
|
const version = app.getVersion()
|
||||||
|
const os = this.getCurrentOS()
|
||||||
|
const environment = this.getCurrentEnvironment()
|
||||||
|
const packaged = this.getPackagedStatus()
|
||||||
|
const mode = this.getInstallMode()
|
||||||
|
const timestamp = new Date().toISOString()
|
||||||
|
|
||||||
|
return `${version}|${os}|${environment}|${packaged}|${mode}|${timestamp}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a version log line into a VersionRecord object
|
||||||
|
* @param {string} line - Pipe-separated version record line
|
||||||
|
* @returns {VersionRecord | null} Parsed version record or null if invalid
|
||||||
|
*/
|
||||||
|
private parseVersionLine(line: string): VersionRecord | null {
|
||||||
|
try {
|
||||||
|
const parts = line.trim().split('|')
|
||||||
|
if (parts.length !== 6) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const [version, os, environment, packaged, mode, timestamp] = parts
|
||||||
|
|
||||||
|
// Validate data
|
||||||
|
if (
|
||||||
|
!version ||
|
||||||
|
!['win', 'mac', 'linux', 'unknown'].includes(os) ||
|
||||||
|
!['prod', 'dev'].includes(environment) ||
|
||||||
|
!['packaged', 'unpackaged'].includes(packaged) ||
|
||||||
|
!['install', 'portable'].includes(mode) ||
|
||||||
|
!timestamp
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
os: os as OS,
|
||||||
|
environment: environment as Environment,
|
||||||
|
packaged: packaged as Packaged,
|
||||||
|
mode: mode as Mode,
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`Failed to parse version line: ${line}`, error as Error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the last 1KB from version.log and returns all lines
|
||||||
|
* Uses reverse reading from file end to avoid reading the entire file
|
||||||
|
* @returns {string[]} Array of version lines from the last 1KB
|
||||||
|
*/
|
||||||
|
private readLastVersionLines(): string[] {
|
||||||
|
const logPath = this.getVersionLogPath()
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(logPath)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = fs.statSync(logPath)
|
||||||
|
const fileSize = stats.size
|
||||||
|
|
||||||
|
if (fileSize === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read from the end of the file, 1KB is enough to find previous version
|
||||||
|
// Typical line: "1.7.0-beta.3|win|prod|packaged|install|2025-01-15T08:30:00.000Z\n" (~70 bytes)
|
||||||
|
// 1KB can store ~14 lines, which is more than enough
|
||||||
|
const bufferSize = Math.min(1024, fileSize)
|
||||||
|
const buffer = Buffer.alloc(bufferSize)
|
||||||
|
|
||||||
|
const fd = fs.openSync(logPath, 'r')
|
||||||
|
try {
|
||||||
|
const startPosition = Math.max(0, fileSize - bufferSize)
|
||||||
|
fs.readSync(fd, buffer, 0, bufferSize, startPosition)
|
||||||
|
|
||||||
|
const content = buffer.toString('utf-8')
|
||||||
|
const lines = content
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.filter((line) => line.trim())
|
||||||
|
|
||||||
|
return lines
|
||||||
|
} finally {
|
||||||
|
fs.closeSync(fd)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to read version log:', error as Error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a version record line to version.log
|
||||||
|
* @param {string} line - Version record line to append
|
||||||
|
*/
|
||||||
|
private appendVersionLine(line: string): void {
|
||||||
|
const logPath = this.getVersionLogPath()
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.appendFileSync(logPath, line + '\n', 'utf-8')
|
||||||
|
logger.debug(`Version recorded: ${line}`)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to append version log:', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the current version on application startup
|
||||||
|
* Only adds a new record if the version has changed since the last run
|
||||||
|
*/
|
||||||
|
recordCurrentVersion(): void {
|
||||||
|
try {
|
||||||
|
const currentLine = this.generateCurrentVersionLine()
|
||||||
|
const lines = this.readLastVersionLines()
|
||||||
|
|
||||||
|
// Add new record if this is the first run or version has changed
|
||||||
|
if (lines.length === 0) {
|
||||||
|
logger.info('First run detected, creating version log')
|
||||||
|
this.appendVersionLine(currentLine)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastLine = lines[lines.length - 1]
|
||||||
|
const lastRecord = this.parseVersionLine(lastLine)
|
||||||
|
const currentVersion = app.getVersion()
|
||||||
|
|
||||||
|
// Check if any meaningful field has changed (version, os, environment, packaged, mode)
|
||||||
|
const currentOS = this.getCurrentOS()
|
||||||
|
const currentEnvironment = this.getCurrentEnvironment()
|
||||||
|
const currentPackaged = this.getPackagedStatus()
|
||||||
|
const currentMode = this.getInstallMode()
|
||||||
|
|
||||||
|
const hasMeaningfulChange =
|
||||||
|
!lastRecord ||
|
||||||
|
lastRecord.version !== currentVersion ||
|
||||||
|
lastRecord.os !== currentOS ||
|
||||||
|
lastRecord.environment !== currentEnvironment ||
|
||||||
|
lastRecord.packaged !== currentPackaged ||
|
||||||
|
lastRecord.mode !== currentMode
|
||||||
|
|
||||||
|
if (hasMeaningfulChange) {
|
||||||
|
logger.info(`Version information changed, recording new entry`)
|
||||||
|
this.appendVersionLine(currentLine)
|
||||||
|
} else {
|
||||||
|
logger.debug(`Version information not changed, skip recording`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to record current version:', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the previous version record (last record with different version than current)
|
||||||
|
* Reads from the last 1KB of version.log to find the most recent different version
|
||||||
|
* Useful for detecting version upgrades and running migrations
|
||||||
|
* @returns {VersionRecord | null} Previous version record or null if not available
|
||||||
|
*/
|
||||||
|
getPreviousVersion(): VersionRecord | null {
|
||||||
|
try {
|
||||||
|
const lines = this.readLastVersionLines()
|
||||||
|
if (lines.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentVersion = app.getVersion()
|
||||||
|
|
||||||
|
// Read from the end backwards to find the first different version
|
||||||
|
for (let i = lines.length - 1; i >= 0; i--) {
|
||||||
|
const record = this.parseVersionLine(lines[i])
|
||||||
|
if (record && record.version !== currentVersion) {
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to get previous version:', error as Error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance of VersionService
|
||||||
|
*/
|
||||||
|
export const versionService = new VersionService()
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import type { WebSocketCandidatesResponse, WebSocketStatusResponse } from '@shared/config/types'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import { networkInterfaces } from 'os'
|
import { networkInterfaces } from 'os'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
@@ -202,12 +203,7 @@ class WebSocketService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getStatus = async (): Promise<{
|
public getStatus = async (): Promise<WebSocketStatusResponse> => {
|
||||||
isRunning: boolean
|
|
||||||
port?: number
|
|
||||||
ip?: string
|
|
||||||
clientConnected: boolean
|
|
||||||
}> => {
|
|
||||||
return {
|
return {
|
||||||
isRunning: this.isStarted,
|
isRunning: this.isStarted,
|
||||||
port: this.isStarted ? this.port : undefined,
|
port: this.isStarted ? this.port : undefined,
|
||||||
@@ -216,13 +212,7 @@ class WebSocketService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAllCandidates = async (): Promise<
|
public getAllCandidates = async (): Promise<WebSocketCandidatesResponse[]> => {
|
||||||
Array<{
|
|
||||||
host: string
|
|
||||||
interface: string
|
|
||||||
priority: number
|
|
||||||
}>
|
|
||||||
> => {
|
|
||||||
const interfaces = networkInterfaces()
|
const interfaces = networkInterfaces()
|
||||||
|
|
||||||
// 按优先级排序的网络接口名称模式
|
// 按优先级排序的网络接口名称模式
|
||||||
|
|||||||
@@ -365,6 +365,16 @@ class ClaudeCodeService implements AgentServiceInterface {
|
|||||||
type: 'chunk',
|
type: 'chunk',
|
||||||
chunk
|
chunk
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Close prompt stream when SDK signals completion or error
|
||||||
|
if (chunk.type === 'finish' || chunk.type === 'error') {
|
||||||
|
logger.info('Closing prompt stream as SDK signaled completion', {
|
||||||
|
chunkType: chunk.type,
|
||||||
|
reason: chunk.type === 'finish' ? 'finished' : 'error_occurred'
|
||||||
|
})
|
||||||
|
closePromptStream()
|
||||||
|
logger.info('Prompt stream closed successfully')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { isWin } from '@main/constant'
|
import { isWin } from '@main/constant'
|
||||||
|
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
||||||
import type { OcrOvConfig, OcrResult, SupportedOcrFile } from '@types'
|
import type { OcrOvConfig, OcrResult, SupportedOcrFile } from '@types'
|
||||||
import { isImageFileMetadata } from '@types'
|
import { isImageFileMetadata } from '@types'
|
||||||
import { exec } from 'child_process'
|
import { exec } from 'child_process'
|
||||||
@@ -13,7 +14,7 @@ import { OcrBaseService } from './OcrBaseService'
|
|||||||
const logger = loggerService.withContext('OvOcrService')
|
const logger = loggerService.withContext('OvOcrService')
|
||||||
const execAsync = promisify(exec)
|
const execAsync = promisify(exec)
|
||||||
|
|
||||||
const PATH_BAT_FILE = path.join(os.homedir(), '.cherrystudio', 'ovms', 'ovocr', 'run.npu.bat')
|
const PATH_BAT_FILE = path.join(os.homedir(), HOME_CHERRY_DIR, 'ovms', 'ovocr', 'run.npu.bat')
|
||||||
|
|
||||||
export class OvOcrService extends OcrBaseService {
|
export class OvOcrService extends OcrBaseService {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -30,7 +31,7 @@ export class OvOcrService extends OcrBaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getOvOcrPath(): string {
|
private getOvOcrPath(): string {
|
||||||
return path.join(os.homedir(), '.cherrystudio', 'ovms', 'ovocr')
|
return path.join(os.homedir(), HOME_CHERRY_DIR, 'ovms', 'ovocr')
|
||||||
}
|
}
|
||||||
|
|
||||||
private getImgDir(): string {
|
private getImgDir(): string {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import os from 'node:os'
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { audioExts, documentExts, imageExts, MB, textExts, videoExts } from '@shared/config/constant'
|
import { audioExts, documentExts, HOME_CHERRY_DIR, imageExts, MB, textExts, videoExts } from '@shared/config/constant'
|
||||||
import type { FileMetadata, NotesTreeNode } from '@types'
|
import type { FileMetadata, NotesTreeNode } from '@types'
|
||||||
import { FileTypes } from '@types'
|
import { FileTypes } from '@types'
|
||||||
import chardet from 'chardet'
|
import chardet from 'chardet'
|
||||||
@@ -160,7 +160,7 @@ export function getNotesDir() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getConfigDir() {
|
export function getConfigDir() {
|
||||||
return path.join(os.homedir(), '.cherrystudio', 'config')
|
return path.join(os.homedir(), HOME_CHERRY_DIR, 'config')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCacheDir() {
|
export function getCacheDir() {
|
||||||
@@ -172,7 +172,7 @@ export function getAppConfigDir(name: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getMcpDir() {
|
export function getMcpDir() {
|
||||||
return path.join(os.homedir(), '.cherrystudio', 'mcp')
|
return path.join(os.homedir(), HOME_CHERRY_DIR, 'mcp')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import os from 'node:os'
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|
||||||
import { isLinux, isPortable, isWin } from '@main/constant'
|
import { isLinux, isPortable, isWin } from '@main/constant'
|
||||||
|
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
|
|
||||||
// Please don't import any other modules which is not node/electron built-in modules
|
// Please don't import any other modules which is not node/electron built-in modules
|
||||||
@@ -17,7 +18,7 @@ function hasWritePermission(path: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getConfigDir() {
|
function getConfigDir() {
|
||||||
return path.join(os.homedir(), '.cherrystudio', 'config')
|
return path.join(os.homedir(), HOME_CHERRY_DIR, 'config')
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initAppDataDir() {
|
export function initAppDataDir() {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
||||||
import { spawn } from 'child_process'
|
import { spawn } from 'child_process'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
@@ -46,11 +47,11 @@ export async function getBinaryName(name: string): Promise<string> {
|
|||||||
|
|
||||||
export async function getBinaryPath(name?: string): Promise<string> {
|
export async function getBinaryPath(name?: string): Promise<string> {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return path.join(os.homedir(), '.cherrystudio', 'bin')
|
return path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||||
}
|
}
|
||||||
|
|
||||||
const binaryName = await getBinaryName(name)
|
const binaryName = await getBinaryName(name)
|
||||||
const binariesDir = path.join(os.homedir(), '.cherrystudio', 'bin')
|
const binariesDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||||
const binariesDirExists = fs.existsSync(binariesDir)
|
const binariesDirExists = fs.existsSync(binariesDir)
|
||||||
return binariesDirExists ? path.join(binariesDir, binaryName) : binaryName
|
return binariesDirExists ? path.join(binariesDir, binaryName) : binaryName
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-20
@@ -6,11 +6,9 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import { PersistGate } from 'redux-persist/integration/react'
|
import { PersistGate } from 'redux-persist/integration/react'
|
||||||
|
|
||||||
import { ToastPortal } from './components/ToastPortal'
|
|
||||||
import TopViewContainer from './components/TopView'
|
import TopViewContainer from './components/TopView'
|
||||||
import AntdProvider from './context/AntdProvider'
|
import AntdProvider from './context/AntdProvider'
|
||||||
import { CodeStyleProvider } from './context/CodeStyleProvider'
|
import { CodeStyleProvider } from './context/CodeStyleProvider'
|
||||||
import { HeroUIProvider } from './context/HeroUIProvider'
|
|
||||||
import { NotificationProvider } from './context/NotificationProvider'
|
import { NotificationProvider } from './context/NotificationProvider'
|
||||||
import StyleSheetManager from './context/StyleSheetManager'
|
import StyleSheetManager from './context/StyleSheetManager'
|
||||||
import { ThemeProvider } from './context/ThemeProvider'
|
import { ThemeProvider } from './context/ThemeProvider'
|
||||||
@@ -34,24 +32,21 @@ function App(): React.ReactElement {
|
|||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<HeroUIProvider>
|
<StyleSheetManager>
|
||||||
<StyleSheetManager>
|
<ThemeProvider>
|
||||||
<ThemeProvider>
|
<AntdProvider>
|
||||||
<AntdProvider>
|
<NotificationProvider>
|
||||||
<NotificationProvider>
|
<CodeStyleProvider>
|
||||||
<CodeStyleProvider>
|
<PersistGate loading={null} persistor={persistor}>
|
||||||
<PersistGate loading={null} persistor={persistor}>
|
<TopViewContainer>
|
||||||
<TopViewContainer>
|
<Router />
|
||||||
<Router />
|
</TopViewContainer>
|
||||||
</TopViewContainer>
|
</PersistGate>
|
||||||
</PersistGate>
|
</CodeStyleProvider>
|
||||||
</CodeStyleProvider>
|
</NotificationProvider>
|
||||||
</NotificationProvider>
|
</AntdProvider>
|
||||||
</AntdProvider>
|
</ThemeProvider>
|
||||||
</ThemeProvider>
|
</StyleSheetManager>
|
||||||
</StyleSheetManager>
|
|
||||||
<ToastPortal />
|
|
||||||
</HeroUIProvider>
|
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</Provider>
|
</Provider>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { BedrockClient, ListFoundationModelsCommand, ListInferenceProfilesCommand } from '@aws-sdk/client-bedrock'
|
import { BedrockClient, ListFoundationModelsCommand, ListInferenceProfilesCommand } from '@aws-sdk/client-bedrock'
|
||||||
import {
|
import {
|
||||||
BedrockRuntimeClient,
|
BedrockRuntimeClient,
|
||||||
|
type BedrockRuntimeClientConfig,
|
||||||
ConverseCommand,
|
ConverseCommand,
|
||||||
InvokeModelCommand,
|
InvokeModelCommand,
|
||||||
InvokeModelWithResponseStreamCommand
|
InvokeModelWithResponseStreamCommand
|
||||||
@@ -11,6 +12,8 @@ import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
|
|||||||
import { findTokenLimit, isReasoningModel } from '@renderer/config/models'
|
import { findTokenLimit, isReasoningModel } from '@renderer/config/models'
|
||||||
import {
|
import {
|
||||||
getAwsBedrockAccessKeyId,
|
getAwsBedrockAccessKeyId,
|
||||||
|
getAwsBedrockApiKey,
|
||||||
|
getAwsBedrockAuthType,
|
||||||
getAwsBedrockRegion,
|
getAwsBedrockRegion,
|
||||||
getAwsBedrockSecretAccessKey
|
getAwsBedrockSecretAccessKey
|
||||||
} from '@renderer/hooks/useAwsBedrock'
|
} from '@renderer/hooks/useAwsBedrock'
|
||||||
@@ -75,32 +78,48 @@ export class AwsBedrockAPIClient extends BaseApiClient<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const region = getAwsBedrockRegion()
|
const region = getAwsBedrockRegion()
|
||||||
const accessKeyId = getAwsBedrockAccessKeyId()
|
const authType = getAwsBedrockAuthType()
|
||||||
const secretAccessKey = getAwsBedrockSecretAccessKey()
|
|
||||||
|
|
||||||
if (!region) {
|
if (!region) {
|
||||||
throw new Error('AWS region is required. Please configure AWS-Region in extra headers.')
|
throw new Error('AWS region is required. Please configure AWS region in settings.')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!accessKeyId || !secretAccessKey) {
|
// Build client configuration based on auth type
|
||||||
throw new Error('AWS credentials are required. Please configure AWS-Access-Key-ID and AWS-Secret-Access-Key.')
|
let clientConfig: BedrockRuntimeClientConfig
|
||||||
|
|
||||||
|
if (authType === 'iam') {
|
||||||
|
// IAM credentials authentication
|
||||||
|
const accessKeyId = getAwsBedrockAccessKeyId()
|
||||||
|
const secretAccessKey = getAwsBedrockSecretAccessKey()
|
||||||
|
|
||||||
|
if (!accessKeyId || !secretAccessKey) {
|
||||||
|
throw new Error('AWS credentials are required. Please configure Access Key ID and Secret Access Key.')
|
||||||
|
}
|
||||||
|
|
||||||
|
clientConfig = {
|
||||||
|
region,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId,
|
||||||
|
secretAccessKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// API Key authentication
|
||||||
|
const awsBedrockApiKey = getAwsBedrockApiKey()
|
||||||
|
|
||||||
|
if (!awsBedrockApiKey) {
|
||||||
|
throw new Error('AWS Bedrock API Key is required. Please configure API Key in settings.')
|
||||||
|
}
|
||||||
|
|
||||||
|
clientConfig = {
|
||||||
|
region,
|
||||||
|
token: { token: awsBedrockApiKey },
|
||||||
|
authSchemePreference: ['httpBearerAuth']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = new BedrockRuntimeClient({
|
const client = new BedrockRuntimeClient(clientConfig)
|
||||||
region,
|
const bedrockClient = new BedrockClient(clientConfig)
|
||||||
credentials: {
|
|
||||||
accessKeyId,
|
|
||||||
secretAccessKey
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const bedrockClient = new BedrockClient({
|
|
||||||
region,
|
|
||||||
credentials: {
|
|
||||||
accessKeyId,
|
|
||||||
secretAccessKey
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.sdkInstance = { client, bedrockClient, region }
|
this.sdkInstance = { client, bedrockClient, region }
|
||||||
return this.sdkInstance
|
return this.sdkInstance
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
|
|||||||
extra_body: {
|
extra_body: {
|
||||||
google: {
|
google: {
|
||||||
thinking_config: {
|
thinking_config: {
|
||||||
thinkingBudget: 0
|
thinking_budget: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,8 +327,8 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
|
|||||||
extra_body: {
|
extra_body: {
|
||||||
google: {
|
google: {
|
||||||
thinking_config: {
|
thinking_config: {
|
||||||
thinkingBudget: -1,
|
thinking_budget: -1,
|
||||||
includeThoughts: true
|
include_thoughts: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -338,8 +338,8 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
|
|||||||
extra_body: {
|
extra_body: {
|
||||||
google: {
|
google: {
|
||||||
thinking_config: {
|
thinking_config: {
|
||||||
thinkingBudget: budgetTokens,
|
thinking_budget: budgetTokens,
|
||||||
includeThoughts: true
|
include_thoughts: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -670,7 +670,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
|
|||||||
} else if (isClaudeReasoningModel(model) && reasoningEffort.thinking?.budget_tokens) {
|
} else if (isClaudeReasoningModel(model) && reasoningEffort.thinking?.budget_tokens) {
|
||||||
suffix = ` --thinking_budget ${reasoningEffort.thinking.budget_tokens}`
|
suffix = ` --thinking_budget ${reasoningEffort.thinking.budget_tokens}`
|
||||||
} else if (isGeminiReasoningModel(model) && reasoningEffort.extra_body?.google?.thinking_config) {
|
} else if (isGeminiReasoningModel(model) && reasoningEffort.extra_body?.google?.thinking_config) {
|
||||||
suffix = ` --thinking_budget ${reasoningEffort.extra_body.google.thinking_config.thinkingBudget}`
|
suffix = ` --thinking_budget ${reasoningEffort.extra_body.google.thinking_config.thinking_budget}`
|
||||||
}
|
}
|
||||||
// FIXME: poe 不支持多个text part,上传文本文件的时候用的不是file part而是text part,因此会出问题
|
// FIXME: poe 不支持多个text part,上传文本文件的时候用的不是file part而是text part,因此会出问题
|
||||||
// 临时解决方案是强制poe用string content,但是其实poe部分支持array
|
// 临时解决方案是强制poe用string content,但是其实poe部分支持array
|
||||||
|
|||||||
@@ -85,6 +85,19 @@ export function supportsLargeFileUpload(model: Model): boolean {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查模型是否支持TopP
|
||||||
|
*/
|
||||||
|
export function supportsTopP(model: Model): boolean {
|
||||||
|
const provider = getProviderByModel(model)
|
||||||
|
|
||||||
|
if (provider?.type === 'anthropic' || model?.endpoint_type === 'anthropic') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取提供商特定的文件大小限制
|
* 获取提供商特定的文件大小限制
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { setupToolsConfig } from '../utils/mcp'
|
|||||||
import { buildProviderOptions } from '../utils/options'
|
import { buildProviderOptions } from '../utils/options'
|
||||||
import { getAnthropicThinkingBudget } from '../utils/reasoning'
|
import { getAnthropicThinkingBudget } from '../utils/reasoning'
|
||||||
import { buildProviderBuiltinWebSearchConfig } from '../utils/websearch'
|
import { buildProviderBuiltinWebSearchConfig } from '../utils/websearch'
|
||||||
|
import { supportsTopP } from './modelCapabilities'
|
||||||
import { getTemperature, getTopP } from './modelParameters'
|
import { getTemperature, getTopP } from './modelParameters'
|
||||||
|
|
||||||
const logger = loggerService.withContext('parameterBuilder')
|
const logger = loggerService.withContext('parameterBuilder')
|
||||||
@@ -176,20 +177,27 @@ export async function buildStreamTextParams(
|
|||||||
messages: sdkMessages,
|
messages: sdkMessages,
|
||||||
maxOutputTokens: maxTokens,
|
maxOutputTokens: maxTokens,
|
||||||
temperature: getTemperature(assistant, model),
|
temperature: getTemperature(assistant, model),
|
||||||
topP: getTopP(assistant, model),
|
|
||||||
abortSignal: options.requestOptions?.signal,
|
abortSignal: options.requestOptions?.signal,
|
||||||
headers: options.requestOptions?.headers,
|
headers: options.requestOptions?.headers,
|
||||||
providerOptions,
|
providerOptions,
|
||||||
stopWhen: stepCountIs(20),
|
stopWhen: stepCountIs(20),
|
||||||
maxRetries: 0
|
maxRetries: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (supportsTopP(model)) {
|
||||||
|
params.topP = getTopP(assistant, model)
|
||||||
|
}
|
||||||
|
|
||||||
if (tools) {
|
if (tools) {
|
||||||
params.tools = tools
|
params.tools = tools
|
||||||
}
|
}
|
||||||
|
|
||||||
if (assistant.prompt) {
|
if (assistant.prompt) {
|
||||||
params.system = await replacePromptVariables(assistant.prompt, model.name)
|
params.system = await replacePromptVariables(assistant.prompt, model.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('params', params)
|
logger.debug('params', params)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
params,
|
params,
|
||||||
modelId: model.id,
|
modelId: model.id,
|
||||||
|
|||||||
@@ -21,10 +21,45 @@ vi.mock('@renderer/store', () => ({
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@renderer/utils/api', () => ({
|
||||||
|
formatApiHost: vi.fn((host, isSupportedAPIVersion = true) => {
|
||||||
|
if (isSupportedAPIVersion === false) {
|
||||||
|
return host // Return host as-is when isSupportedAPIVersion is false
|
||||||
|
}
|
||||||
|
return `${host}/v1` // Default behavior when isSupportedAPIVersion is true
|
||||||
|
}),
|
||||||
|
routeToEndpoint: vi.fn((host) => ({
|
||||||
|
baseURL: host,
|
||||||
|
endpoint: '/chat/completions'
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@renderer/config/providers', async (importOriginal) => {
|
||||||
|
const actual = (await importOriginal()) as any
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
isCherryAIProvider: vi.fn(),
|
||||||
|
isPerplexityProvider: vi.fn(),
|
||||||
|
isAnthropicProvider: vi.fn(() => false),
|
||||||
|
isAzureOpenAIProvider: vi.fn(() => false),
|
||||||
|
isGeminiProvider: vi.fn(() => false),
|
||||||
|
isNewApiProvider: vi.fn(() => false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.mock('@renderer/hooks/useVertexAI', () => ({
|
||||||
|
isVertexProvider: vi.fn(() => false),
|
||||||
|
isVertexAIConfigured: vi.fn(() => false),
|
||||||
|
createVertexProvider: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
import { isCherryAIProvider, isPerplexityProvider } from '@renderer/config/providers'
|
||||||
|
import { getProviderByModel } from '@renderer/services/AssistantService'
|
||||||
import type { Model, Provider } from '@renderer/types'
|
import type { Model, Provider } from '@renderer/types'
|
||||||
|
import { formatApiHost } from '@renderer/utils/api'
|
||||||
|
|
||||||
import { COPILOT_DEFAULT_HEADERS, COPILOT_EDITOR_VERSION, isCopilotResponsesModel } from '../constants'
|
import { COPILOT_DEFAULT_HEADERS, COPILOT_EDITOR_VERSION, isCopilotResponsesModel } from '../constants'
|
||||||
import { providerToAiSdkConfig } from '../providerConfig'
|
import { getActualProvider, providerToAiSdkConfig } from '../providerConfig'
|
||||||
|
|
||||||
const createWindowKeyv = () => {
|
const createWindowKeyv = () => {
|
||||||
const store = new Map<string, string>()
|
const store = new Map<string, string>()
|
||||||
@@ -46,11 +81,31 @@ const createCopilotProvider = (): Provider => ({
|
|||||||
isSystem: true
|
isSystem: true
|
||||||
})
|
})
|
||||||
|
|
||||||
const createModel = (id: string, name = id): Model => ({
|
const createModel = (id: string, name = id, provider = 'copilot'): Model => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
provider: 'copilot',
|
provider,
|
||||||
group: 'copilot'
|
group: provider
|
||||||
|
})
|
||||||
|
|
||||||
|
const createCherryAIProvider = (): Provider => ({
|
||||||
|
id: 'cherryai',
|
||||||
|
type: 'openai',
|
||||||
|
name: 'CherryAI',
|
||||||
|
apiKey: 'test-key',
|
||||||
|
apiHost: 'https://api.cherryai.com',
|
||||||
|
models: [],
|
||||||
|
isSystem: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const createPerplexityProvider = (): Provider => ({
|
||||||
|
id: 'perplexity',
|
||||||
|
type: 'openai',
|
||||||
|
name: 'Perplexity',
|
||||||
|
apiKey: 'test-key',
|
||||||
|
apiHost: 'https://api.perplexity.ai',
|
||||||
|
models: [],
|
||||||
|
isSystem: false
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Copilot responses routing', () => {
|
describe('Copilot responses routing', () => {
|
||||||
@@ -87,3 +142,134 @@ describe('Copilot responses routing', () => {
|
|||||||
expect(config.options.headers?.['Copilot-Integration-Id']).toBe(COPILOT_DEFAULT_HEADERS['Copilot-Integration-Id'])
|
expect(config.options.headers?.['Copilot-Integration-Id']).toBe(COPILOT_DEFAULT_HEADERS['Copilot-Integration-Id'])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('CherryAI provider configuration', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
;(globalThis as any).window = {
|
||||||
|
...(globalThis as any).window,
|
||||||
|
keyv: createWindowKeyv()
|
||||||
|
}
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('formats CherryAI provider apiHost with false parameter', () => {
|
||||||
|
const provider = createCherryAIProvider()
|
||||||
|
const model = createModel('gpt-4', 'GPT-4', 'cherryai')
|
||||||
|
|
||||||
|
// Mock the functions to simulate CherryAI provider detection
|
||||||
|
vi.mocked(isCherryAIProvider).mockReturnValue(true)
|
||||||
|
vi.mocked(getProviderByModel).mockReturnValue(provider)
|
||||||
|
|
||||||
|
// Call getActualProvider which should trigger formatProviderApiHost
|
||||||
|
const actualProvider = getActualProvider(model)
|
||||||
|
|
||||||
|
// Verify that formatApiHost was called with false as the second parameter
|
||||||
|
expect(formatApiHost).toHaveBeenCalledWith('https://api.cherryai.com', false)
|
||||||
|
expect(actualProvider.apiHost).toBe('https://api.cherryai.com')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not format non-CherryAI provider with false parameter', () => {
|
||||||
|
const provider = {
|
||||||
|
id: 'openai',
|
||||||
|
type: 'openai',
|
||||||
|
name: 'OpenAI',
|
||||||
|
apiKey: 'test-key',
|
||||||
|
apiHost: 'https://api.openai.com',
|
||||||
|
models: [],
|
||||||
|
isSystem: false
|
||||||
|
} as Provider
|
||||||
|
const model = createModel('gpt-4', 'GPT-4', 'openai')
|
||||||
|
|
||||||
|
// Mock the functions to simulate non-CherryAI provider
|
||||||
|
vi.mocked(isCherryAIProvider).mockReturnValue(false)
|
||||||
|
vi.mocked(getProviderByModel).mockReturnValue(provider)
|
||||||
|
|
||||||
|
// Call getActualProvider
|
||||||
|
const actualProvider = getActualProvider(model)
|
||||||
|
|
||||||
|
// Verify that formatApiHost was called with default parameters (true)
|
||||||
|
expect(formatApiHost).toHaveBeenCalledWith('https://api.openai.com')
|
||||||
|
expect(actualProvider.apiHost).toBe('https://api.openai.com/v1')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles CherryAI provider with empty apiHost', () => {
|
||||||
|
const provider = createCherryAIProvider()
|
||||||
|
provider.apiHost = ''
|
||||||
|
const model = createModel('gpt-4', 'GPT-4', 'cherryai')
|
||||||
|
|
||||||
|
vi.mocked(isCherryAIProvider).mockReturnValue(true)
|
||||||
|
vi.mocked(getProviderByModel).mockReturnValue(provider)
|
||||||
|
|
||||||
|
const actualProvider = getActualProvider(model)
|
||||||
|
|
||||||
|
expect(formatApiHost).toHaveBeenCalledWith('', false)
|
||||||
|
expect(actualProvider.apiHost).toBe('')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Perplexity provider configuration', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
;(globalThis as any).window = {
|
||||||
|
...(globalThis as any).window,
|
||||||
|
keyv: createWindowKeyv()
|
||||||
|
}
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('formats Perplexity provider apiHost with false parameter', () => {
|
||||||
|
const provider = createPerplexityProvider()
|
||||||
|
const model = createModel('sonar', 'Sonar', 'perplexity')
|
||||||
|
|
||||||
|
// Mock the functions to simulate Perplexity provider detection
|
||||||
|
vi.mocked(isCherryAIProvider).mockReturnValue(false)
|
||||||
|
vi.mocked(isPerplexityProvider).mockReturnValue(true)
|
||||||
|
vi.mocked(getProviderByModel).mockReturnValue(provider)
|
||||||
|
|
||||||
|
// Call getActualProvider which should trigger formatProviderApiHost
|
||||||
|
const actualProvider = getActualProvider(model)
|
||||||
|
|
||||||
|
// Verify that formatApiHost was called with false as the second parameter
|
||||||
|
expect(formatApiHost).toHaveBeenCalledWith('https://api.perplexity.ai', false)
|
||||||
|
expect(actualProvider.apiHost).toBe('https://api.perplexity.ai')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not format non-Perplexity provider with false parameter', () => {
|
||||||
|
const provider = {
|
||||||
|
id: 'openai',
|
||||||
|
type: 'openai',
|
||||||
|
name: 'OpenAI',
|
||||||
|
apiKey: 'test-key',
|
||||||
|
apiHost: 'https://api.openai.com',
|
||||||
|
models: [],
|
||||||
|
isSystem: false
|
||||||
|
} as Provider
|
||||||
|
const model = createModel('gpt-4', 'GPT-4', 'openai')
|
||||||
|
|
||||||
|
// Mock the functions to simulate non-Perplexity provider
|
||||||
|
vi.mocked(isCherryAIProvider).mockReturnValue(false)
|
||||||
|
vi.mocked(isPerplexityProvider).mockReturnValue(false)
|
||||||
|
vi.mocked(getProviderByModel).mockReturnValue(provider)
|
||||||
|
|
||||||
|
// Call getActualProvider
|
||||||
|
const actualProvider = getActualProvider(model)
|
||||||
|
|
||||||
|
// Verify that formatApiHost was called with default parameters (true)
|
||||||
|
expect(formatApiHost).toHaveBeenCalledWith('https://api.openai.com')
|
||||||
|
expect(actualProvider.apiHost).toBe('https://api.openai.com/v1')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles Perplexity provider with empty apiHost', () => {
|
||||||
|
const provider = createPerplexityProvider()
|
||||||
|
provider.apiHost = ''
|
||||||
|
const model = createModel('sonar', 'Sonar', 'perplexity')
|
||||||
|
|
||||||
|
vi.mocked(isCherryAIProvider).mockReturnValue(false)
|
||||||
|
vi.mocked(isPerplexityProvider).mockReturnValue(true)
|
||||||
|
vi.mocked(getProviderByModel).mockReturnValue(provider)
|
||||||
|
|
||||||
|
const actualProvider = getActualProvider(model)
|
||||||
|
|
||||||
|
expect(formatApiHost).toHaveBeenCalledWith('', false)
|
||||||
|
expect(actualProvider.apiHost).toBe('')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const AIHUBMIX_RULES: RuleSet = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
fallbackRule: (provider: Provider) => provider
|
fallbackRule: (provider: Provider) => extraProviderConfig(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const aihubmixProviderCreator = provider2Provider.bind(null, AIHUBMIX_RULES)
|
export const aihubmixProviderCreator = provider2Provider.bind(null, AIHUBMIX_RULES)
|
||||||
|
|||||||
@@ -9,11 +9,15 @@ import { isOpenAIChatCompletionOnlyModel } from '@renderer/config/models'
|
|||||||
import {
|
import {
|
||||||
isAnthropicProvider,
|
isAnthropicProvider,
|
||||||
isAzureOpenAIProvider,
|
isAzureOpenAIProvider,
|
||||||
|
isCherryAIProvider,
|
||||||
isGeminiProvider,
|
isGeminiProvider,
|
||||||
isNewApiProvider
|
isNewApiProvider,
|
||||||
|
isPerplexityProvider
|
||||||
} from '@renderer/config/providers'
|
} from '@renderer/config/providers'
|
||||||
import {
|
import {
|
||||||
getAwsBedrockAccessKeyId,
|
getAwsBedrockAccessKeyId,
|
||||||
|
getAwsBedrockApiKey,
|
||||||
|
getAwsBedrockAuthType,
|
||||||
getAwsBedrockRegion,
|
getAwsBedrockRegion,
|
||||||
getAwsBedrockSecretAccessKey
|
getAwsBedrockSecretAccessKey
|
||||||
} from '@renderer/hooks/useAwsBedrock'
|
} from '@renderer/hooks/useAwsBedrock'
|
||||||
@@ -98,6 +102,10 @@ function formatProviderApiHost(provider: Provider): Provider {
|
|||||||
formatted.apiHost = formatAzureOpenAIApiHost(formatted.apiHost)
|
formatted.apiHost = formatAzureOpenAIApiHost(formatted.apiHost)
|
||||||
} else if (isVertexProvider(formatted)) {
|
} else if (isVertexProvider(formatted)) {
|
||||||
formatted.apiHost = formatVertexApiHost(formatted)
|
formatted.apiHost = formatVertexApiHost(formatted)
|
||||||
|
} else if (isCherryAIProvider(formatted)) {
|
||||||
|
formatted.apiHost = formatApiHost(formatted.apiHost, false)
|
||||||
|
} else if (isPerplexityProvider(formatted)) {
|
||||||
|
formatted.apiHost = formatApiHost(formatted.apiHost, false)
|
||||||
} else {
|
} else {
|
||||||
formatted.apiHost = formatApiHost(formatted.apiHost)
|
formatted.apiHost = formatApiHost(formatted.apiHost)
|
||||||
}
|
}
|
||||||
@@ -192,9 +200,15 @@ export function providerToAiSdkConfig(
|
|||||||
|
|
||||||
// bedrock
|
// bedrock
|
||||||
if (aiSdkProviderId === 'bedrock') {
|
if (aiSdkProviderId === 'bedrock') {
|
||||||
|
const authType = getAwsBedrockAuthType()
|
||||||
extraOptions.region = getAwsBedrockRegion()
|
extraOptions.region = getAwsBedrockRegion()
|
||||||
extraOptions.accessKeyId = getAwsBedrockAccessKeyId()
|
|
||||||
extraOptions.secretAccessKey = getAwsBedrockSecretAccessKey()
|
if (authType === 'apiKey') {
|
||||||
|
extraOptions.apiKey = getAwsBedrockApiKey()
|
||||||
|
} else {
|
||||||
|
extraOptions.accessKeyId = getAwsBedrockAccessKeyId()
|
||||||
|
extraOptions.secretAccessKey = getAwsBedrockSecretAccessKey()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// google-vertex
|
// google-vertex
|
||||||
if (aiSdkProviderId === 'google-vertex' || aiSdkProviderId === 'google-vertex-anthropic') {
|
if (aiSdkProviderId === 'google-vertex' || aiSdkProviderId === 'google-vertex-anthropic') {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { getAiSdkProviderId } from '../provider/factory'
|
|||||||
import { buildGeminiGenerateImageParams } from './image'
|
import { buildGeminiGenerateImageParams } from './image'
|
||||||
import {
|
import {
|
||||||
getAnthropicReasoningParams,
|
getAnthropicReasoningParams,
|
||||||
|
getBedrockReasoningParams,
|
||||||
getCustomParameters,
|
getCustomParameters,
|
||||||
getGeminiReasoningParams,
|
getGeminiReasoningParams,
|
||||||
getOpenAIReasoningParams,
|
getOpenAIReasoningParams,
|
||||||
@@ -127,6 +128,9 @@ export function buildProviderOptions(
|
|||||||
case 'google-vertex-anthropic':
|
case 'google-vertex-anthropic':
|
||||||
providerSpecificOptions = buildAnthropicProviderOptions(assistant, model, capabilities)
|
providerSpecificOptions = buildAnthropicProviderOptions(assistant, model, capabilities)
|
||||||
break
|
break
|
||||||
|
case 'bedrock':
|
||||||
|
providerSpecificOptions = buildBedrockProviderOptions(assistant, model, capabilities)
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
// 对于其他 provider,使用通用的构建逻辑
|
// 对于其他 provider,使用通用的构建逻辑
|
||||||
providerSpecificOptions = {
|
providerSpecificOptions = {
|
||||||
@@ -266,6 +270,32 @@ function buildXAIProviderOptions(
|
|||||||
return providerOptions
|
return providerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build Bedrock providerOptions
|
||||||
|
*/
|
||||||
|
function buildBedrockProviderOptions(
|
||||||
|
assistant: Assistant,
|
||||||
|
model: Model,
|
||||||
|
capabilities: {
|
||||||
|
enableReasoning: boolean
|
||||||
|
enableWebSearch: boolean
|
||||||
|
enableGenerateImage: boolean
|
||||||
|
}
|
||||||
|
): Record<string, any> {
|
||||||
|
const { enableReasoning } = capabilities
|
||||||
|
let providerOptions: Record<string, any> = {}
|
||||||
|
|
||||||
|
if (enableReasoning) {
|
||||||
|
const reasoningParams = getBedrockReasoningParams(assistant, model)
|
||||||
|
providerOptions = {
|
||||||
|
...providerOptions,
|
||||||
|
...reasoningParams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return providerOptions
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建通用的 providerOptions(用于其他 provider)
|
* 构建通用的 providerOptions(用于其他 provider)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
|
|||||||
extra_body: {
|
extra_body: {
|
||||||
google: {
|
google: {
|
||||||
thinking_config: {
|
thinking_config: {
|
||||||
thinkingBudget: 0
|
thinking_budget: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,8 +259,8 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
|
|||||||
extra_body: {
|
extra_body: {
|
||||||
google: {
|
google: {
|
||||||
thinking_config: {
|
thinking_config: {
|
||||||
thinkingBudget: -1,
|
thinking_budget: -1,
|
||||||
includeThoughts: true
|
include_thoughts: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,8 +270,8 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
|
|||||||
extra_body: {
|
extra_body: {
|
||||||
google: {
|
google: {
|
||||||
thinking_config: {
|
thinking_config: {
|
||||||
thinkingBudget: budgetTokens,
|
thinking_budget: budgetTokens ?? -1,
|
||||||
includeThoughts: true
|
include_thoughts: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,8 +431,8 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re
|
|||||||
if (reasoningEffort === undefined) {
|
if (reasoningEffort === undefined) {
|
||||||
return {
|
return {
|
||||||
thinkingConfig: {
|
thinkingConfig: {
|
||||||
includeThoughts: false,
|
include_thoughts: false,
|
||||||
...(GEMINI_FLASH_MODEL_REGEX.test(model.id) ? { thinkingBudget: 0 } : {})
|
...(GEMINI_FLASH_MODEL_REGEX.test(model.id) ? { thinking_budget: 0 } : {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -442,7 +442,7 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re
|
|||||||
if (effortRatio > 1) {
|
if (effortRatio > 1) {
|
||||||
return {
|
return {
|
||||||
thinkingConfig: {
|
thinkingConfig: {
|
||||||
includeThoughts: true
|
include_thoughts: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -452,8 +452,8 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
thinkingConfig: {
|
thinkingConfig: {
|
||||||
...(budget > 0 ? { thinkingBudget: budget } : {}),
|
...(budget > 0 ? { thinking_budget: budget } : {}),
|
||||||
includeThoughts: true
|
include_thoughts: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -485,6 +485,34 @@ export function getXAIReasoningParams(assistant: Assistant, model: Model): Recor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Bedrock reasoning parameters
|
||||||
|
*/
|
||||||
|
export function getBedrockReasoningParams(assistant: Assistant, model: Model): Record<string, any> {
|
||||||
|
if (!isReasoningModel(model)) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reasoningEffort = assistant?.settings?.reasoning_effort
|
||||||
|
|
||||||
|
if (reasoningEffort === undefined) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only apply thinking budget for Claude reasoning models
|
||||||
|
if (!isSupportedThinkingTokenClaudeModel(model)) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const budgetTokens = getAnthropicThinkingBudget(assistant, model)
|
||||||
|
return {
|
||||||
|
reasoningConfig: {
|
||||||
|
type: 'enabled',
|
||||||
|
budgetTokens: budgetTokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取自定义参数
|
* 获取自定义参数
|
||||||
* 从 assistant 设置中提取自定义参数
|
* 从 assistant 设置中提取自定义参数
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="33" height="38" viewBox="0 0 33 38" fill="none">
|
||||||
|
<g clip-path="url(#clip0_4321_9943)">
|
||||||
|
<path d="M1.51221 6.59813C1.51221 4.09263 3.54331 2.06152 6.04881 2.06152H27.9757C30.4812 2.06152 32.5123 4.09263 32.5123 6.59813C32.5123 9.10362 30.4812 11.1347 27.9757 11.1347H6.04881C3.54331 11.1347 1.51221 9.10362 1.51221 6.59813Z" fill="#6200EE"/>
|
||||||
|
<path d="M3.38905 3.56467C5.26076 1.89906 8.12831 2.06615 9.79391 3.93785L22.1493 17.8221C23.8149 19.6938 23.6478 22.5614 21.7761 24.227C19.9044 25.8926 17.0369 25.7255 15.3713 23.8538L3.01586 9.96953C1.35026 8.09782 1.51734 5.23027 3.38905 3.56467Z" fill="#6200EE"/>
|
||||||
|
<path d="M1.51221 20.9643C1.51221 18.4588 3.54331 16.4277 6.04881 16.4277H18.9025C21.408 16.4277 23.4391 18.4588 23.4391 20.9643C23.4391 23.4698 21.408 25.5009 18.9025 25.5009H6.04881C3.54331 25.5009 1.51221 23.4698 1.51221 20.9643Z" fill="#6200EE"/>
|
||||||
|
<path d="M10.5854 32.3052C10.5854 34.8107 8.55431 36.8418 6.04881 36.8418C3.54331 36.8418 1.51221 34.8107 1.51221 32.3052C1.51221 29.7997 3.54331 27.7686 6.04881 27.7686C8.55431 27.7686 10.5854 29.7997 10.5854 32.3052Z" fill="#BF7AFF"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_4321_9943">
|
||||||
|
<rect width="32.5124" height="36.9029" fill="white" transform="translate(0 0.548828)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -41,11 +41,11 @@ body,
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #root {
|
#root {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
} */
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
@import 'tailwindcss' source('../../../../renderer');
|
@import 'tailwindcss' source('../../../../renderer');
|
||||||
@import 'tw-animate-css';
|
@import 'tw-animate-css';
|
||||||
|
|
||||||
/* heroui */
|
|
||||||
@plugin '../../hero.ts';
|
|
||||||
@source '../../../../../node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}';
|
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
/* 如需自定义:
|
/* 如需自定义:
|
||||||
@@ -156,11 +152,6 @@
|
|||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* To disable drag title bar on toast. tailwind css doesn't provide such class name. */
|
|
||||||
.hero-toast {
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Avatar, cn } from '@heroui/react'
|
|
||||||
import { getModelLogoById } from '@renderer/config/models'
|
|
||||||
import type { ApiModel } from '@renderer/types'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
import Ellipsis from './Ellipsis'
|
|
||||||
|
|
||||||
export interface ModelLabelProps extends Omit<React.ComponentPropsWithRef<'div'>, 'children'> {
|
|
||||||
model?: ApiModel
|
|
||||||
classNames?: {
|
|
||||||
container?: string
|
|
||||||
avatar?: string
|
|
||||||
modelName?: string
|
|
||||||
divider?: string
|
|
||||||
providerName?: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ApiModelLabel: React.FC<ModelLabelProps> = ({ model, className, classNames, ...props }) => {
|
|
||||||
return (
|
|
||||||
<div className={cn('flex items-center gap-1', className, classNames?.container)} {...props}>
|
|
||||||
<Avatar
|
|
||||||
src={model ? (getModelLogoById(model.id) ?? getModelLogoById(model.name)) : undefined}
|
|
||||||
className={cn('h-4 w-4', classNames?.avatar)}
|
|
||||||
/>
|
|
||||||
<Ellipsis className={classNames?.modelName}>{model?.name}</Ellipsis>
|
|
||||||
<span className={classNames?.divider}> | </span>
|
|
||||||
<Ellipsis className={classNames?.providerName}>{model?.provider_name}</Ellipsis>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button, Popover, PopoverContent, PopoverTrigger } from '@heroui/react'
|
import { Button, Popover } from 'antd'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import EmojiPicker from '../EmojiPicker'
|
import EmojiPicker from '../EmojiPicker'
|
||||||
@@ -10,13 +10,10 @@ type Props = {
|
|||||||
|
|
||||||
export const EmojiAvatarWithPicker: React.FC<Props> = ({ emoji, onPick }) => {
|
export const EmojiAvatarWithPicker: React.FC<Props> = ({ emoji, onPick }) => {
|
||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover content={<EmojiPicker onEmojiClick={onPick} />} trigger="click">
|
||||||
<PopoverTrigger>
|
<Button type="text" style={{ width: 32, height: 32, fontSize: 18 }}>
|
||||||
<Button size="sm" startContent={<span className="text-lg">{emoji}</span>} isIconOnly />
|
{emoji}
|
||||||
</PopoverTrigger>
|
</Button>
|
||||||
<PopoverContent>
|
|
||||||
<EmojiPicker onEmojiClick={onPick}></EmojiPicker>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
</Popover>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { cn } from '@heroui/react'
|
import { cn } from '@renderer/utils'
|
||||||
import type { ButtonProps } from 'antd'
|
import type { ButtonProps } from 'antd'
|
||||||
import { Button } from 'antd'
|
import { Button } from 'antd'
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button } from '@heroui/react'
|
import { CheckOutlined, CloseOutlined } from '@ant-design/icons'
|
||||||
import { CheckIcon, XIcon } from 'lucide-react'
|
import { Button } from 'antd'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
|
|
||||||
@@ -28,12 +28,22 @@ const ConfirmDialog: FC<Props> = ({ x, y, message, onConfirm, onCancel }) => {
|
|||||||
<div className="flex min-w-[160px] items-center rounded-lg border border-[var(--color-border)] bg-[var(--color-background)] p-3 shadow-[0_4px_12px_rgba(0,0,0,0.15)]">
|
<div className="flex min-w-[160px] items-center rounded-lg border border-[var(--color-border)] bg-[var(--color-background)] p-3 shadow-[0_4px_12px_rgba(0,0,0,0.15)]">
|
||||||
<div className="mr-2 text-sm leading-[1.4]">{message}</div>
|
<div className="mr-2 text-sm leading-[1.4]">{message}</div>
|
||||||
<div className="flex justify-center gap-2">
|
<div className="flex justify-center gap-2">
|
||||||
<Button onPress={onCancel} radius="full" className="h-6 w-6 min-w-0 p-1" color="danger">
|
<Button
|
||||||
<XIcon className="text-danger-foreground" size={16} />
|
onClick={onCancel}
|
||||||
</Button>
|
shape="circle"
|
||||||
<Button onPress={onConfirm} radius="full" className="h-6 w-6 min-w-0 p-1" color="success">
|
size="small"
|
||||||
<CheckIcon className="text-success-foreground" size={16} />
|
danger
|
||||||
</Button>
|
icon={<CloseOutlined />}
|
||||||
|
style={{ width: 24, height: 24, minWidth: 24 }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={onConfirm}
|
||||||
|
shape="circle"
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
icon={<CheckOutlined />}
|
||||||
|
style={{ width: 24, height: 24, minWidth: 24, backgroundColor: '#52c41a' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button } from '@heroui/button'
|
|
||||||
import { formatErrorMessage } from '@renderer/utils/error'
|
import { formatErrorMessage } from '@renderer/utils/error'
|
||||||
|
import { Button } from 'antd'
|
||||||
import { Alert, Space } from 'antd'
|
import { Alert, Space } from 'antd'
|
||||||
import type { ComponentType, ReactNode } from 'react'
|
import type { ComponentType, ReactNode } from 'react'
|
||||||
import type { FallbackProps } from 'react-error-boundary'
|
import type { FallbackProps } from 'react-error-boundary'
|
||||||
@@ -24,10 +24,10 @@ const DefaultFallback: ComponentType<FallbackProps> = (props: FallbackProps): Re
|
|||||||
type="error"
|
type="error"
|
||||||
action={
|
action={
|
||||||
<Space>
|
<Space>
|
||||||
<Button size="sm" onPress={debug}>
|
<Button size="small" onClick={debug}>
|
||||||
{t('error.boundary.default.devtools')}
|
{t('error.boundary.default.devtools')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="sm" onPress={reload}>
|
<Button size="small" onClick={reload}>
|
||||||
{t('error.boundary.default.reload')}
|
{t('error.boundary.default.reload')}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { cn } from '@heroui/react'
|
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
|
import { cn } from '@renderer/utils'
|
||||||
import { ChevronRight } from 'lucide-react'
|
import { ChevronRight } from 'lucide-react'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { cn } from '@heroui/react'
|
|
||||||
import { TopView } from '@renderer/components/TopView'
|
import { TopView } from '@renderer/components/TopView'
|
||||||
|
import { cn } from '@renderer/utils'
|
||||||
import { Modal } from 'antd'
|
import { Modal } from 'antd'
|
||||||
import { Bot, MessageSquare } from 'lucide-react'
|
import { Bot, MessageSquare } from 'lucide-react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
@@ -51,7 +51,7 @@ const PopupContainer: React.FC<Props> = ({ onSelect, resolve }) => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleSelect('assistant')}
|
onClick={() => handleSelect('assistant')}
|
||||||
className="group flex flex-col items-center gap-3 rounded-lg bg-[var(--color-background-soft)] p-6 transition-all hover:bg-[var(--color-hover)]"
|
className="group flex cursor-pointer flex-col items-center gap-3 rounded-lg bg-[var(--color-background-soft)] p-6 transition-all hover:bg-[var(--color-hover)]"
|
||||||
onMouseEnter={() => setHoveredOption('assistant')}
|
onMouseEnter={() => setHoveredOption('assistant')}
|
||||||
onMouseLeave={() => setHoveredOption(null)}>
|
onMouseLeave={() => setHoveredOption(null)}>
|
||||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-list-item)] transition-colors">
|
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-list-item)] transition-colors">
|
||||||
@@ -73,7 +73,7 @@ const PopupContainer: React.FC<Props> = ({ onSelect, resolve }) => {
|
|||||||
<button
|
<button
|
||||||
onClick={() => handleSelect('agent')}
|
onClick={() => handleSelect('agent')}
|
||||||
type="button"
|
type="button"
|
||||||
className="group flex flex-col items-center gap-3 rounded-lg bg-[var(--color-background-soft)] p-6 transition-all hover:bg-[var(--color-hover)]"
|
className="group flex cursor-pointer flex-col items-center gap-3 rounded-lg bg-[var(--color-background-soft)] p-6 transition-all hover:bg-[var(--color-hover)]"
|
||||||
onMouseEnter={() => setHoveredOption('agent')}
|
onMouseEnter={() => setHoveredOption('agent')}
|
||||||
onMouseLeave={() => setHoveredOption(null)}>
|
onMouseLeave={() => setHoveredOption(null)}>
|
||||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-list-item)] transition-colors">
|
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-[var(--color-list-item)] transition-colors">
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Button } from '@heroui/button'
|
|
||||||
import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@heroui/modal'
|
|
||||||
import { Progress } from '@heroui/progress'
|
|
||||||
import { Spinner } from '@heroui/spinner'
|
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
|
import { AppLogo } from '@renderer/config/env'
|
||||||
import { SettingHelpText, SettingRow } from '@renderer/pages/settings'
|
import { SettingHelpText, SettingRow } from '@renderer/pages/settings'
|
||||||
|
import type { WebSocketCandidatesResponse } from '@shared/config/types'
|
||||||
|
import { Alert, Button, Modal, Progress, Spin } from 'antd'
|
||||||
import { QRCodeSVG } from 'qrcode.react'
|
import { QRCodeSVG } from 'qrcode.react'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -23,7 +22,7 @@ const LoadingQRCode: React.FC = () => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '12px' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '12px' }}>
|
||||||
<Spinner />
|
<Spin />
|
||||||
<span style={{ fontSize: '14px', color: 'var(--color-text-2)' }}>
|
<span style={{ fontSize: '14px', color: 'var(--color-text-2)' }}>
|
||||||
{t('settings.data.export_to_phone.lan.generating_qr')}
|
{t('settings.data.export_to_phone.lan.generating_qr')}
|
||||||
</span>
|
</span>
|
||||||
@@ -38,10 +37,10 @@ const ScanQRCode: React.FC<{ qrCodeValue: string }> = ({ qrCodeValue }) => {
|
|||||||
<QRCodeSVG
|
<QRCodeSVG
|
||||||
marginSize={2}
|
marginSize={2}
|
||||||
value={qrCodeValue}
|
value={qrCodeValue}
|
||||||
level="Q"
|
level="H"
|
||||||
size={160}
|
size={200}
|
||||||
imageSettings={{
|
imageSettings={{
|
||||||
src: '/src/assets/images/logo.png',
|
src: AppLogo,
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
excavate: true
|
excavate: true
|
||||||
@@ -70,7 +69,7 @@ const ConnectingAnimation: React.FC = () => {
|
|||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
backgroundColor: 'var(--color-status-warning)'
|
backgroundColor: 'var(--color-status-warning)'
|
||||||
}}>
|
}}>
|
||||||
<Spinner size="lg" color="warning" />
|
<Spin size="large" />
|
||||||
<span style={{ fontSize: '14px', color: 'var(--color-text)', marginTop: '12px' }}>
|
<span style={{ fontSize: '14px', color: 'var(--color-text)', marginTop: '12px' }}>
|
||||||
{t('settings.data.export_to_phone.lan.status.connecting')}
|
{t('settings.data.export_to_phone.lan.status.connecting')}
|
||||||
</span>
|
</span>
|
||||||
@@ -135,7 +134,6 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
const [selectedFolderPath, setSelectedFolderPath] = useState<string | null>(null)
|
const [selectedFolderPath, setSelectedFolderPath] = useState<string | null>(null)
|
||||||
const [sendProgress, setSendProgress] = useState(0)
|
const [sendProgress, setSendProgress] = useState(0)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
const [showCloseConfirm, setShowCloseConfirm] = useState(false)
|
|
||||||
const [autoCloseCountdown, setAutoCloseCountdown] = useState<number | null>(null)
|
const [autoCloseCountdown, setAutoCloseCountdown] = useState<number | null>(null)
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -198,17 +196,28 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
const { port, ip } = await window.api.webSocket.status()
|
const { port, ip } = await window.api.webSocket.status()
|
||||||
|
|
||||||
if (ip && port) {
|
if (ip && port) {
|
||||||
const candidates = await window.api.webSocket.getAllCandidates()
|
const candidatesData = await window.api.webSocket.getAllCandidates()
|
||||||
const connectionInfo = {
|
|
||||||
type: 'cherry-studio-app',
|
const optimizeConnectionInfo = () => {
|
||||||
candidates,
|
const ipToNumber = (ip: string) => {
|
||||||
selectedHost: ip,
|
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet), 0)
|
||||||
port,
|
}
|
||||||
timestamp: Date.now()
|
|
||||||
|
const compressedData = [
|
||||||
|
'CSA',
|
||||||
|
ipToNumber(ip),
|
||||||
|
candidatesData.map((candidate: WebSocketCandidatesResponse) => ipToNumber(candidate.host)),
|
||||||
|
port, // 端口号
|
||||||
|
Date.now() % 86400000
|
||||||
|
]
|
||||||
|
|
||||||
|
return compressedData
|
||||||
}
|
}
|
||||||
setQrCodeValue(JSON.stringify(connectionInfo))
|
|
||||||
|
const compressedData = optimizeConnectionInfo()
|
||||||
|
const qrCodeValue = JSON.stringify(compressedData)
|
||||||
|
setQrCodeValue(qrCodeValue)
|
||||||
setConnectionPhase('waiting_qr_scan')
|
setConnectionPhase('waiting_qr_scan')
|
||||||
logger.info(`QR code generated: ${ip}:${port} with ${candidates.length} IP candidates`)
|
|
||||||
} else {
|
} else {
|
||||||
setError(t('settings.data.export_to_phone.lan.error.no_ip'))
|
setError(t('settings.data.export_to_phone.lan.error.no_ip'))
|
||||||
setConnectionPhase('error')
|
setConnectionPhase('error')
|
||||||
@@ -286,22 +295,20 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
// 尝试关闭弹窗 - 如果正在传输则显示确认
|
// 尝试关闭弹窗 - 如果正在传输则显示确认
|
||||||
const handleCancel = useCallback(() => {
|
const handleCancel = useCallback(() => {
|
||||||
if (isSending) {
|
if (isSending) {
|
||||||
setShowCloseConfirm(true)
|
window.modal.confirm({
|
||||||
|
title: t('settings.data.export_to_phone.lan.confirm_close_title'),
|
||||||
|
content: t('settings.data.export_to_phone.lan.confirm_close_message'),
|
||||||
|
centered: true,
|
||||||
|
okButtonProps: {
|
||||||
|
danger: true
|
||||||
|
},
|
||||||
|
okText: t('settings.data.export_to_phone.lan.force_close'),
|
||||||
|
onOk: () => setIsOpen(false)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
}
|
}
|
||||||
}, [isSending])
|
}, [isSending, t])
|
||||||
|
|
||||||
// 确认强制关闭
|
|
||||||
const handleForceClose = useCallback(() => {
|
|
||||||
logger.info('Force closing popup during transfer')
|
|
||||||
setIsOpen(false)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// 取消关闭确认
|
|
||||||
const handleCancelClose = useCallback(() => {
|
|
||||||
setShowCloseConfirm(false)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// 清理并关闭
|
// 清理并关闭
|
||||||
const handleClose = useCallback(async () => {
|
const handleClose = useCallback(async () => {
|
||||||
@@ -363,11 +370,13 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
gap: '8px',
|
gap: '8px',
|
||||||
padding: '8px 12px',
|
padding: '5px 12px',
|
||||||
borderRadius: '8px',
|
width: '100%',
|
||||||
backgroundColor: connectionStatusStyles.bg,
|
backgroundColor: connectionStatusStyles.bg,
|
||||||
border: `1px solid ${connectionStatusStyles.border}`
|
border: `1px solid ${connectionStatusStyles.border}`,
|
||||||
|
marginBottom: 10
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: '14px', fontWeight: '500', color: 'var(--color-text)' }}>{connectionStatusText}</span>
|
<span style={{ fontSize: '14px', fontWeight: '500', color: 'var(--color-text)' }}>{connectionStatusText}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -399,7 +408,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
if (!isSending && transferPhase !== 'completed') return null
|
if (!isSending && transferPhase !== 'completed') return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ paddingTop: '8px' }}>
|
<div style={{ paddingTop: '20px' }}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -428,11 +437,9 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Progress
|
<Progress
|
||||||
value={Math.round(sendProgress)}
|
percent={Math.round(sendProgress)}
|
||||||
size="md"
|
status={transferPhase === 'completed' ? 'success' : 'active'}
|
||||||
color={transferPhase === 'completed' ? 'success' : 'primary'}
|
showInfo={false}
|
||||||
showValueLabel={false}
|
|
||||||
aria-label="Send progress"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -475,95 +482,50 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
open={isOpen}
|
||||||
onOpenChange={(open) => {
|
onCancel={handleCancel}
|
||||||
if (!open) {
|
afterClose={handleClose}
|
||||||
handleCancel()
|
title={t('settings.data.export_to_phone.lan.title')}
|
||||||
}
|
centered
|
||||||
}}
|
closable={!isSending}
|
||||||
isDismissable={false}
|
maskClosable={false}
|
||||||
isKeyboardDismissDisabled={false}
|
keyboard={true}
|
||||||
placement="center"
|
footer={null}
|
||||||
onClose={handleClose}>
|
styles={{ body: { paddingBottom: 10 } }}>
|
||||||
<ModalContent>
|
<SettingRow>
|
||||||
{() => (
|
<StatusIndicator />
|
||||||
<>
|
</SettingRow>
|
||||||
<ModalHeader>{t('settings.data.export_to_phone.lan.title')}</ModalHeader>
|
|
||||||
<ModalBody>
|
|
||||||
<SettingRow>
|
|
||||||
<StatusIndicator />
|
|
||||||
</SettingRow>
|
|
||||||
|
|
||||||
<SettingRow>
|
<Alert message={t('settings.data.export_to_phone.lan.content')} type="info" style={{ borderRadius: 0 }} />
|
||||||
<div>{t('settings.data.export_to_phone.lan.content')}</div>
|
|
||||||
</SettingRow>
|
|
||||||
|
|
||||||
<SettingRow style={{ display: 'flex', justifyContent: 'center', minHeight: '180px' }}>
|
<SettingRow style={{ display: 'flex', justifyContent: 'center', minHeight: '180px', marginBlock: 25 }}>
|
||||||
<QRCodeDisplay />
|
<QRCodeDisplay />
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
<SettingRow style={{ display: 'flex', alignItems: 'center' }}>
|
<SettingRow style={{ display: 'flex', alignItems: 'center', marginBlock: 10 }}>
|
||||||
<div style={{ display: 'flex', gap: 10, justifyContent: 'center', width: '100%' }}>
|
<div style={{ display: 'flex', gap: 10, justifyContent: 'center', width: '100%' }}>
|
||||||
<Button color="default" variant="flat" onPress={handleSelectZip} isDisabled={isSending}>
|
<Button onClick={handleSelectZip} disabled={isSending}>
|
||||||
{t('settings.data.export_to_phone.lan.selectZip')}
|
{t('settings.data.export_to_phone.lan.selectZip')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="primary" onPress={handleSendZip} isDisabled={!canSend} isLoading={isSending}>
|
<Button type="primary" onClick={handleSendZip} disabled={!canSend} loading={isSending}>
|
||||||
{transferStatusText || t('settings.data.export_to_phone.lan.sendZip')}
|
{transferStatusText || t('settings.data.export_to_phone.lan.sendZip')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
<SettingHelpText
|
<SettingHelpText
|
||||||
style={{
|
style={{
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
}}>
|
}}>
|
||||||
{selectedFolderPath || t('settings.data.export_to_phone.lan.noZipSelected')}
|
{selectedFolderPath || t('settings.data.export_to_phone.lan.noZipSelected')}
|
||||||
</SettingHelpText>
|
</SettingHelpText>
|
||||||
|
|
||||||
<TransferProgress />
|
<TransferProgress />
|
||||||
<AutoCloseCountdown />
|
<AutoCloseCountdown />
|
||||||
<ErrorDisplay />
|
<ErrorDisplay />
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
{showCloseConfirm && (
|
|
||||||
<ModalFooter>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
width: '100%',
|
|
||||||
gap: '12px',
|
|
||||||
padding: '8px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
backgroundColor: 'var(--color-status-warning)',
|
|
||||||
border: '1px solid var(--color-status-warning)'
|
|
||||||
}}>
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
||||||
<span style={{ fontSize: '20px' }}>⚠️</span>
|
|
||||||
<span style={{ fontSize: '14px', color: 'var(--color-text)', fontWeight: '500' }}>
|
|
||||||
{t('settings.data.export_to_phone.lan.confirm_close_title')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<span style={{ fontSize: '13px', color: 'var(--color-text-2)', marginLeft: '28px' }}>
|
|
||||||
{t('settings.data.export_to_phone.lan.confirm_close_message')}
|
|
||||||
</span>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '8px', marginTop: '4px' }}>
|
|
||||||
<Button size="sm" color="default" variant="flat" onPress={handleCancelClose}>
|
|
||||||
{t('common.cancel')}
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" color="danger" onPress={handleForceClose}>
|
|
||||||
{t('settings.data.export_to_phone.lan.force_close')}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ModalFooter>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { TopView } from '@renderer/components/TopView'
|
||||||
|
import { handleSaveData } from '@renderer/store'
|
||||||
|
import { Button, Modal } from 'antd'
|
||||||
|
import type { ReleaseNoteInfo, UpdateInfo } from 'builder-util-runtime'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import Markdown from 'react-markdown'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('UpdateDialog')
|
||||||
|
|
||||||
|
interface ShowParams {
|
||||||
|
releaseInfo: UpdateInfo | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props extends ShowParams {
|
||||||
|
resolve: (data: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const PopupContainer: React.FC<Props> = ({ releaseInfo, resolve }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [open, setOpen] = useState(true)
|
||||||
|
const [isInstalling, setIsInstalling] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (releaseInfo) {
|
||||||
|
logger.info('Update dialog opened', { version: releaseInfo.version })
|
||||||
|
}
|
||||||
|
}, [releaseInfo])
|
||||||
|
|
||||||
|
const handleInstall = async () => {
|
||||||
|
setIsInstalling(true)
|
||||||
|
try {
|
||||||
|
await handleSaveData()
|
||||||
|
await window.api.quitAndInstall()
|
||||||
|
setOpen(false)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to save data before update', error as Error)
|
||||||
|
setIsInstalling(false)
|
||||||
|
window.toast.error(t('update.saveDataError'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
resolve({})
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateDialogPopup.hide = onCancel
|
||||||
|
|
||||||
|
const releaseNotes = releaseInfo?.releaseNotes
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
<ModalHeaderWrapper>
|
||||||
|
<h3>{t('update.title')}</h3>
|
||||||
|
<p>{t('update.message').replace('{{version}}', releaseInfo?.version || '')}</p>
|
||||||
|
</ModalHeaderWrapper>
|
||||||
|
}
|
||||||
|
open={open}
|
||||||
|
onCancel={onCancel}
|
||||||
|
afterClose={onClose}
|
||||||
|
transitionName="animation-move-down"
|
||||||
|
centered
|
||||||
|
width={720}
|
||||||
|
footer={[
|
||||||
|
<Button key="later" onClick={onCancel} disabled={isInstalling}>
|
||||||
|
{t('update.later')}
|
||||||
|
</Button>,
|
||||||
|
<Button key="install" type="primary" onClick={handleInstall} loading={isInstalling}>
|
||||||
|
{t('update.install')}
|
||||||
|
</Button>
|
||||||
|
]}>
|
||||||
|
<ModalBodyWrapper>
|
||||||
|
<ReleaseNotesWrapper className="markdown">
|
||||||
|
<Markdown>
|
||||||
|
{typeof releaseNotes === 'string'
|
||||||
|
? releaseNotes
|
||||||
|
: Array.isArray(releaseNotes)
|
||||||
|
? releaseNotes
|
||||||
|
.map((note: ReleaseNoteInfo) => note.note)
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n\n')
|
||||||
|
: t('update.noReleaseNotes')}
|
||||||
|
</Markdown>
|
||||||
|
</ReleaseNotesWrapper>
|
||||||
|
</ModalBodyWrapper>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const TopViewKey = 'UpdateDialogPopup'
|
||||||
|
|
||||||
|
export default class UpdateDialogPopup {
|
||||||
|
static topviewId = 0
|
||||||
|
static hide() {
|
||||||
|
TopView.hide(TopViewKey)
|
||||||
|
}
|
||||||
|
static show(props: ShowParams) {
|
||||||
|
return new Promise<any>((resolve) => {
|
||||||
|
TopView.show(
|
||||||
|
<PopupContainer
|
||||||
|
{...props}
|
||||||
|
resolve={(v) => {
|
||||||
|
resolve(v)
|
||||||
|
TopView.hide(TopViewKey)
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
TopViewKey
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalHeaderWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const ModalBodyWrapper = styled.div`
|
||||||
|
max-height: 450px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 12px 0;
|
||||||
|
`
|
||||||
|
|
||||||
|
const ReleaseNotesWrapper = styled.div`
|
||||||
|
background-color: var(--color-bg-2);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
margin: 16px 0 8px 0;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
margin: 8px 0;
|
||||||
|
padding-left: 24px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 2px 6px;
|
||||||
|
background-color: var(--color-bg-3);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 12px;
|
||||||
|
background-color: var(--color-bg-3);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
@@ -1,44 +1,32 @@
|
|||||||
import type { SelectedItemProps } from '@heroui/react'
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Form,
|
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
ModalBody,
|
|
||||||
ModalContent,
|
|
||||||
ModalFooter,
|
|
||||||
ModalHeader,
|
|
||||||
Select,
|
|
||||||
SelectItem,
|
|
||||||
Textarea,
|
|
||||||
useDisclosure
|
|
||||||
} from '@heroui/react'
|
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import type { Selection } from '@react-types/shared'
|
|
||||||
import ClaudeIcon from '@renderer/assets/images/models/claude.png'
|
import ClaudeIcon from '@renderer/assets/images/models/claude.png'
|
||||||
|
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
||||||
|
import { TopView } from '@renderer/components/TopView'
|
||||||
import { permissionModeCards } from '@renderer/config/agent'
|
import { permissionModeCards } from '@renderer/config/agent'
|
||||||
import { agentModelFilter, getModelLogoById } from '@renderer/config/models'
|
|
||||||
import { useAgents } from '@renderer/hooks/agents/useAgents'
|
import { useAgents } from '@renderer/hooks/agents/useAgents'
|
||||||
import { useApiModels } from '@renderer/hooks/agents/useModels'
|
|
||||||
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
||||||
|
import SelectAgentBaseModelButton from '@renderer/pages/home/components/SelectAgentBaseModelButton'
|
||||||
import type {
|
import type {
|
||||||
AddAgentForm,
|
AddAgentForm,
|
||||||
AgentEntity,
|
AgentEntity,
|
||||||
AgentType,
|
AgentType,
|
||||||
|
ApiModel,
|
||||||
BaseAgentForm,
|
BaseAgentForm,
|
||||||
PermissionMode,
|
PermissionMode,
|
||||||
Tool,
|
Tool,
|
||||||
UpdateAgentForm
|
UpdateAgentForm
|
||||||
} from '@renderer/types'
|
} from '@renderer/types'
|
||||||
import { AgentConfigurationSchema, isAgentType } from '@renderer/types'
|
import { AgentConfigurationSchema, isAgentType } from '@renderer/types'
|
||||||
|
import { Avatar, Button, Input, Modal, Select } from 'antd'
|
||||||
import { AlertTriangleIcon } from 'lucide-react'
|
import { AlertTriangleIcon } from 'lucide-react'
|
||||||
import type { ChangeEvent, FormEvent } from 'react'
|
import type { ChangeEvent, FormEvent } from 'react'
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { ErrorBoundary } from '../../ErrorBoundary'
|
import type { BaseOption } from './shared'
|
||||||
import type { BaseOption, ModelOption } from './shared'
|
|
||||||
import { Option, renderOption } from './shared'
|
const { TextArea } = Input
|
||||||
|
|
||||||
const logger = loggerService.withContext('AddAgentPopup')
|
const logger = loggerService.withContext('AddAgentPopup')
|
||||||
|
|
||||||
@@ -48,8 +36,6 @@ interface AgentTypeOption extends BaseOption {
|
|||||||
name: AgentEntity['name']
|
name: AgentEntity['name']
|
||||||
}
|
}
|
||||||
|
|
||||||
type Option = AgentTypeOption | ModelOption
|
|
||||||
|
|
||||||
type AgentWithTools = AgentEntity & { tools?: Tool[] }
|
type AgentWithTools = AgentEntity & { tools?: Tool[] }
|
||||||
|
|
||||||
const buildAgentForm = (existing?: AgentWithTools): BaseAgentForm => ({
|
const buildAgentForm = (existing?: AgentWithTools): BaseAgentForm => ({
|
||||||
@@ -64,58 +50,37 @@ const buildAgentForm = (existing?: AgentWithTools): BaseAgentForm => ({
|
|||||||
configuration: AgentConfigurationSchema.parse(existing?.configuration ?? {})
|
configuration: AgentConfigurationSchema.parse(existing?.configuration ?? {})
|
||||||
})
|
})
|
||||||
|
|
||||||
type Props = {
|
interface ShowParams {
|
||||||
agent?: AgentWithTools
|
agent?: AgentWithTools
|
||||||
isOpen: boolean
|
|
||||||
onClose: () => void
|
|
||||||
afterSubmit?: (a: AgentEntity) => void
|
afterSubmit?: (a: AgentEntity) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
interface Props extends ShowParams {
|
||||||
* Modal component for creating or editing an agent.
|
resolve: (data: any) => void
|
||||||
*
|
}
|
||||||
* Either trigger or isOpen and onClose is given.
|
|
||||||
* @param agent - Optional agent entity for editing mode.
|
const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
|
||||||
* @param isOpen - Optional controlled modal open state. From useDisclosure.
|
|
||||||
* @param onClose - Optional callback when modal closes. From useDisclosure.
|
|
||||||
* @returns Modal component for agent creation/editing
|
|
||||||
*/
|
|
||||||
export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _onClose, afterSubmit }) => {
|
|
||||||
const { isOpen, onClose } = useDisclosure({ isOpen: _isOpen, onClose: _onClose })
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const [open, setOpen] = useState(true)
|
||||||
const loadingRef = useRef(false)
|
const loadingRef = useRef(false)
|
||||||
// const { setTimeoutTimer } = useTimer()
|
|
||||||
const { addAgent } = useAgents()
|
const { addAgent } = useAgents()
|
||||||
const { updateAgent } = useUpdateAgent()
|
const { updateAgent } = useUpdateAgent()
|
||||||
// hard-coded. We only support anthropic for now.
|
|
||||||
const { models } = useApiModels({ providerType: 'anthropic' })
|
|
||||||
const isEditing = (agent?: AgentWithTools) => agent !== undefined
|
const isEditing = (agent?: AgentWithTools) => agent !== undefined
|
||||||
|
|
||||||
const [form, setForm] = useState<BaseAgentForm>(() => buildAgentForm(agent))
|
const [form, setForm] = useState<BaseAgentForm>(() => buildAgentForm(agent))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (open) {
|
||||||
setForm(buildAgentForm(agent))
|
setForm(buildAgentForm(agent))
|
||||||
}
|
}
|
||||||
}, [agent, isOpen])
|
}, [agent, open])
|
||||||
|
|
||||||
const selectedPermissionMode = form.configuration?.permission_mode ?? 'default'
|
const selectedPermissionMode = form.configuration?.permission_mode ?? 'default'
|
||||||
|
|
||||||
const onPermissionModeChange = useCallback((keys: Selection) => {
|
const onPermissionModeChange = useCallback((value: PermissionMode) => {
|
||||||
if (keys === 'all') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const [first] = Array.from(keys)
|
|
||||||
if (!first) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setForm((prev) => {
|
setForm((prev) => {
|
||||||
const parsedConfiguration = AgentConfigurationSchema.parse(prev.configuration ?? {})
|
const parsedConfiguration = AgentConfigurationSchema.parse(prev.configuration ?? {})
|
||||||
const nextMode = first as PermissionMode
|
if (parsedConfiguration.permission_mode === value) {
|
||||||
|
|
||||||
if (parsedConfiguration.permission_mode === nextMode) {
|
|
||||||
if (!prev.configuration) {
|
if (!prev.configuration) {
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
@@ -129,7 +94,7 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
|
|||||||
...prev,
|
...prev,
|
||||||
configuration: {
|
configuration: {
|
||||||
...parsedConfiguration,
|
...parsedConfiguration,
|
||||||
permission_mode: nextMode
|
permission_mode: value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -150,55 +115,57 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
const agentOptions: AgentTypeOption[] = useMemo(
|
const agentOptions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
agentConfig.map(
|
agentConfig.map((option) => ({
|
||||||
(option) =>
|
value: option.key,
|
||||||
({
|
label: (
|
||||||
...option,
|
<OptionWrapper>
|
||||||
rendered: <Option option={option} />
|
<Avatar src={option.avatar} size={24} />
|
||||||
}) as const satisfies SelectedItemProps
|
<span>{option.label}</span>
|
||||||
),
|
</OptionWrapper>
|
||||||
|
)
|
||||||
|
})),
|
||||||
[agentConfig]
|
[agentConfig]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onAgentTypeChange = useCallback(
|
const onAgentTypeChange = useCallback(
|
||||||
(e: ChangeEvent<HTMLSelectElement>) => {
|
(value: AgentType) => {
|
||||||
const prevConfig = agentConfig.find((config) => config.key === form.type)
|
const prevConfig = agentConfig.find((config) => config.key === form.type)
|
||||||
let newName: string | undefined = form.name
|
let newName: string | undefined = form.name
|
||||||
if (prevConfig && prevConfig.name === form.name) {
|
if (prevConfig && prevConfig.name === form.name) {
|
||||||
const newConfig = agentConfig.find((config) => config.key === e.target.value)
|
const newConfig = agentConfig.find((config) => config.key === value)
|
||||||
if (newConfig) {
|
if (newConfig) {
|
||||||
newName = newConfig.name
|
newName = newConfig.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setForm((prev) => ({
|
setForm((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
type: e.target.value as AgentType,
|
type: value,
|
||||||
name: newName
|
name: newName
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
[agentConfig, form.name, form.type]
|
[agentConfig, form.name, form.type]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onNameChange = useCallback((name: string) => {
|
const onNameChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setForm((prev) => ({
|
setForm((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
name
|
name: e.target.value
|
||||||
}))
|
}))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const onDescChange = useCallback((description: string) => {
|
const onDescChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setForm((prev) => ({
|
setForm((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
description
|
description: e.target.value
|
||||||
}))
|
}))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const onInstChange = useCallback((instructions: string) => {
|
const onInstChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setForm((prev) => ({
|
setForm((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
instructions
|
instructions: e.target.value
|
||||||
}))
|
}))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -231,34 +198,36 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
|
|||||||
}))
|
}))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const modelOptions = useMemo(() => {
|
// Create a temporary agentBase object for SelectAgentBaseModelButton
|
||||||
// mocked data. not final version
|
const tempAgentBase: AgentEntity = useMemo(
|
||||||
return (models ?? [])
|
() => ({
|
||||||
.filter((m) =>
|
id: agent?.id ?? 'temp-creating',
|
||||||
agentModelFilter({
|
type: form.type,
|
||||||
id: m.id,
|
name: form.name,
|
||||||
provider: m.provider || '',
|
model: form.model,
|
||||||
name: m.name,
|
accessible_paths: form.accessible_paths.length > 0 ? form.accessible_paths : ['/'],
|
||||||
group: ''
|
allowed_tools: form.allowed_tools ?? [],
|
||||||
})
|
description: form.description,
|
||||||
)
|
instructions: form.instructions,
|
||||||
.map((model) => ({
|
configuration: form.configuration,
|
||||||
type: 'model',
|
created_at: agent?.created_at ?? new Date().toISOString(),
|
||||||
key: model.id,
|
updated_at: agent?.updated_at ?? new Date().toISOString()
|
||||||
label: model.name,
|
}),
|
||||||
avatar: getModelLogoById(model.id),
|
[form, agent?.id, agent?.created_at, agent?.updated_at]
|
||||||
providerId: model.provider,
|
)
|
||||||
providerName: model.provider_name
|
|
||||||
})) satisfies ModelOption[]
|
|
||||||
}, [models])
|
|
||||||
|
|
||||||
const onModelChange = useCallback((e: ChangeEvent<HTMLSelectElement>) => {
|
const handleModelSelect = useCallback(async (model: ApiModel) => {
|
||||||
setForm((prev) => ({
|
setForm((prev) => ({ ...prev, model: model.id }))
|
||||||
...prev,
|
|
||||||
model: e.target.value
|
|
||||||
}))
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
resolve({})
|
||||||
|
}
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
const onSubmit = useCallback(
|
||||||
async (e: FormEvent<HTMLFormElement>) => {
|
async (e: FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -330,9 +299,7 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
|
|||||||
afterSubmit?.(result.data)
|
afterSubmit?.(result.data)
|
||||||
}
|
}
|
||||||
loadingRef.current = false
|
loadingRef.current = false
|
||||||
|
setOpen(false)
|
||||||
// setTimeoutTimer('onCreateAgent', () => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0)
|
|
||||||
onClose()
|
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
form.type,
|
form.type,
|
||||||
@@ -344,7 +311,6 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
|
|||||||
form.allowed_tools,
|
form.allowed_tools,
|
||||||
form.configuration,
|
form.configuration,
|
||||||
agent,
|
agent,
|
||||||
onClose,
|
|
||||||
t,
|
t,
|
||||||
updateAgent,
|
updateAgent,
|
||||||
afterSubmit,
|
afterSubmit,
|
||||||
@@ -352,138 +318,312 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
AgentModalPopup.hide = onCancel
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
title={isEditing(agent) ? t('agent.edit.title') : t('agent.add.title')}
|
||||||
onClose={onClose}
|
open={open}
|
||||||
classNames={{
|
onCancel={onCancel}
|
||||||
base: 'max-h-[90vh]',
|
afterClose={onClose}
|
||||||
wrapper: 'overflow-hidden'
|
transitionName="animation-move-down"
|
||||||
}}>
|
centered
|
||||||
<ModalContent>
|
width={500}
|
||||||
{(onClose) => (
|
footer={null}>
|
||||||
<>
|
<StyledForm onSubmit={onSubmit}>
|
||||||
<ModalHeader>{isEditing(agent) ? t('agent.edit.title') : t('agent.add.title')}</ModalHeader>
|
<FormContent>
|
||||||
<Form onSubmit={onSubmit} className="min-h-0 w-full shrink overflow-auto">
|
<FormRow>
|
||||||
<ModalBody className="min-h-0 w-full flex-1 shrink overflow-auto">
|
<FormItem style={{ flex: 1 }}>
|
||||||
<div className="flex gap-2">
|
<Label>{t('agent.type.label')}</Label>
|
||||||
<Select
|
<Select
|
||||||
isRequired
|
value={form.type}
|
||||||
isDisabled={isEditing(agent)}
|
onChange={onAgentTypeChange}
|
||||||
selectionMode="single"
|
options={agentOptions}
|
||||||
selectedKeys={[form.type]}
|
disabled={isEditing(agent)}
|
||||||
disallowEmptySelection
|
style={{ width: '100%' }}
|
||||||
onChange={onAgentTypeChange}
|
/>
|
||||||
items={agentOptions}
|
</FormItem>
|
||||||
label={t('agent.type.label')}
|
<FormItem style={{ flex: 1 }}>
|
||||||
placeholder={t('agent.add.type.placeholder')}
|
<Label>
|
||||||
renderValue={renderOption}>
|
{t('common.name')} <RequiredMark>*</RequiredMark>
|
||||||
{(option) => (
|
</Label>
|
||||||
<SelectItem key={option.key} textValue={option.label}>
|
<Input value={form.name} onChange={onNameChange} required />
|
||||||
<Option option={option} />
|
</FormItem>
|
||||||
</SelectItem>
|
</FormRow>
|
||||||
)}
|
|
||||||
</Select>
|
<FormItem>
|
||||||
<Input isRequired value={form.name} onValueChange={onNameChange} label={t('common.name')} />
|
<Label>
|
||||||
</div>
|
{t('common.model')} <RequiredMark>*</RequiredMark>
|
||||||
<Select
|
</Label>
|
||||||
isRequired
|
<SelectAgentBaseModelButton
|
||||||
selectionMode="single"
|
agentBase={tempAgentBase}
|
||||||
selectedKeys={form.model ? [form.model] : []}
|
onSelect={handleModelSelect}
|
||||||
disallowEmptySelection
|
fontSize={14}
|
||||||
onChange={onModelChange}
|
avatarSize={24}
|
||||||
items={modelOptions}
|
iconSize={16}
|
||||||
label={t('common.model')}
|
buttonStyle={{
|
||||||
placeholder={t('common.placeholders.select.model')}
|
padding: '8px 12px',
|
||||||
renderValue={renderOption}>
|
width: '100%',
|
||||||
{(option) => (
|
border: '1px solid var(--color-border)',
|
||||||
<SelectItem key={option.key} textValue={option.label}>
|
borderRadius: 6,
|
||||||
<Option option={option} />
|
height: 'auto'
|
||||||
</SelectItem>
|
}}
|
||||||
)}
|
containerClassName="flex items-center justify-between w-full"
|
||||||
</Select>
|
/>
|
||||||
<Select
|
</FormItem>
|
||||||
isRequired
|
|
||||||
selectionMode="single"
|
<FormItem>
|
||||||
selectedKeys={[selectedPermissionMode]}
|
<Label>
|
||||||
onSelectionChange={onPermissionModeChange}
|
{t('agent.settings.tooling.permissionMode.title', 'Permission mode')} <RequiredMark>*</RequiredMark>
|
||||||
label={t('agent.settings.tooling.permissionMode.title', 'Permission mode')}
|
</Label>
|
||||||
placeholder={t('agent.settings.tooling.permissionMode.placeholder', 'Select permission mode')}
|
<Select
|
||||||
description={t(
|
value={selectedPermissionMode}
|
||||||
'agent.settings.tooling.permissionMode.helper',
|
onChange={onPermissionModeChange}
|
||||||
'Choose how the agent handles tool approvals.'
|
style={{ width: '100%' }}
|
||||||
)}
|
placeholder={t('agent.settings.tooling.permissionMode.placeholder', 'Select permission mode')}
|
||||||
items={permissionModeCards}>
|
dropdownStyle={{ minWidth: '500px' }}
|
||||||
{(item) => (
|
optionLabelProp="label">
|
||||||
<SelectItem key={item.mode} textValue={t(item.titleKey, item.titleFallback)}>
|
{permissionModeCards.map((item) => (
|
||||||
<div className="flex flex-col gap-1">
|
<Select.Option key={item.mode} value={item.mode} label={t(item.titleKey, item.titleFallback)}>
|
||||||
<span className="font-medium text-sm">{t(item.titleKey, item.titleFallback)}</span>
|
<PermissionOptionWrapper>
|
||||||
<span className="text-foreground-500 text-xs">
|
<div className="title">{t(item.titleKey, item.titleFallback)}</div>
|
||||||
{t(item.descriptionKey, item.descriptionFallback)}
|
<div className="description">{t(item.descriptionKey, item.descriptionFallback)}</div>
|
||||||
</span>
|
<div className="behavior">{t(item.behaviorKey, item.behaviorFallback)}</div>
|
||||||
<span className="text-foreground-400 text-xs">
|
{item.caution && (
|
||||||
{t(item.behaviorKey, item.behaviorFallback)}
|
<div className="caution">
|
||||||
</span>
|
<AlertTriangleIcon size={12} />
|
||||||
{item.caution ? (
|
{t(
|
||||||
<span className="flex items-center gap-1 text-danger-500 text-xs">
|
'agent.settings.tooling.permissionMode.bypassPermissions.warning',
|
||||||
<AlertTriangleIcon size={12} className="text-danger" />
|
'Use with caution — all tools will run without asking for approval.'
|
||||||
{t(
|
)}
|
||||||
'agent.settings.tooling.permissionMode.bypassPermissions.warning',
|
|
||||||
'Use with caution — all tools will run without asking for approval.'
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
)}
|
||||||
)}
|
</PermissionOptionWrapper>
|
||||||
</Select>
|
</Select.Option>
|
||||||
<div className="space-y-2">
|
))}
|
||||||
<div className="flex items-center justify-between">
|
</Select>
|
||||||
<span className="font-medium text-foreground text-sm">
|
<HelpText>
|
||||||
{t('agent.session.accessible_paths.label')}
|
{t('agent.settings.tooling.permissionMode.helper', 'Choose how the agent handles tool approvals.')}
|
||||||
</span>
|
</HelpText>
|
||||||
<Button size="sm" variant="flat" onPress={addAccessiblePath}>
|
</FormItem>
|
||||||
{t('agent.session.accessible_paths.add')}
|
|
||||||
|
<FormItem>
|
||||||
|
<LabelWithButton>
|
||||||
|
<Label>
|
||||||
|
{t('agent.session.accessible_paths.label')} <RequiredMark>*</RequiredMark>
|
||||||
|
</Label>
|
||||||
|
<Button size="small" onClick={addAccessiblePath}>
|
||||||
|
{t('agent.session.accessible_paths.add')}
|
||||||
|
</Button>
|
||||||
|
</LabelWithButton>
|
||||||
|
{form.accessible_paths.length > 0 ? (
|
||||||
|
<PathList>
|
||||||
|
{form.accessible_paths.map((path) => (
|
||||||
|
<PathItem key={path}>
|
||||||
|
<PathText title={path}>{path}</PathText>
|
||||||
|
<Button size="small" danger onClick={() => removeAccessiblePath(path)}>
|
||||||
|
{t('common.delete')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</PathItem>
|
||||||
{form.accessible_paths.length > 0 ? (
|
))}
|
||||||
<div className="space-y-2">
|
</PathList>
|
||||||
{form.accessible_paths.map((path) => (
|
) : (
|
||||||
<div
|
<EmptyText>{t('agent.session.accessible_paths.empty')}</EmptyText>
|
||||||
key={path}
|
)}
|
||||||
className="flex items-center justify-between gap-2 rounded-medium border border-default-200 px-3 py-2">
|
</FormItem>
|
||||||
<span className="truncate text-sm" title={path}>
|
|
||||||
{path}
|
<FormItem>
|
||||||
</span>
|
<Label>{t('common.prompt')}</Label>
|
||||||
<Button size="sm" variant="light" color="danger" onPress={() => removeAccessiblePath(path)}>
|
<TextArea rows={3} value={form.instructions ?? ''} onChange={onInstChange} />
|
||||||
{t('common.delete')}
|
</FormItem>
|
||||||
</Button>
|
|
||||||
</div>
|
<FormItem>
|
||||||
))}
|
<Label>{t('common.description')}</Label>
|
||||||
</div>
|
<TextArea rows={2} value={form.description ?? ''} onChange={onDescChange} />
|
||||||
) : (
|
</FormItem>
|
||||||
<p className="text-foreground-400 text-sm">{t('agent.session.accessible_paths.empty')}</p>
|
</FormContent>
|
||||||
)}
|
|
||||||
</div>
|
<FormFooter>
|
||||||
<Textarea label={t('common.prompt')} value={form.instructions ?? ''} onValueChange={onInstChange} />
|
<Button onClick={onCancel}>{t('common.close')}</Button>
|
||||||
<Textarea
|
<Button type="primary" htmlType="submit" loading={loadingRef.current}>
|
||||||
label={t('common.description')}
|
{isEditing(agent) ? t('common.confirm') : t('common.add')}
|
||||||
value={form.description ?? ''}
|
</Button>
|
||||||
onValueChange={onDescChange}
|
</FormFooter>
|
||||||
/>
|
</StyledForm>
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter className="w-full">
|
|
||||||
<Button onPress={onClose}>{t('common.close')}</Button>
|
|
||||||
<Button color="primary" type="submit" isLoading={loadingRef.current}>
|
|
||||||
{isEditing(agent) ? t('common.confirm') : t('common.add')}
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</Form>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TopViewKey = 'AgentModalPopup'
|
||||||
|
|
||||||
|
export default class AgentModalPopup {
|
||||||
|
static topviewId = 0
|
||||||
|
static hide() {
|
||||||
|
TopView.hide(TopViewKey)
|
||||||
|
}
|
||||||
|
static show(props: ShowParams) {
|
||||||
|
return new Promise<any>((resolve) => {
|
||||||
|
TopView.show(
|
||||||
|
<PopupContainer
|
||||||
|
{...props}
|
||||||
|
resolve={(v) => {
|
||||||
|
resolve(v)
|
||||||
|
TopView.hide(TopViewKey)
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
TopViewKey
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the old export for backward compatibility during migration
|
||||||
|
export const AgentModal = AgentModalPopup
|
||||||
|
|
||||||
|
const StyledForm = styled.form`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const FormContent = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 8px;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--color-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const FormRow = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const FormItem = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const Label = styled.label`
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
font-weight: 500;
|
||||||
|
`
|
||||||
|
|
||||||
|
const RequiredMark = styled.span`
|
||||||
|
color: #ff4d4f;
|
||||||
|
margin-left: 4px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const HelpText = styled.div`
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
`
|
||||||
|
|
||||||
|
const LabelWithButton = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
`
|
||||||
|
|
||||||
|
const PathList = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const PathItem = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: var(--color-bg-1);
|
||||||
|
`
|
||||||
|
|
||||||
|
const PathText = styled.span`
|
||||||
|
flex: 1;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
`
|
||||||
|
|
||||||
|
const EmptyText = styled.p`
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
margin: 0;
|
||||||
|
`
|
||||||
|
|
||||||
|
const FormFooter = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
`
|
||||||
|
|
||||||
|
const OptionWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const PermissionOptionWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 0;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.behavior {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caution {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #ff4d4f;
|
||||||
|
margin-top: 4px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
background-color: rgba(255, 77, 79, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|||||||
@@ -1,320 +0,0 @@
|
|||||||
import {
|
|
||||||
Button,
|
|
||||||
cn,
|
|
||||||
Form,
|
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
ModalBody,
|
|
||||||
ModalContent,
|
|
||||||
ModalFooter,
|
|
||||||
ModalHeader,
|
|
||||||
Textarea,
|
|
||||||
useDisclosure
|
|
||||||
} from '@heroui/react'
|
|
||||||
import { loggerService } from '@logger'
|
|
||||||
import type { Selection } from '@react-types/shared'
|
|
||||||
import { AllowedToolsSelect } from '@renderer/components/agent'
|
|
||||||
import { useAgent } from '@renderer/hooks/agents/useAgent'
|
|
||||||
import { useSessions } from '@renderer/hooks/agents/useSessions'
|
|
||||||
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
|
||||||
import type {
|
|
||||||
AgentEntity,
|
|
||||||
AgentSessionEntity,
|
|
||||||
BaseSessionForm,
|
|
||||||
CreateSessionForm,
|
|
||||||
Tool,
|
|
||||||
UpdateSessionForm
|
|
||||||
} from '@renderer/types'
|
|
||||||
import type { FormEvent, ReactNode } from 'react'
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
import { ErrorBoundary } from '../../ErrorBoundary'
|
|
||||||
|
|
||||||
const logger = loggerService.withContext('SessionAgentPopup')
|
|
||||||
|
|
||||||
type AgentWithTools = AgentEntity & { tools?: Tool[] }
|
|
||||||
type SessionWithTools = AgentSessionEntity & { tools?: Tool[] }
|
|
||||||
|
|
||||||
const buildSessionForm = (existing?: SessionWithTools, agent?: AgentWithTools): BaseSessionForm => ({
|
|
||||||
name: existing?.name ?? agent?.name ?? 'Claude Code',
|
|
||||||
description: existing?.description ?? agent?.description,
|
|
||||||
instructions: existing?.instructions ?? agent?.instructions,
|
|
||||||
model: existing?.model ?? agent?.model ?? '',
|
|
||||||
accessible_paths: existing?.accessible_paths
|
|
||||||
? [...existing.accessible_paths]
|
|
||||||
: agent?.accessible_paths
|
|
||||||
? [...agent.accessible_paths]
|
|
||||||
: [],
|
|
||||||
allowed_tools: existing?.allowed_tools
|
|
||||||
? [...existing.allowed_tools]
|
|
||||||
: agent?.allowed_tools
|
|
||||||
? [...agent.allowed_tools]
|
|
||||||
: [],
|
|
||||||
mcps: existing?.mcps ? [...existing.mcps] : agent?.mcps ? [...agent.mcps] : []
|
|
||||||
})
|
|
||||||
|
|
||||||
interface BaseProps {
|
|
||||||
agentId: string
|
|
||||||
session?: SessionWithTools
|
|
||||||
onSessionCreated?: (session: AgentSessionEntity) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TriggerProps extends BaseProps {
|
|
||||||
trigger: { content: ReactNode; className?: string }
|
|
||||||
isOpen?: never
|
|
||||||
onClose?: never
|
|
||||||
}
|
|
||||||
|
|
||||||
interface StateProps extends BaseProps {
|
|
||||||
trigger?: never
|
|
||||||
isOpen: boolean
|
|
||||||
onClose: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = TriggerProps | StateProps
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modal component for creating or editing a Session.
|
|
||||||
* @deprecated may as a reference when migrating to v2
|
|
||||||
*
|
|
||||||
* Either trigger or isOpen and onClose is given.
|
|
||||||
* @param agentId - The ID of agent which the session is related.
|
|
||||||
* @param session - Optional session entity for editing mode.
|
|
||||||
* @param trigger - Optional trigger element that opens the modal. It MUST propagate the click event to trigger the modal.
|
|
||||||
* @param isOpen - Optional controlled modal open state. From useDisclosure.
|
|
||||||
* @param onClose - Optional callback when modal closes. From useDisclosure.
|
|
||||||
* @returns Modal component for agent creation/editing
|
|
||||||
*/
|
|
||||||
export const SessionModal: React.FC<Props> = ({
|
|
||||||
agentId,
|
|
||||||
session,
|
|
||||||
trigger,
|
|
||||||
isOpen: _isOpen,
|
|
||||||
onClose: _onClose,
|
|
||||||
onSessionCreated
|
|
||||||
}) => {
|
|
||||||
const { isOpen, onClose, onOpen } = useDisclosure({ isOpen: _isOpen, onClose: _onClose })
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const loadingRef = useRef(false)
|
|
||||||
// const { setTimeoutTimer } = useTimer()
|
|
||||||
const { createSession } = useSessions(agentId)
|
|
||||||
const { updateSession } = useUpdateSession(agentId)
|
|
||||||
const { agent } = useAgent(agentId)
|
|
||||||
const isEditing = (session?: AgentSessionEntity) => session !== undefined
|
|
||||||
|
|
||||||
const [form, setForm] = useState<BaseSessionForm>(() => buildSessionForm(session, agent ?? undefined))
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isOpen) {
|
|
||||||
setForm(buildSessionForm(session, agent ?? undefined))
|
|
||||||
}
|
|
||||||
}, [session, agent, isOpen])
|
|
||||||
|
|
||||||
const availableTools = useMemo(() => session?.tools ?? agent?.tools ?? [], [agent?.tools, session?.tools])
|
|
||||||
const selectedToolKeys = useMemo(() => new Set(form.allowed_tools ?? []), [form.allowed_tools])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!availableTools.length) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setForm((prev) => {
|
|
||||||
const allowed = prev.allowed_tools ?? []
|
|
||||||
const validTools = allowed.filter((id) => availableTools.some((tool) => tool.id === id))
|
|
||||||
if (validTools.length === allowed.length) {
|
|
||||||
return prev
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
allowed_tools: validTools
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [availableTools])
|
|
||||||
|
|
||||||
const onNameChange = useCallback((name: string) => {
|
|
||||||
setForm((prev) => ({
|
|
||||||
...prev,
|
|
||||||
name
|
|
||||||
}))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onDescChange = useCallback((description: string) => {
|
|
||||||
setForm((prev) => ({
|
|
||||||
...prev,
|
|
||||||
description
|
|
||||||
}))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onInstChange = useCallback((instructions: string) => {
|
|
||||||
setForm((prev) => ({
|
|
||||||
...prev,
|
|
||||||
instructions
|
|
||||||
}))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onAllowedToolsChange = useCallback(
|
|
||||||
(keys: Selection) => {
|
|
||||||
setForm((prev) => {
|
|
||||||
const existing = prev.allowed_tools ?? []
|
|
||||||
if (keys === 'all') {
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
allowed_tools: availableTools.map((tool) => tool.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const next = Array.from(keys).map(String)
|
|
||||||
const filtered = availableTools.length
|
|
||||||
? next.filter((id) => availableTools.some((tool) => tool.id === id))
|
|
||||||
: next
|
|
||||||
|
|
||||||
if (existing.length === filtered.length && existing.every((id) => filtered.includes(id))) {
|
|
||||||
return prev
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
allowed_tools: filtered
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
[availableTools]
|
|
||||||
)
|
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
|
||||||
async (e: FormEvent<HTMLFormElement>) => {
|
|
||||||
e.preventDefault()
|
|
||||||
if (loadingRef.current) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingRef.current = true
|
|
||||||
|
|
||||||
// Additional validation check besides native HTML validation to ensure security
|
|
||||||
if (!form.model) {
|
|
||||||
window.toast.error(t('error.model.not_exists'))
|
|
||||||
loadingRef.current = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (form.accessible_paths.length === 0) {
|
|
||||||
window.toast.error(t('agent.session.accessible_paths.error.at_least_one'))
|
|
||||||
loadingRef.current = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (isEditing(session)) {
|
|
||||||
if (!session) {
|
|
||||||
throw new Error('Agent is required for editing mode')
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatePayload = {
|
|
||||||
id: session.id,
|
|
||||||
name: form.name,
|
|
||||||
description: form.description,
|
|
||||||
instructions: form.instructions,
|
|
||||||
model: form.model,
|
|
||||||
accessible_paths: [...form.accessible_paths],
|
|
||||||
allowed_tools: [...(form.allowed_tools ?? [])],
|
|
||||||
mcps: [...(form.mcps ?? [])]
|
|
||||||
} satisfies UpdateSessionForm
|
|
||||||
|
|
||||||
updateSession(updatePayload)
|
|
||||||
logger.debug('Updated agent', updatePayload)
|
|
||||||
} else {
|
|
||||||
const newSession = {
|
|
||||||
name: form.name,
|
|
||||||
description: form.description,
|
|
||||||
instructions: form.instructions,
|
|
||||||
model: form.model,
|
|
||||||
accessible_paths: [...form.accessible_paths],
|
|
||||||
allowed_tools: [...(form.allowed_tools ?? [])],
|
|
||||||
mcps: [...(form.mcps ?? [])]
|
|
||||||
} satisfies CreateSessionForm
|
|
||||||
const createdSession = await createSession(newSession)
|
|
||||||
if (createdSession) {
|
|
||||||
onSessionCreated?.(createdSession)
|
|
||||||
}
|
|
||||||
logger.debug('Added agent', newSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setTimeoutTimer('onCreateAgent', () => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0)
|
|
||||||
onClose()
|
|
||||||
} finally {
|
|
||||||
loadingRef.current = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
form.model,
|
|
||||||
form.name,
|
|
||||||
form.description,
|
|
||||||
form.instructions,
|
|
||||||
form.accessible_paths,
|
|
||||||
form.allowed_tools,
|
|
||||||
form.mcps,
|
|
||||||
session,
|
|
||||||
onClose,
|
|
||||||
onSessionCreated,
|
|
||||||
t,
|
|
||||||
updateSession,
|
|
||||||
createSession
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ErrorBoundary>
|
|
||||||
{/* NOTE: Hero UI Modal Pattern: Combine the Button and Modal components into a single
|
|
||||||
encapsulated component. This is because the Modal component needs to bind the onOpen
|
|
||||||
event handler to the Button for proper focus management.
|
|
||||||
|
|
||||||
Or just use external isOpen/onOpen/onClose to control modal state.
|
|
||||||
*/}
|
|
||||||
|
|
||||||
{trigger && (
|
|
||||||
<div
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
onOpen()
|
|
||||||
}}
|
|
||||||
className={cn('w-full', trigger.className)}>
|
|
||||||
{trigger.content}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Modal isOpen={isOpen} onClose={onClose}>
|
|
||||||
<ModalContent>
|
|
||||||
{(onClose) => (
|
|
||||||
<>
|
|
||||||
<ModalHeader>
|
|
||||||
{isEditing(session) ? t('agent.session.edit.title') : t('agent.session.add.title')}
|
|
||||||
</ModalHeader>
|
|
||||||
<Form onSubmit={onSubmit} className="w-full">
|
|
||||||
<ModalBody className="w-full">
|
|
||||||
<Input isRequired value={form.name} onValueChange={onNameChange} label={t('common.name')} />
|
|
||||||
<Textarea
|
|
||||||
label={t('common.description')}
|
|
||||||
value={form.description ?? ''}
|
|
||||||
onValueChange={onDescChange}
|
|
||||||
/>
|
|
||||||
<AllowedToolsSelect
|
|
||||||
items={availableTools}
|
|
||||||
selectedKeys={selectedToolKeys}
|
|
||||||
onSelectionChange={onAllowedToolsChange}
|
|
||||||
/>
|
|
||||||
<Textarea label={t('common.prompt')} value={form.instructions ?? ''} onValueChange={onInstChange} />
|
|
||||||
</ModalBody>
|
|
||||||
<ModalFooter className="w-full">
|
|
||||||
<Button onPress={onClose}>{t('common.close')}</Button>
|
|
||||||
<Button color="primary" type="submit" isLoading={loadingRef.current}>
|
|
||||||
{isEditing(session) ? t('common.confirm') : t('common.add')}
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</Form>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
</ErrorBoundary>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,3 @@
|
|||||||
import type { SelectedItemProps, SelectedItems } from '@heroui/react'
|
|
||||||
import { Avatar } from '@heroui/react'
|
|
||||||
import { getProviderLabel } from '@renderer/i18n/label'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
export interface BaseOption {
|
export interface BaseOption {
|
||||||
type: 'type' | 'model'
|
type: 'type' | 'model'
|
||||||
key: string
|
key: string
|
||||||
@@ -10,43 +5,3 @@ export interface BaseOption {
|
|||||||
// img src
|
// img src
|
||||||
avatar?: string
|
avatar?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelOption extends BaseOption {
|
|
||||||
providerId?: string
|
|
||||||
providerName?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isModelOption(option: BaseOption): option is ModelOption {
|
|
||||||
return option.type === 'model'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Item = ({ item }: { item: SelectedItemProps<BaseOption> }) => <Option option={item.data} />
|
|
||||||
|
|
||||||
export const renderOption = (items: SelectedItems<BaseOption>) =>
|
|
||||||
items.map((item) => <Item key={item.key} item={item} />)
|
|
||||||
|
|
||||||
export const Option = ({ option }: { option?: BaseOption | null }) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
if (!option) {
|
|
||||||
return (
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Avatar name="?" className="h-5 w-5" />
|
|
||||||
{t('common.invalid_value')}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const providerLabel = (() => {
|
|
||||||
if (!isModelOption(option)) return null
|
|
||||||
if (option.providerName) return option.providerName
|
|
||||||
if (option.providerId) return getProviderLabel(option.providerId)
|
|
||||||
return null
|
|
||||||
})()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Avatar src={option.avatar} className="h-5 w-5" />
|
|
||||||
<span className="truncate">{option.label}</span>
|
|
||||||
{providerLabel ? <span className="truncate text-foreground-500">| {providerLabel}</span> : null}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export type QuickPanelListItem = {
|
|||||||
isSelected?: boolean
|
isSelected?: boolean
|
||||||
isMenu?: boolean
|
isMenu?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
hidden?: boolean
|
||||||
/**
|
/**
|
||||||
* 固定显示项:不参与过滤,始终出现在列表顶部。
|
* 固定显示项:不参与过滤,始终出现在列表顶部。
|
||||||
* 例如“清除”按钮可设置为 alwaysVisible,从而在有匹配项时始终可见;
|
* 例如“清除”按钮可设置为 alwaysVisible,从而在有匹配项时始终可见;
|
||||||
|
|||||||
@@ -143,7 +143,8 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
|
|||||||
prevSymbolRef.current = ctx.symbol
|
prevSymbolRef.current = ctx.symbol
|
||||||
|
|
||||||
// 固定项置顶 + 过滤后的普通项
|
// 固定项置顶 + 过滤后的普通项
|
||||||
return [...pinnedItems, ...filteredNormalItems]
|
const pinnedFiltered = [...pinnedItems, ...filteredNormalItems]
|
||||||
|
return pinnedFiltered.filter((item) => !item.hidden)
|
||||||
}, [ctx.isVisible, ctx.symbol, ctx.list, searchText])
|
}, [ctx.isVisible, ctx.symbol, ctx.list, searchText])
|
||||||
|
|
||||||
const canForwardAndBackward = useMemo(() => {
|
const canForwardAndBackward = useMemo(() => {
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { ToastProvider } from '@heroui/toast'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { createPortal } from 'react-dom'
|
|
||||||
|
|
||||||
export const ToastPortal = () => {
|
|
||||||
const [mounted, setMounted] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true)
|
|
||||||
return () => setMounted(false)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
if (!mounted) return null
|
|
||||||
|
|
||||||
return createPortal(
|
|
||||||
<ToastProvider
|
|
||||||
placement="top-center"
|
|
||||||
regionProps={{
|
|
||||||
className: 'z-[1001]'
|
|
||||||
}}
|
|
||||||
toastOffset={20}
|
|
||||||
toastProps={{
|
|
||||||
timeout: 3000,
|
|
||||||
classNames: {
|
|
||||||
// This setting causes the 'hero-toast' class to be applied twice to the toast element. This is weird and I don't know why, but it works.
|
|
||||||
base: 'hero-toast'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>,
|
|
||||||
document.body
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -2,12 +2,12 @@
|
|||||||
import TopViewMinappContainer from '@renderer/components/MinApp/TopViewMinappContainer'
|
import TopViewMinappContainer from '@renderer/components/MinApp/TopViewMinappContainer'
|
||||||
import { useAppInit } from '@renderer/hooks/useAppInit'
|
import { useAppInit } from '@renderer/hooks/useAppInit'
|
||||||
import { useShortcuts } from '@renderer/hooks/useShortcuts'
|
import { useShortcuts } from '@renderer/hooks/useShortcuts'
|
||||||
import { Modal } from 'antd'
|
import { message, Modal } from 'antd'
|
||||||
import type { PropsWithChildren } from 'react'
|
import type { PropsWithChildren } from 'react'
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
import { Box } from '../Layout'
|
import { Box } from '../Layout'
|
||||||
import { getToastUtilities } from './toast'
|
import { getToastUtilities, initMessageApi } from './toast'
|
||||||
|
|
||||||
let onPop = () => {}
|
let onPop = () => {}
|
||||||
let onShow = ({ element, id }: { element: React.FC | React.ReactNode; id: string }) => {
|
let onShow = ({ element, id }: { element: React.FC | React.ReactNode; id: string }) => {
|
||||||
@@ -36,6 +36,7 @@ const TopViewContainer: React.FC<Props> = ({ children }) => {
|
|||||||
elementsRef.current = elements
|
elementsRef.current = elements
|
||||||
|
|
||||||
const [modal, modalContextHolder] = Modal.useModal()
|
const [modal, modalContextHolder] = Modal.useModal()
|
||||||
|
const [messageApi, messageContextHolder] = message.useMessage()
|
||||||
const { shortcuts } = useShortcuts()
|
const { shortcuts } = useShortcuts()
|
||||||
const enableQuitFullScreen = shortcuts.find((item) => item.key === 'exit_fullscreen')?.enabled
|
const enableQuitFullScreen = shortcuts.find((item) => item.key === 'exit_fullscreen')?.enabled
|
||||||
|
|
||||||
@@ -43,8 +44,9 @@ const TopViewContainer: React.FC<Props> = ({ children }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.modal = modal
|
window.modal = modal
|
||||||
|
initMessageApi(messageApi)
|
||||||
window.toast = getToastUtilities()
|
window.toast = getToastUtilities()
|
||||||
}, [modal])
|
}, [messageApi, modal])
|
||||||
|
|
||||||
onPop = () => {
|
onPop = () => {
|
||||||
const views = [...elementsRef.current]
|
const views = [...elementsRef.current]
|
||||||
@@ -97,6 +99,7 @@ const TopViewContainer: React.FC<Props> = ({ children }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{children}
|
{children}
|
||||||
|
{messageContextHolder}
|
||||||
{modalContextHolder}
|
{modalContextHolder}
|
||||||
<TopViewMinappContainer />
|
<TopViewMinappContainer />
|
||||||
{elements.map(({ element: Element, id }) => (
|
{elements.map(({ element: Element, id }) => (
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
import { addToast, closeAll, closeToast, getToastQueue, isToastClosing } from '@heroui/toast'
|
|
||||||
import type { RequireSome } from '@renderer/types'
|
|
||||||
|
|
||||||
type AddToastProps = Parameters<typeof addToast>[0]
|
|
||||||
type ToastPropsColored = Omit<AddToastProps, 'color'>
|
|
||||||
|
|
||||||
const createToast = (color: 'danger' | 'success' | 'warning' | 'default') => {
|
|
||||||
return (arg: ToastPropsColored | string): string | null => {
|
|
||||||
if (typeof arg === 'string') {
|
|
||||||
return addToast({ color, title: arg })
|
|
||||||
} else {
|
|
||||||
return addToast({ color, ...arg })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// syntatic sugar, oh yeah
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display an error toast notification with red color
|
|
||||||
* @param arg - Toast content (string) or toast options object
|
|
||||||
* @returns Toast ID or null
|
|
||||||
*/
|
|
||||||
export const error = createToast('danger')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a success toast notification with green color
|
|
||||||
* @param arg - Toast content (string) or toast options object
|
|
||||||
* @returns Toast ID or null
|
|
||||||
*/
|
|
||||||
export const success = createToast('success')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a warning toast notification with yellow color
|
|
||||||
* @param arg - Toast content (string) or toast options object
|
|
||||||
* @returns Toast ID or null
|
|
||||||
*/
|
|
||||||
export const warning = createToast('warning')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display an info toast notification with default color
|
|
||||||
* @param arg - Toast content (string) or toast options object
|
|
||||||
* @returns Toast ID or null
|
|
||||||
*/
|
|
||||||
export const info = createToast('default')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a loading toast notification that resolves with a promise
|
|
||||||
* @param args - Toast options object containing a promise to resolve
|
|
||||||
* @returns Toast ID or null
|
|
||||||
*/
|
|
||||||
export const loading = (args: RequireSome<AddToastProps, 'promise'>) => {
|
|
||||||
// Disappear immediately by default
|
|
||||||
if (args.timeout === undefined) {
|
|
||||||
args.timeout = 1
|
|
||||||
}
|
|
||||||
return addToast(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getToastUtilities = () =>
|
|
||||||
({
|
|
||||||
getToastQueue,
|
|
||||||
addToast,
|
|
||||||
closeToast,
|
|
||||||
closeAll,
|
|
||||||
isToastClosing,
|
|
||||||
error,
|
|
||||||
success,
|
|
||||||
warning,
|
|
||||||
info,
|
|
||||||
loading
|
|
||||||
}) as const
|
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
import type { RequireSome } from '@renderer/types'
|
||||||
|
import { message as antdMessage } from 'antd'
|
||||||
|
import type { MessageInstance } from 'antd/es/message/interface'
|
||||||
|
import type React from 'react'
|
||||||
|
|
||||||
|
// Global message instance for static usage
|
||||||
|
let messageApi: MessageInstance | null = null
|
||||||
|
|
||||||
|
// Initialize message API - should be called once the App component is mounted
|
||||||
|
export const initMessageApi = (api: MessageInstance) => {
|
||||||
|
messageApi = api
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get message API instance
|
||||||
|
const getMessageApi = (): MessageInstance => {
|
||||||
|
if (!messageApi) {
|
||||||
|
// Fallback to static method if hook API is not available
|
||||||
|
return antdMessage
|
||||||
|
}
|
||||||
|
return messageApi
|
||||||
|
}
|
||||||
|
|
||||||
|
type ToastColor = 'danger' | 'success' | 'warning' | 'default'
|
||||||
|
type MessageType = 'error' | 'success' | 'warning' | 'info'
|
||||||
|
|
||||||
|
interface ToastConfig {
|
||||||
|
title?: React.ReactNode
|
||||||
|
icon?: React.ReactNode
|
||||||
|
description?: React.ReactNode
|
||||||
|
timeout?: number
|
||||||
|
key?: string | number
|
||||||
|
className?: string
|
||||||
|
style?: React.CSSProperties
|
||||||
|
onClick?: () => void
|
||||||
|
onClose?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoadingToastConfig extends ToastConfig {
|
||||||
|
promise: Promise<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
const colorToType = (color: ToastColor): MessageType => {
|
||||||
|
switch (color) {
|
||||||
|
case 'danger':
|
||||||
|
return 'error'
|
||||||
|
case 'success':
|
||||||
|
return 'success'
|
||||||
|
case 'warning':
|
||||||
|
return 'warning'
|
||||||
|
case 'default':
|
||||||
|
return 'info'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toast content component
|
||||||
|
const ToastContent: React.FC<{ title?: React.ReactNode; description?: React.ReactNode; icon?: React.ReactNode }> = ({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
icon
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
{(icon || title) && (
|
||||||
|
<div className="flex items-center gap-2 font-semibold">
|
||||||
|
{icon}
|
||||||
|
{title}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{description && <div className="text-sm">{description}</div>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const createToast = (color: ToastColor) => {
|
||||||
|
return (arg: ToastConfig | string): string | null => {
|
||||||
|
const api = getMessageApi()
|
||||||
|
const type = colorToType(color) as 'error' | 'success' | 'warning' | 'info'
|
||||||
|
|
||||||
|
if (typeof arg === 'string') {
|
||||||
|
// antd message methods return a function to close the message
|
||||||
|
api[type](arg)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, description, icon, timeout, ...restConfig } = arg
|
||||||
|
|
||||||
|
// Convert timeout from milliseconds to seconds (antd uses seconds)
|
||||||
|
const duration = timeout !== undefined ? timeout / 1000 : 3
|
||||||
|
|
||||||
|
return (
|
||||||
|
(api.open({
|
||||||
|
type: type as 'error' | 'success' | 'warning' | 'info',
|
||||||
|
content: <ToastContent title={title} description={description} icon={icon} />,
|
||||||
|
duration,
|
||||||
|
...restConfig
|
||||||
|
}) as any) || null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display an error toast notification with red color
|
||||||
|
* @param arg - Toast content (string) or toast options object
|
||||||
|
* @returns Toast ID or null
|
||||||
|
*/
|
||||||
|
export const error = createToast('danger')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a success toast notification with green color
|
||||||
|
* @param arg - Toast content (string) or toast options object
|
||||||
|
* @returns Toast ID or null
|
||||||
|
*/
|
||||||
|
export const success = createToast('success')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a warning toast notification with yellow color
|
||||||
|
* @param arg - Toast content (string) or toast options object
|
||||||
|
* @returns Toast ID or null
|
||||||
|
*/
|
||||||
|
export const warning = createToast('warning')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display an info toast notification with default color
|
||||||
|
* @param arg - Toast content (string) or toast options object
|
||||||
|
* @returns Toast ID or null
|
||||||
|
*/
|
||||||
|
export const info = createToast('default')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a loading toast notification that resolves with a promise
|
||||||
|
* @param args - Toast options object containing a promise to resolve
|
||||||
|
*/
|
||||||
|
export const loading = (args: RequireSome<LoadingToastConfig, 'promise'>): string | null => {
|
||||||
|
const api = getMessageApi()
|
||||||
|
const { title, description, icon, promise, timeout, ...restConfig } = args
|
||||||
|
|
||||||
|
// Generate unique key for this loading message
|
||||||
|
const key = args.key || `loading-${Date.now()}-${Math.random()}`
|
||||||
|
|
||||||
|
// Show loading message
|
||||||
|
api.loading({
|
||||||
|
content: <ToastContent title={title || 'Loading...'} description={description} icon={icon} />,
|
||||||
|
duration: 0, // Don't auto-close
|
||||||
|
key,
|
||||||
|
...restConfig
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle promise resolution
|
||||||
|
promise
|
||||||
|
.then((result) => {
|
||||||
|
api.success({
|
||||||
|
content: <ToastContent title={title || 'Success'} description={description} />,
|
||||||
|
duration: timeout !== undefined ? timeout / 1000 : 2,
|
||||||
|
key,
|
||||||
|
...restConfig
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
api.error({
|
||||||
|
content: (
|
||||||
|
<ToastContent title={title || 'Error'} description={err?.message || description || 'An error occurred'} />
|
||||||
|
),
|
||||||
|
duration: timeout !== undefined ? timeout / 1000 : 3,
|
||||||
|
key,
|
||||||
|
...restConfig
|
||||||
|
})
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
|
||||||
|
return key as string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a toast notification
|
||||||
|
* @param config - Toast configuration object
|
||||||
|
* @returns Toast ID or null
|
||||||
|
*/
|
||||||
|
export const addToast = (config: ToastConfig) => info(config)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close a specific toast notification by its key
|
||||||
|
* @param key - Toast key (string)
|
||||||
|
*/
|
||||||
|
export const closeToast = (key: string) => {
|
||||||
|
getMessageApi().destroy(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close all toast notifications
|
||||||
|
*/
|
||||||
|
export const closeAll = () => {
|
||||||
|
getMessageApi().destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stub functions for compatibility with previous toast API
|
||||||
|
* These are no-ops since antd message doesn't expose a queue
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This function is a no-op stub for backward compatibility only.
|
||||||
|
* Antd message doesn't expose a queue. Do not rely on this function.
|
||||||
|
* @returns Empty toast queue stub
|
||||||
|
*/
|
||||||
|
export const getToastQueue = (): any => ({ toasts: [] })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated This function is a no-op stub for backward compatibility only.
|
||||||
|
* Antd message doesn't track closing state. Do not rely on this function.
|
||||||
|
* @param key - Toast key (unused)
|
||||||
|
* @returns Always returns false
|
||||||
|
*/
|
||||||
|
export const isToastClosing = (key?: string): boolean => {
|
||||||
|
key // unused
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getToastUtilities = () =>
|
||||||
|
({
|
||||||
|
getToastQueue,
|
||||||
|
addToast,
|
||||||
|
closeToast,
|
||||||
|
closeAll,
|
||||||
|
isToastClosing,
|
||||||
|
error,
|
||||||
|
success,
|
||||||
|
warning,
|
||||||
|
info,
|
||||||
|
loading
|
||||||
|
}) as const
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, ScrollShadow } from '@heroui/react'
|
|
||||||
import { loggerService } from '@logger'
|
|
||||||
import { handleSaveData } from '@renderer/store'
|
|
||||||
import type { ReleaseNoteInfo, UpdateInfo } from 'builder-util-runtime'
|
|
||||||
import React, { useEffect, useState } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import Markdown from 'react-markdown'
|
|
||||||
|
|
||||||
const logger = loggerService.withContext('UpdateDialog')
|
|
||||||
|
|
||||||
interface UpdateDialogProps {
|
|
||||||
isOpen: boolean
|
|
||||||
onClose: () => void
|
|
||||||
releaseInfo: UpdateInfo | null
|
|
||||||
}
|
|
||||||
|
|
||||||
const UpdateDialog: React.FC<UpdateDialogProps> = ({ isOpen, onClose, releaseInfo }) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const [isInstalling, setIsInstalling] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isOpen && releaseInfo) {
|
|
||||||
logger.info('Update dialog opened', { version: releaseInfo.version })
|
|
||||||
}
|
|
||||||
}, [isOpen, releaseInfo])
|
|
||||||
|
|
||||||
const handleInstall = async () => {
|
|
||||||
setIsInstalling(true)
|
|
||||||
try {
|
|
||||||
await handleSaveData()
|
|
||||||
await window.api.quitAndInstall()
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('Failed to save data before update', error as Error)
|
|
||||||
setIsInstalling(false)
|
|
||||||
window.toast.error(t('update.saveDataError'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const releaseNotes = releaseInfo?.releaseNotes
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onClose={onClose}
|
|
||||||
size="2xl"
|
|
||||||
scrollBehavior="inside"
|
|
||||||
classNames={{
|
|
||||||
base: 'max-h-[85vh]',
|
|
||||||
header: 'border-b border-divider',
|
|
||||||
footer: 'border-t border-divider'
|
|
||||||
}}>
|
|
||||||
<ModalContent>
|
|
||||||
{(onModalClose) => (
|
|
||||||
<>
|
|
||||||
<ModalHeader className="flex flex-col gap-1">
|
|
||||||
<h3 className="font-semibold text-lg">{t('update.title')}</h3>
|
|
||||||
<p className="text-default-500 text-small">
|
|
||||||
{t('update.message').replace('{{version}}', releaseInfo?.version || '')}
|
|
||||||
</p>
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
<ScrollShadow className="max-h-[450px]" hideScrollBar>
|
|
||||||
<div className="markdown rounded-lg bg-default-50 p-4">
|
|
||||||
<Markdown>
|
|
||||||
{typeof releaseNotes === 'string'
|
|
||||||
? releaseNotes
|
|
||||||
: Array.isArray(releaseNotes)
|
|
||||||
? releaseNotes
|
|
||||||
.map((note: ReleaseNoteInfo) => note.note)
|
|
||||||
.filter(Boolean)
|
|
||||||
.join('\n\n')
|
|
||||||
: t('update.noReleaseNotes')}
|
|
||||||
</Markdown>
|
|
||||||
</div>
|
|
||||||
</ScrollShadow>
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button variant="light" onPress={onModalClose} isDisabled={isInstalling}>
|
|
||||||
{t('update.later')}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
onPress={async () => {
|
|
||||||
await handleInstall()
|
|
||||||
onModalClose()
|
|
||||||
}}
|
|
||||||
isLoading={isInstalling}>
|
|
||||||
{t('update.install')}
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default UpdateDialog
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import type { SelectedItems, SelectProps } from '@heroui/react'
|
|
||||||
import { Chip, cn, Select, SelectItem } from '@heroui/react'
|
|
||||||
import type { Tool } from '@renderer/types'
|
|
||||||
import React, { useCallback } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
|
|
||||||
export interface AllowedToolsSelectProps extends Omit<SelectProps, 'children'> {
|
|
||||||
items: Tool[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AllowedToolsSelect: React.FC<AllowedToolsSelectProps> = (props) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const { items: availableTools, className, ...rest } = props
|
|
||||||
|
|
||||||
const renderSelectedTools = useCallback((items: SelectedItems<Tool>) => {
|
|
||||||
if (!items.length) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{items.map((item) => (
|
|
||||||
<Chip key={item.key} size="sm" variant="flat" className="max-w-[160px] truncate">
|
|
||||||
{item.data?.name ?? item.textValue ?? item.key}
|
|
||||||
</Chip>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
aria-label={t('agent.session.allowed_tools.label')}
|
|
||||||
selectionMode="multiple"
|
|
||||||
isMultiline
|
|
||||||
label={t('agent.session.allowed_tools.label')}
|
|
||||||
placeholder={t('agent.session.allowed_tools.placeholder')}
|
|
||||||
description={
|
|
||||||
availableTools.length ? t('agent.session.allowed_tools.helper') : t('agent.session.allowed_tools.empty')
|
|
||||||
}
|
|
||||||
isDisabled={!availableTools.length}
|
|
||||||
items={availableTools}
|
|
||||||
renderValue={renderSelectedTools}
|
|
||||||
className={cn('max-w-xl', className)}
|
|
||||||
{...rest}>
|
|
||||||
{(tool) => (
|
|
||||||
<SelectItem key={tool.id} textValue={tool.name}>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<span className="font-medium text-sm">{tool.name}</span>
|
|
||||||
{tool.description ? <span className="text-foreground-500 text-xs">{tool.description}</span> : null}
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
)}
|
|
||||||
</Select>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { AllowedToolsSelect } from './AllowedToolsSelect'
|
|
||||||
@@ -27,6 +27,7 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
|||||||
],
|
],
|
||||||
cherryin: [],
|
cherryin: [],
|
||||||
vertexai: [],
|
vertexai: [],
|
||||||
|
sophnet: [],
|
||||||
'302ai': [
|
'302ai': [
|
||||||
{
|
{
|
||||||
id: 'deepseek-chat',
|
id: 'deepseek-chat',
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import Ph8ProviderLogo from '@renderer/assets/images/providers/ph8.png'
|
|||||||
import PPIOProviderLogo from '@renderer/assets/images/providers/ppio.png'
|
import PPIOProviderLogo from '@renderer/assets/images/providers/ppio.png'
|
||||||
import QiniuProviderLogo from '@renderer/assets/images/providers/qiniu.webp'
|
import QiniuProviderLogo from '@renderer/assets/images/providers/qiniu.webp'
|
||||||
import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png'
|
import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png'
|
||||||
|
import SophnetProviderLogo from '@renderer/assets/images/providers/sophnet.svg'
|
||||||
import StepProviderLogo from '@renderer/assets/images/providers/step.png'
|
import StepProviderLogo from '@renderer/assets/images/providers/step.png'
|
||||||
import TencentCloudProviderLogo from '@renderer/assets/images/providers/tencent-cloud-ti.png'
|
import TencentCloudProviderLogo from '@renderer/assets/images/providers/tencent-cloud-ti.png'
|
||||||
import TogetherProviderLogo from '@renderer/assets/images/providers/together.png'
|
import TogetherProviderLogo from '@renderer/assets/images/providers/together.png'
|
||||||
@@ -246,6 +247,16 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
|
sophnet: {
|
||||||
|
id: 'sophnet',
|
||||||
|
name: 'SophNet',
|
||||||
|
type: 'openai',
|
||||||
|
apiKey: '',
|
||||||
|
apiHost: 'https://www.sophnet.com/api/open-apis/v1',
|
||||||
|
models: [],
|
||||||
|
isSystem: true,
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
ppio: {
|
ppio: {
|
||||||
id: 'ppio',
|
id: 'ppio',
|
||||||
name: 'PPIO',
|
name: 'PPIO',
|
||||||
@@ -729,7 +740,8 @@ export const PROVIDER_LOGO_MAP: AtLeast<SystemProviderId, string> = {
|
|||||||
poe: 'poe', // use svg icon component
|
poe: 'poe', // use svg icon component
|
||||||
aionly: AiOnlyProviderLogo,
|
aionly: AiOnlyProviderLogo,
|
||||||
longcat: LongCatProviderLogo,
|
longcat: LongCatProviderLogo,
|
||||||
huggingface: HuggingfaceProviderLogo
|
huggingface: HuggingfaceProviderLogo,
|
||||||
|
sophnet: SophnetProviderLogo
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export function getProviderLogo(providerId: string) {
|
export function getProviderLogo(providerId: string) {
|
||||||
@@ -808,6 +820,17 @@ export const PROVIDER_URLS: Record<SystemProviderId, ProviderUrls> = {
|
|||||||
models: 'https://ai.burncloud.com/pricing'
|
models: 'https://ai.burncloud.com/pricing'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
sophnet: {
|
||||||
|
api: {
|
||||||
|
url: 'https://www.sophnet.com/api/open-apis/v1'
|
||||||
|
},
|
||||||
|
websites: {
|
||||||
|
official: 'https://sophnet.com',
|
||||||
|
apiKey: 'https://sophnet.com/#/project/key',
|
||||||
|
docs: 'https://sophnet.com/docs/component/introduce.html',
|
||||||
|
models: 'https://sophnet.com/#/model/list'
|
||||||
|
}
|
||||||
|
},
|
||||||
ppio: {
|
ppio: {
|
||||||
api: {
|
api: {
|
||||||
url: 'https://api.ppinfra.com/v3/openai'
|
url: 'https://api.ppinfra.com/v3/openai'
|
||||||
@@ -1463,6 +1486,14 @@ export const isNewApiProvider = (provider: Provider) => {
|
|||||||
return ['new-api', 'cherryin'].includes(provider.id) || provider.type === 'new-api'
|
return ['new-api', 'cherryin'].includes(provider.id) || provider.type === 'new-api'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isCherryAIProvider(provider: Provider): boolean {
|
||||||
|
return provider.id === 'cherryai'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPerplexityProvider(provider: Provider): boolean {
|
||||||
|
return provider.id === 'perplexity'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断是否为 OpenAI 兼容的提供商
|
* 判断是否为 OpenAI 兼容的提供商
|
||||||
* @param {Provider} provider 提供商对象
|
* @param {Provider} provider 提供商对象
|
||||||
@@ -1488,7 +1519,7 @@ export function isGeminiProvider(provider: Provider): boolean {
|
|||||||
return provider.type === 'gemini'
|
return provider.type === 'gemini'
|
||||||
}
|
}
|
||||||
|
|
||||||
const NOT_SUPPORT_API_VERSION_PROVIDERS = ['github', 'copilot'] as const satisfies SystemProviderId[]
|
const NOT_SUPPORT_API_VERSION_PROVIDERS = ['github', 'copilot', 'perplexity'] as const satisfies SystemProviderId[]
|
||||||
|
|
||||||
export const isSupportAPIVersionProvider = (provider: Provider) => {
|
export const isSupportAPIVersionProvider = (provider: Provider) => {
|
||||||
if (isSystemProvider(provider)) {
|
if (isSystemProvider(provider)) {
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { HeroUIProvider } from '@heroui/react'
|
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
|
||||||
|
|
||||||
const AppHeroUIProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
||||||
const { language } = useSettings()
|
|
||||||
return (
|
|
||||||
<HeroUIProvider className="flex h-full w-full flex-1" locale={language}>
|
|
||||||
{children}
|
|
||||||
</HeroUIProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { AppHeroUIProvider as HeroUIProvider }
|
|
||||||
Vendored
+12
-2
@@ -1,12 +1,22 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
import type { PermissionUpdate } from '@anthropic-ai/claude-agent-sdk'
|
import type { PermissionUpdate } from '@anthropic-ai/claude-agent-sdk'
|
||||||
import type { addToast, closeAll, closeToast, getToastQueue, isToastClosing } from '@heroui/toast'
|
|
||||||
import type KeyvStorage from '@kangfenmao/keyv-storage'
|
import type KeyvStorage from '@kangfenmao/keyv-storage'
|
||||||
import type { HookAPI } from 'antd/es/modal/useModal'
|
import type { HookAPI } from 'antd/es/modal/useModal'
|
||||||
import type { NavigateFunction } from 'react-router-dom'
|
import type { NavigateFunction } from 'react-router-dom'
|
||||||
|
|
||||||
import type { error, info, loading, success, warning } from './components/TopView/toast'
|
import type {
|
||||||
|
addToast,
|
||||||
|
closeAll,
|
||||||
|
closeToast,
|
||||||
|
error,
|
||||||
|
getToastQueue,
|
||||||
|
info,
|
||||||
|
isToastClosing,
|
||||||
|
loading,
|
||||||
|
success,
|
||||||
|
warning
|
||||||
|
} from './components/TopView/toast'
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
VITE_RENDERER_INTEGRATED_MODEL: string
|
VITE_RENDERER_INTEGRATED_MODEL: string
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
import { heroui } from '@heroui/react'
|
|
||||||
export default heroui()
|
|
||||||
@@ -41,6 +41,7 @@ export const useAgents = () => {
|
|||||||
// NOTE: We only use the array for now. useUpdateAgent depends on this behavior.
|
// NOTE: We only use the array for now. useUpdateAgent depends on this behavior.
|
||||||
return result.data
|
return result.data
|
||||||
}, [apiServerConfig.enabled, apiServerRunning, client, t])
|
}, [apiServerConfig.enabled, apiServerRunning, client, t])
|
||||||
|
|
||||||
const { data, error, isLoading, mutate } = useSWR(swrKey, fetcher)
|
const { data, error, isLoading, mutate } = useSWR(swrKey, fetcher)
|
||||||
const { chat } = useRuntime()
|
const { chat } = useRuntime()
|
||||||
const { activeAgentId } = chat
|
const { activeAgentId } = chat
|
||||||
|
|||||||
@@ -31,21 +31,24 @@ export const useApiServer = () => {
|
|||||||
try {
|
try {
|
||||||
const status = await window.api.apiServer.getStatus()
|
const status = await window.api.apiServer.getStatus()
|
||||||
setApiServerRunning(status.running)
|
setApiServerRunning(status.running)
|
||||||
|
if (status.running && !apiServerConfig.enabled) {
|
||||||
|
setApiServerEnabled(true)
|
||||||
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error('Failed to check API server status:', error)
|
logger.error('Failed to check API server status:', error)
|
||||||
} finally {
|
} finally {
|
||||||
setApiServerLoading(false)
|
setApiServerLoading(false)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [apiServerConfig.enabled, setApiServerEnabled])
|
||||||
|
|
||||||
const startApiServer = useCallback(async () => {
|
const startApiServer = useCallback(async () => {
|
||||||
if (apiServerLoading) return
|
if (apiServerLoading) return
|
||||||
|
|
||||||
setApiServerLoading(true)
|
setApiServerLoading(true)
|
||||||
try {
|
try {
|
||||||
const result = await window.api.apiServer.start()
|
const result = await window.api.apiServer.start()
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
setApiServerRunning(true)
|
setApiServerRunning(true)
|
||||||
|
setApiServerEnabled(true)
|
||||||
window.toast.success(t('apiServer.messages.startSuccess'))
|
window.toast.success(t('apiServer.messages.startSuccess'))
|
||||||
} else {
|
} else {
|
||||||
window.toast.error(t('apiServer.messages.startError') + result.error)
|
window.toast.error(t('apiServer.messages.startError') + result.error)
|
||||||
@@ -55,16 +58,16 @@ export const useApiServer = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setApiServerLoading(false)
|
setApiServerLoading(false)
|
||||||
}
|
}
|
||||||
}, [apiServerLoading, t])
|
}, [apiServerLoading, setApiServerEnabled, t])
|
||||||
|
|
||||||
const stopApiServer = useCallback(async () => {
|
const stopApiServer = useCallback(async () => {
|
||||||
if (apiServerLoading) return
|
if (apiServerLoading) return
|
||||||
|
|
||||||
setApiServerLoading(true)
|
setApiServerLoading(true)
|
||||||
try {
|
try {
|
||||||
const result = await window.api.apiServer.stop()
|
const result = await window.api.apiServer.stop()
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
setApiServerRunning(false)
|
setApiServerRunning(false)
|
||||||
|
setApiServerEnabled(false)
|
||||||
window.toast.success(t('apiServer.messages.stopSuccess'))
|
window.toast.success(t('apiServer.messages.stopSuccess'))
|
||||||
} else {
|
} else {
|
||||||
window.toast.error(t('apiServer.messages.stopError') + result.error)
|
window.toast.error(t('apiServer.messages.stopError') + result.error)
|
||||||
@@ -74,14 +77,14 @@ export const useApiServer = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setApiServerLoading(false)
|
setApiServerLoading(false)
|
||||||
}
|
}
|
||||||
}, [apiServerLoading, t])
|
}, [apiServerLoading, setApiServerEnabled, t])
|
||||||
|
|
||||||
const restartApiServer = useCallback(async () => {
|
const restartApiServer = useCallback(async () => {
|
||||||
if (apiServerLoading) return
|
if (apiServerLoading) return
|
||||||
|
|
||||||
setApiServerLoading(true)
|
setApiServerLoading(true)
|
||||||
try {
|
try {
|
||||||
const result = await window.api.apiServer.restart()
|
const result = await window.api.apiServer.restart()
|
||||||
|
setApiServerEnabled(result.success)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
await checkApiServerStatus()
|
await checkApiServerStatus()
|
||||||
window.toast.success(t('apiServer.messages.restartSuccess'))
|
window.toast.success(t('apiServer.messages.restartSuccess'))
|
||||||
@@ -93,7 +96,7 @@ export const useApiServer = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setApiServerLoading(false)
|
setApiServerLoading(false)
|
||||||
}
|
}
|
||||||
}, [apiServerLoading, checkApiServerStatus, t])
|
}, [apiServerLoading, checkApiServerStatus, setApiServerEnabled, t])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkApiServerStatus()
|
checkApiServerStatus()
|
||||||
|
|||||||
@@ -221,13 +221,12 @@ export function useAppInit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.electron.ipcRenderer.on(IpcChannel.AgentToolPermission_Request, requestListener)
|
const removeListeners = [
|
||||||
window.electron.ipcRenderer.on(IpcChannel.AgentToolPermission_Result, resultListener)
|
window.electron.ipcRenderer.on(IpcChannel.AgentToolPermission_Request, requestListener),
|
||||||
|
window.electron.ipcRenderer.on(IpcChannel.AgentToolPermission_Result, resultListener)
|
||||||
|
]
|
||||||
|
|
||||||
return () => {
|
return () => removeListeners.forEach((removeListener) => removeListener())
|
||||||
window.electron?.ipcRenderer.removeListener(IpcChannel.AgentToolPermission_Request, requestListener)
|
|
||||||
window.electron?.ipcRenderer.removeListener(IpcChannel.AgentToolPermission_Result, resultListener)
|
|
||||||
}
|
|
||||||
}, [dispatch, t])
|
}, [dispatch, t])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import store, { useAppSelector } from '@renderer/store'
|
import store, { useAppSelector } from '@renderer/store'
|
||||||
import { setAwsBedrockAccessKeyId, setAwsBedrockRegion, setAwsBedrockSecretAccessKey } from '@renderer/store/llm'
|
import {
|
||||||
|
setAwsBedrockAccessKeyId,
|
||||||
|
setAwsBedrockApiKey,
|
||||||
|
setAwsBedrockAuthType,
|
||||||
|
setAwsBedrockRegion,
|
||||||
|
setAwsBedrockSecretAccessKey
|
||||||
|
} from '@renderer/store/llm'
|
||||||
|
import type { AwsBedrockAuthType } from '@renderer/types'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
|
|
||||||
export function useAwsBedrockSettings() {
|
export function useAwsBedrockSettings() {
|
||||||
@@ -8,8 +15,10 @@ export function useAwsBedrockSettings() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...settings,
|
...settings,
|
||||||
|
setAuthType: (authType: AwsBedrockAuthType) => dispatch(setAwsBedrockAuthType(authType)),
|
||||||
setAccessKeyId: (accessKeyId: string) => dispatch(setAwsBedrockAccessKeyId(accessKeyId)),
|
setAccessKeyId: (accessKeyId: string) => dispatch(setAwsBedrockAccessKeyId(accessKeyId)),
|
||||||
setSecretAccessKey: (secretAccessKey: string) => dispatch(setAwsBedrockSecretAccessKey(secretAccessKey)),
|
setSecretAccessKey: (secretAccessKey: string) => dispatch(setAwsBedrockSecretAccessKey(secretAccessKey)),
|
||||||
|
setApiKey: (apiKey: string) => dispatch(setAwsBedrockApiKey(apiKey)),
|
||||||
setRegion: (region: string) => dispatch(setAwsBedrockRegion(region))
|
setRegion: (region: string) => dispatch(setAwsBedrockRegion(region))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,6 +27,10 @@ export function getAwsBedrockSettings() {
|
|||||||
return store.getState().llm.settings.awsBedrock
|
return store.getState().llm.settings.awsBedrock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAwsBedrockAuthType() {
|
||||||
|
return store.getState().llm.settings.awsBedrock.authType
|
||||||
|
}
|
||||||
|
|
||||||
export function getAwsBedrockAccessKeyId() {
|
export function getAwsBedrockAccessKeyId() {
|
||||||
return store.getState().llm.settings.awsBedrock.accessKeyId
|
return store.getState().llm.settings.awsBedrock.accessKeyId
|
||||||
}
|
}
|
||||||
@@ -26,6 +39,10 @@ export function getAwsBedrockSecretAccessKey() {
|
|||||||
return store.getState().llm.settings.awsBedrock.secretAccessKey
|
return store.getState().llm.settings.awsBedrock.secretAccessKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAwsBedrockApiKey() {
|
||||||
|
return store.getState().llm.settings.awsBedrock.apiKey
|
||||||
|
}
|
||||||
|
|
||||||
export function getAwsBedrockRegion() {
|
export function getAwsBedrockRegion() {
|
||||||
return store.getState().llm.settings.awsBedrock.region
|
return store.getState().llm.settings.awsBedrock.region
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,19 +12,7 @@ export default function useUserTheme() {
|
|||||||
const colorPrimary = Color(theme.colorPrimary)
|
const colorPrimary = Color(theme.colorPrimary)
|
||||||
|
|
||||||
document.body.style.setProperty('--color-primary', colorPrimary.toString())
|
document.body.style.setProperty('--color-primary', colorPrimary.toString())
|
||||||
// overwrite hero UI primary color.
|
|
||||||
document.body.style.setProperty('--primary', colorPrimary.toString())
|
document.body.style.setProperty('--primary', colorPrimary.toString())
|
||||||
document.body.style.setProperty('--heroui-primary', colorPrimary.toString())
|
|
||||||
document.body.style.setProperty('--heroui-primary-900', colorPrimary.lighten(0.5).toString())
|
|
||||||
document.body.style.setProperty('--heroui-primary-800', colorPrimary.lighten(0.4).toString())
|
|
||||||
document.body.style.setProperty('--heroui-primary-700', colorPrimary.lighten(0.3).toString())
|
|
||||||
document.body.style.setProperty('--heroui-primary-600', colorPrimary.lighten(0.2).toString())
|
|
||||||
document.body.style.setProperty('--heroui-primary-500', colorPrimary.lighten(0.1).toString())
|
|
||||||
document.body.style.setProperty('--heroui-primary-400', colorPrimary.toString())
|
|
||||||
document.body.style.setProperty('--heroui-primary-300', colorPrimary.darken(0.1).toString())
|
|
||||||
document.body.style.setProperty('--heroui-primary-200', colorPrimary.darken(0.2).toString())
|
|
||||||
document.body.style.setProperty('--heroui-primary-100', colorPrimary.darken(0.3).toString())
|
|
||||||
document.body.style.setProperty('--heroui-primary-50', colorPrimary.darken(0.4).toString())
|
|
||||||
document.body.style.setProperty('--color-primary-soft', colorPrimary.alpha(0.6).toString())
|
document.body.style.setProperty('--color-primary-soft', colorPrimary.alpha(0.6).toString())
|
||||||
document.body.style.setProperty('--color-primary-mute', colorPrimary.alpha(0.3).toString())
|
document.body.style.setProperty('--color-primary-mute', colorPrimary.alpha(0.3).toString())
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ const providerKeyMap = {
|
|||||||
poe: 'provider.poe',
|
poe: 'provider.poe',
|
||||||
aionly: 'provider.aionly',
|
aionly: 'provider.aionly',
|
||||||
longcat: 'provider.longcat',
|
longcat: 'provider.longcat',
|
||||||
huggingface: 'provider.huggingface'
|
huggingface: 'provider.huggingface',
|
||||||
|
sophnet: 'provider.sophnet'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -238,7 +239,7 @@ const paintingsImageSizeOptionsKeyMap = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const getPaintingsImageSizeOptionsLabel = (key: string): string => {
|
export const getPaintingsImageSizeOptionsLabel = (key: string): string => {
|
||||||
return getLabel(paintingsImageSizeOptionsKeyMap, key)
|
return paintingsImageSizeOptionsKeyMap[key] ? getLabel(paintingsImageSizeOptionsKeyMap, key) : key
|
||||||
}
|
}
|
||||||
|
|
||||||
const paintingsQualityOptionsKeyMap = {
|
const paintingsQualityOptionsKeyMap = {
|
||||||
|
|||||||
@@ -339,6 +339,41 @@
|
|||||||
},
|
},
|
||||||
"title": "API Server"
|
"title": "API Server"
|
||||||
},
|
},
|
||||||
|
"appMenu": {
|
||||||
|
"about": "About",
|
||||||
|
"close": "Close Window",
|
||||||
|
"copy": "Copy",
|
||||||
|
"cut": "Cut",
|
||||||
|
"delete": "Delete",
|
||||||
|
"documentation": "Documentation",
|
||||||
|
"edit": "Edit",
|
||||||
|
"feedback": "Feedback",
|
||||||
|
"file": "File",
|
||||||
|
"forceReload": "Force Reload",
|
||||||
|
"front": "Bring All to Front",
|
||||||
|
"help": "Help",
|
||||||
|
"hide": "Hide",
|
||||||
|
"hideOthers": "Hide Others",
|
||||||
|
"minimize": "Minimize",
|
||||||
|
"paste": "Paste",
|
||||||
|
"quit": "Quit",
|
||||||
|
"redo": "Redo",
|
||||||
|
"releases": "Releases",
|
||||||
|
"reload": "Reload",
|
||||||
|
"resetZoom": "Actual Size",
|
||||||
|
"selectAll": "Select All",
|
||||||
|
"services": "Services",
|
||||||
|
"toggleDevTools": "Toggle Developer Tools",
|
||||||
|
"toggleFullscreen": "Toggle Fullscreen",
|
||||||
|
"undo": "Undo",
|
||||||
|
"unhide": "Show All",
|
||||||
|
"view": "View",
|
||||||
|
"website": "Website",
|
||||||
|
"window": "Window",
|
||||||
|
"zoom": "Zoom",
|
||||||
|
"zoomIn": "Zoom In",
|
||||||
|
"zoomOut": "Zoom Out"
|
||||||
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"abbr": "Assistants",
|
"abbr": "Assistants",
|
||||||
"clear": {
|
"clear": {
|
||||||
@@ -2482,6 +2517,7 @@
|
|||||||
"qiniu": "Qiniu AI",
|
"qiniu": "Qiniu AI",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "SiliconFlow",
|
"silicon": "SiliconFlow",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "StepFun",
|
"stepfun": "StepFun",
|
||||||
"tencent-cloud-ti": "Tencent Cloud TI",
|
"tencent-cloud-ti": "Tencent Cloud TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@@ -3765,6 +3801,7 @@
|
|||||||
"description": "Do not enable MCP server functionality",
|
"description": "Do not enable MCP server functionality",
|
||||||
"label": "Disable MCP Server"
|
"label": "Disable MCP Server"
|
||||||
},
|
},
|
||||||
|
"discover": "Discover",
|
||||||
"duplicateName": "A server with this name already exists",
|
"duplicateName": "A server with this name already exists",
|
||||||
"editJson": "Edit JSON",
|
"editJson": "Edit JSON",
|
||||||
"editMcpJson": "Edit MCP Configuration",
|
"editMcpJson": "Edit MCP Configuration",
|
||||||
@@ -3775,6 +3812,10 @@
|
|||||||
"32000": "MCP server failed to start, please check the parameters according to the tutorial",
|
"32000": "MCP server failed to start, please check the parameters according to the tutorial",
|
||||||
"toolNotFound": "Tool {{name}} not found"
|
"toolNotFound": "Tool {{name}} not found"
|
||||||
},
|
},
|
||||||
|
"fetch": {
|
||||||
|
"button": "Fetch Servers",
|
||||||
|
"success": "Successfully fetched MCP servers"
|
||||||
|
},
|
||||||
"findMore": "Find More MCP",
|
"findMore": "Find More MCP",
|
||||||
"headers": "Headers",
|
"headers": "Headers",
|
||||||
"headersTooltip": "Custom headers for HTTP requests",
|
"headersTooltip": "Custom headers for HTTP requests",
|
||||||
@@ -3790,6 +3831,7 @@
|
|||||||
"logoUrl": "Logo URL",
|
"logoUrl": "Logo URL",
|
||||||
"longRunning": "Long Running Mode",
|
"longRunning": "Long Running Mode",
|
||||||
"longRunningTooltip": "When enabled, the server supports long-running tasks. When receiving progress notifications, the timeout will be reset and the maximum execution time will be extended to 10 minutes.",
|
"longRunningTooltip": "When enabled, the server supports long-running tasks. When receiving progress notifications, the timeout will be reset and the maximum execution time will be extended to 10 minutes.",
|
||||||
|
"marketplaces": "Marketplaces",
|
||||||
"missingDependencies": "is Missing, please install it to continue.",
|
"missingDependencies": "is Missing, please install it to continue.",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "Curated MCP Server List",
|
"awesome": "Curated MCP Server List",
|
||||||
@@ -3838,6 +3880,7 @@
|
|||||||
"provider": "Provider",
|
"provider": "Provider",
|
||||||
"providerPlaceholder": "Provider name",
|
"providerPlaceholder": "Provider name",
|
||||||
"providerUrl": "Provider URL",
|
"providerUrl": "Provider URL",
|
||||||
|
"providers": "Providers",
|
||||||
"registry": "Package Registry",
|
"registry": "Package Registry",
|
||||||
"registryDefault": "Default",
|
"registryDefault": "Default",
|
||||||
"registryTooltip": "Choose the registry for package installation to resolve network issues with the default registry.",
|
"registryTooltip": "Choose the registry for package installation to resolve network issues with the default registry.",
|
||||||
@@ -3860,6 +3903,7 @@
|
|||||||
"searchNpx": "Search MCP",
|
"searchNpx": "Search MCP",
|
||||||
"serverPlural": "servers",
|
"serverPlural": "servers",
|
||||||
"serverSingular": "server",
|
"serverSingular": "server",
|
||||||
|
"servers": "MCP Servers",
|
||||||
"sse": "Server-Sent Events (sse)",
|
"sse": "Server-Sent Events (sse)",
|
||||||
"startError": "Start failed",
|
"startError": "Start failed",
|
||||||
"stdio": "Standard Input/Output (stdio)",
|
"stdio": "Standard Input/Output (stdio)",
|
||||||
@@ -4259,6 +4303,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "AWS Access Key ID",
|
"access_key_id": "AWS Access Key ID",
|
||||||
"access_key_id_help": "Your AWS Access Key ID for accessing AWS Bedrock services",
|
"access_key_id_help": "Your AWS Access Key ID for accessing AWS Bedrock services",
|
||||||
|
"api_key": "Bedrock API Key",
|
||||||
|
"api_key_help": "Your AWS Bedrock API Key for authentication",
|
||||||
|
"auth_type": "Authentication Type",
|
||||||
|
"auth_type_api_key": "Bedrock API Key",
|
||||||
|
"auth_type_help": "Choose between IAM credentials or Bedrock API Key authentication",
|
||||||
|
"auth_type_iam": "IAM Credentials",
|
||||||
"description": "AWS Bedrock is Amazon's fully managed foundation model service that supports various advanced large language models",
|
"description": "AWS Bedrock is Amazon's fully managed foundation model service that supports various advanced large language models",
|
||||||
"region": "AWS Region",
|
"region": "AWS Region",
|
||||||
"region_help": "Your AWS service region, e.g., us-east-1",
|
"region_help": "Your AWS service region, e.g., us-east-1",
|
||||||
|
|||||||
@@ -339,6 +339,41 @@
|
|||||||
},
|
},
|
||||||
"title": "API 服务器"
|
"title": "API 服务器"
|
||||||
},
|
},
|
||||||
|
"appMenu": {
|
||||||
|
"about": "关于",
|
||||||
|
"close": "关闭窗口",
|
||||||
|
"copy": "复制",
|
||||||
|
"cut": "剪切",
|
||||||
|
"delete": "删除",
|
||||||
|
"documentation": "文档",
|
||||||
|
"edit": "编辑",
|
||||||
|
"feedback": "反馈",
|
||||||
|
"file": "文件",
|
||||||
|
"forceReload": "强制重新加载",
|
||||||
|
"front": "全部置于顶层",
|
||||||
|
"help": "帮助",
|
||||||
|
"hide": "隐藏",
|
||||||
|
"hideOthers": "隐藏其他",
|
||||||
|
"minimize": "最小化",
|
||||||
|
"paste": "粘贴",
|
||||||
|
"quit": "退出",
|
||||||
|
"redo": "重做",
|
||||||
|
"releases": "版本发布",
|
||||||
|
"reload": "重新加载",
|
||||||
|
"resetZoom": "实际大小",
|
||||||
|
"selectAll": "全选",
|
||||||
|
"services": "服务",
|
||||||
|
"toggleDevTools": "切换开发者工具",
|
||||||
|
"toggleFullscreen": "切换全屏",
|
||||||
|
"undo": "撤销",
|
||||||
|
"unhide": "全部显示",
|
||||||
|
"view": "视图",
|
||||||
|
"website": "网站",
|
||||||
|
"window": "窗口",
|
||||||
|
"zoom": "缩放",
|
||||||
|
"zoomIn": "放大",
|
||||||
|
"zoomOut": "缩小"
|
||||||
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"abbr": "助手",
|
"abbr": "助手",
|
||||||
"clear": {
|
"clear": {
|
||||||
@@ -1611,7 +1646,7 @@
|
|||||||
},
|
},
|
||||||
"assistant": {
|
"assistant": {
|
||||||
"added": {
|
"added": {
|
||||||
"content": "智能体添加成功"
|
"content": "助手添加成功"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"attachments": {
|
"attachments": {
|
||||||
@@ -2482,6 +2517,7 @@
|
|||||||
"qiniu": "七牛云 AI 推理",
|
"qiniu": "七牛云 AI 推理",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "硅基流动",
|
"silicon": "硅基流动",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "阶跃星辰",
|
"stepfun": "阶跃星辰",
|
||||||
"tencent-cloud-ti": "腾讯云 TI",
|
"tencent-cloud-ti": "腾讯云 TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@@ -3765,6 +3801,7 @@
|
|||||||
"description": "不启用 MCP 服务功能",
|
"description": "不启用 MCP 服务功能",
|
||||||
"label": "不使用 MCP 服务器"
|
"label": "不使用 MCP 服务器"
|
||||||
},
|
},
|
||||||
|
"discover": "发现",
|
||||||
"duplicateName": "已存在同名服务器",
|
"duplicateName": "已存在同名服务器",
|
||||||
"editJson": "编辑 JSON",
|
"editJson": "编辑 JSON",
|
||||||
"editMcpJson": "编辑 MCP 配置",
|
"editMcpJson": "编辑 MCP 配置",
|
||||||
@@ -3775,6 +3812,10 @@
|
|||||||
"32000": "MCP 服务器启动失败,请根据教程检查参数是否填写完整",
|
"32000": "MCP 服务器启动失败,请根据教程检查参数是否填写完整",
|
||||||
"toolNotFound": "未找到工具 {{name}}"
|
"toolNotFound": "未找到工具 {{name}}"
|
||||||
},
|
},
|
||||||
|
"fetch": {
|
||||||
|
"button": "获取服务器",
|
||||||
|
"success": "服务器获取成功"
|
||||||
|
},
|
||||||
"findMore": "更多 MCP",
|
"findMore": "更多 MCP",
|
||||||
"headers": "请求头",
|
"headers": "请求头",
|
||||||
"headersTooltip": "HTTP 请求的自定义请求头",
|
"headersTooltip": "HTTP 请求的自定义请求头",
|
||||||
@@ -3790,6 +3831,7 @@
|
|||||||
"logoUrl": "标志网址",
|
"logoUrl": "标志网址",
|
||||||
"longRunning": "长时间运行模式",
|
"longRunning": "长时间运行模式",
|
||||||
"longRunningTooltip": "启用后,服务器支持长时间任务,接收到进度通知时会重置超时计时器,并延长最大超时时间至10分钟",
|
"longRunningTooltip": "启用后,服务器支持长时间任务,接收到进度通知时会重置超时计时器,并延长最大超时时间至10分钟",
|
||||||
|
"marketplaces": "市场",
|
||||||
"missingDependencies": "缺失,请安装它以继续",
|
"missingDependencies": "缺失,请安装它以继续",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "精选的 MCP 服务器列表",
|
"awesome": "精选的 MCP 服务器列表",
|
||||||
@@ -3838,6 +3880,7 @@
|
|||||||
"provider": "提供者",
|
"provider": "提供者",
|
||||||
"providerPlaceholder": "提供者名称",
|
"providerPlaceholder": "提供者名称",
|
||||||
"providerUrl": "提供者网址",
|
"providerUrl": "提供者网址",
|
||||||
|
"providers": "提供商",
|
||||||
"registry": "包管理源",
|
"registry": "包管理源",
|
||||||
"registryDefault": "默认",
|
"registryDefault": "默认",
|
||||||
"registryTooltip": "选择用于安装包的源,以解决默认源的网络问题",
|
"registryTooltip": "选择用于安装包的源,以解决默认源的网络问题",
|
||||||
@@ -3860,6 +3903,7 @@
|
|||||||
"searchNpx": "搜索 MCP",
|
"searchNpx": "搜索 MCP",
|
||||||
"serverPlural": "服务器",
|
"serverPlural": "服务器",
|
||||||
"serverSingular": "服务器",
|
"serverSingular": "服务器",
|
||||||
|
"servers": "MCP 服务器",
|
||||||
"sse": "服务器发送事件 (sse)",
|
"sse": "服务器发送事件 (sse)",
|
||||||
"startError": "启动失败",
|
"startError": "启动失败",
|
||||||
"stdio": "标准输入 / 输出 (stdio)",
|
"stdio": "标准输入 / 输出 (stdio)",
|
||||||
@@ -4259,6 +4303,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "AWS 访问密钥 ID",
|
"access_key_id": "AWS 访问密钥 ID",
|
||||||
"access_key_id_help": "您的 AWS 访问密钥 ID,用于访问 AWS Bedrock 服务",
|
"access_key_id_help": "您的 AWS 访问密钥 ID,用于访问 AWS Bedrock 服务",
|
||||||
|
"api_key": "Bedrock API 密钥",
|
||||||
|
"api_key_help": "您的 AWS Bedrock API 密钥,用于身份验证",
|
||||||
|
"auth_type": "认证方式",
|
||||||
|
"auth_type_api_key": "Bedrock API 密钥",
|
||||||
|
"auth_type_help": "选择使用 IAM 凭证或 Bedrock API 密钥进行身份验证",
|
||||||
|
"auth_type_iam": "IAM 凭证",
|
||||||
"description": "AWS Bedrock 是亚马逊提供的全托管基础模型服务,支持多种先进的大语言模型",
|
"description": "AWS Bedrock 是亚马逊提供的全托管基础模型服务,支持多种先进的大语言模型",
|
||||||
"region": "AWS 区域",
|
"region": "AWS 区域",
|
||||||
"region_help": "您的 AWS 服务区域,例如 us-east-1",
|
"region_help": "您的 AWS 服务区域,例如 us-east-1",
|
||||||
|
|||||||
@@ -339,6 +339,41 @@
|
|||||||
},
|
},
|
||||||
"title": "API 伺服器"
|
"title": "API 伺服器"
|
||||||
},
|
},
|
||||||
|
"appMenu": {
|
||||||
|
"about": "關於",
|
||||||
|
"close": "關閉視窗",
|
||||||
|
"copy": "複製",
|
||||||
|
"cut": "剪下",
|
||||||
|
"delete": "刪除",
|
||||||
|
"documentation": "文件",
|
||||||
|
"edit": "編輯",
|
||||||
|
"feedback": "回饋",
|
||||||
|
"file": "檔案",
|
||||||
|
"forceReload": "強制重新載入",
|
||||||
|
"front": "全部置於頂層",
|
||||||
|
"help": "幫助",
|
||||||
|
"hide": "隱藏",
|
||||||
|
"hideOthers": "隱藏其他",
|
||||||
|
"minimize": "最小化",
|
||||||
|
"paste": "貼上",
|
||||||
|
"quit": "結束",
|
||||||
|
"redo": "重做",
|
||||||
|
"releases": "版本發布",
|
||||||
|
"reload": "重新載入",
|
||||||
|
"resetZoom": "實際大小",
|
||||||
|
"selectAll": "全選",
|
||||||
|
"services": "服務",
|
||||||
|
"toggleDevTools": "切換開發者工具",
|
||||||
|
"toggleFullscreen": "切換全螢幕",
|
||||||
|
"undo": "復原",
|
||||||
|
"unhide": "全部顯示",
|
||||||
|
"view": "檢視",
|
||||||
|
"website": "網站",
|
||||||
|
"window": "視窗",
|
||||||
|
"zoom": "縮放",
|
||||||
|
"zoomIn": "放大",
|
||||||
|
"zoomOut": "縮小"
|
||||||
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"abbr": "助手",
|
"abbr": "助手",
|
||||||
"clear": {
|
"clear": {
|
||||||
@@ -1611,7 +1646,7 @@
|
|||||||
},
|
},
|
||||||
"assistant": {
|
"assistant": {
|
||||||
"added": {
|
"added": {
|
||||||
"content": "智慧代理人新增成功"
|
"content": "助手新增成功"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"attachments": {
|
"attachments": {
|
||||||
@@ -2482,6 +2517,7 @@
|
|||||||
"qiniu": "七牛雲 AI 推理",
|
"qiniu": "七牛雲 AI 推理",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "SiliconFlow",
|
"silicon": "SiliconFlow",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "StepFun",
|
"stepfun": "StepFun",
|
||||||
"tencent-cloud-ti": "騰訊雲 TI",
|
"tencent-cloud-ti": "騰訊雲 TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@@ -3765,6 +3801,7 @@
|
|||||||
"description": "不啟用 MCP 服務功能",
|
"description": "不啟用 MCP 服務功能",
|
||||||
"label": "不使用 MCP 伺服器"
|
"label": "不使用 MCP 伺服器"
|
||||||
},
|
},
|
||||||
|
"discover": "發現",
|
||||||
"duplicateName": "已存在相同名稱的伺服器",
|
"duplicateName": "已存在相同名稱的伺服器",
|
||||||
"editJson": "編輯 JSON",
|
"editJson": "編輯 JSON",
|
||||||
"editMcpJson": "編輯 MCP 配置",
|
"editMcpJson": "編輯 MCP 配置",
|
||||||
@@ -3775,6 +3812,10 @@
|
|||||||
"32000": "MCP 伺服器啟動失敗,請根據教程檢查參數是否填寫完整",
|
"32000": "MCP 伺服器啟動失敗,請根據教程檢查參數是否填寫完整",
|
||||||
"toolNotFound": "未找到工具 {{name}}"
|
"toolNotFound": "未找到工具 {{name}}"
|
||||||
},
|
},
|
||||||
|
"fetch": {
|
||||||
|
"button": "獲取伺服器",
|
||||||
|
"success": "伺服器獲取成功"
|
||||||
|
},
|
||||||
"findMore": "更多 MCP",
|
"findMore": "更多 MCP",
|
||||||
"headers": "請求標頭",
|
"headers": "請求標頭",
|
||||||
"headersTooltip": "HTTP 請求的自定義標頭",
|
"headersTooltip": "HTTP 請求的自定義標頭",
|
||||||
@@ -3790,6 +3831,7 @@
|
|||||||
"logoUrl": "標誌網址",
|
"logoUrl": "標誌網址",
|
||||||
"longRunning": "長時間運行模式",
|
"longRunning": "長時間運行模式",
|
||||||
"longRunningTooltip": "啟用後,伺服器支援長時間任務,接收到進度通知時會重置超時計時器,並延長最大超時時間至10分鐘",
|
"longRunningTooltip": "啟用後,伺服器支援長時間任務,接收到進度通知時會重置超時計時器,並延長最大超時時間至10分鐘",
|
||||||
|
"marketplaces": "市場",
|
||||||
"missingDependencies": "缺失,請安裝它以繼續",
|
"missingDependencies": "缺失,請安裝它以繼續",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "精選的 MCP 伺服器清單",
|
"awesome": "精選的 MCP 伺服器清單",
|
||||||
@@ -3838,6 +3880,7 @@
|
|||||||
"provider": "提供者",
|
"provider": "提供者",
|
||||||
"providerPlaceholder": "提供者名稱",
|
"providerPlaceholder": "提供者名稱",
|
||||||
"providerUrl": "提供者網址",
|
"providerUrl": "提供者網址",
|
||||||
|
"providers": "提供商",
|
||||||
"registry": "套件管理源",
|
"registry": "套件管理源",
|
||||||
"registryDefault": "預設",
|
"registryDefault": "預設",
|
||||||
"registryTooltip": "選擇用於安裝套件的源,以解決預設源的網路問題",
|
"registryTooltip": "選擇用於安裝套件的源,以解決預設源的網路問題",
|
||||||
@@ -3860,6 +3903,7 @@
|
|||||||
"searchNpx": "搜索 MCP",
|
"searchNpx": "搜索 MCP",
|
||||||
"serverPlural": "伺服器",
|
"serverPlural": "伺服器",
|
||||||
"serverSingular": "伺服器",
|
"serverSingular": "伺服器",
|
||||||
|
"servers": "MCP 伺服器",
|
||||||
"sse": "伺服器傳送事件 (sse)",
|
"sse": "伺服器傳送事件 (sse)",
|
||||||
"startError": "啟動失敗",
|
"startError": "啟動失敗",
|
||||||
"stdio": "標準輸入 / 輸出 (stdio)",
|
"stdio": "標準輸入 / 輸出 (stdio)",
|
||||||
@@ -4259,6 +4303,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "AWS 存取密鑰 ID",
|
"access_key_id": "AWS 存取密鑰 ID",
|
||||||
"access_key_id_help": "您的 AWS 存取密鑰 ID,用於存取 AWS Bedrock 服務",
|
"access_key_id_help": "您的 AWS 存取密鑰 ID,用於存取 AWS Bedrock 服務",
|
||||||
|
"api_key": "Bedrock API 金鑰",
|
||||||
|
"api_key_help": "您的 AWS Bedrock API 金鑰,用於身份驗證",
|
||||||
|
"auth_type": "認證方式",
|
||||||
|
"auth_type_api_key": "Bedrock API 金鑰",
|
||||||
|
"auth_type_help": "選擇使用 IAM 憑證或 Bedrock API 金鑰進行身份驗證",
|
||||||
|
"auth_type_iam": "IAM 憑證",
|
||||||
"description": "AWS Bedrock 是亞馬遜提供的全托管基础模型服務,支持多種先進的大語言模型",
|
"description": "AWS Bedrock 是亞馬遜提供的全托管基础模型服務,支持多種先進的大語言模型",
|
||||||
"region": "AWS 區域",
|
"region": "AWS 區域",
|
||||||
"region_help": "您的 AWS 服務區域,例如 us-east-1",
|
"region_help": "您的 AWS 服務區域,例如 us-east-1",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
|
"description": "Bewältigen Sie komplexe Aufgaben mit verschiedenen Werkzeugen",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "Agent hinzufügen fehlgeschlagen",
|
"failed": "Agent hinzufügen fehlgeschlagen",
|
||||||
"invalid_agent": "Ungültiger Agent"
|
"invalid_agent": "Ungültiger Agent"
|
||||||
@@ -338,6 +339,41 @@
|
|||||||
},
|
},
|
||||||
"title": "API-Server"
|
"title": "API-Server"
|
||||||
},
|
},
|
||||||
|
"appMenu": {
|
||||||
|
"about": "Über",
|
||||||
|
"close": "Fenster schließen",
|
||||||
|
"copy": "Kopieren",
|
||||||
|
"cut": "Schneiden",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"documentation": "Dokumentation",
|
||||||
|
"edit": "Bearbeiten",
|
||||||
|
"feedback": "Rückmeldung",
|
||||||
|
"file": "Datei",
|
||||||
|
"forceReload": "Neu laden erzwingen",
|
||||||
|
"front": "Alle in den Vordergrund bringen",
|
||||||
|
"help": "Hilfe",
|
||||||
|
"hide": "Verstecken",
|
||||||
|
"hideOthers": "Andere ausblenden",
|
||||||
|
"minimize": "Minimieren",
|
||||||
|
"paste": "Einfügen",
|
||||||
|
"quit": "Aufhören",
|
||||||
|
"redo": "Wiederholen",
|
||||||
|
"releases": "Veröffentlichungen",
|
||||||
|
"reload": "Neu laden",
|
||||||
|
"resetZoom": "Tatsächliche Größe",
|
||||||
|
"selectAll": "Alle auswählen",
|
||||||
|
"services": "Dienstleistungen",
|
||||||
|
"toggleDevTools": "Entwicklertools ein-/ausblenden",
|
||||||
|
"toggleFullscreen": "Vollbild umschalten",
|
||||||
|
"undo": "Rückgängig machen",
|
||||||
|
"unhide": "Alle anzeigen",
|
||||||
|
"view": "Ansicht",
|
||||||
|
"website": "Website",
|
||||||
|
"window": "Fenster",
|
||||||
|
"zoom": "Zoom",
|
||||||
|
"zoomIn": "Heranzoomen",
|
||||||
|
"zoomOut": "Herauszoomen"
|
||||||
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"abbr": "Assistent",
|
"abbr": "Assistent",
|
||||||
"clear": {
|
"clear": {
|
||||||
@@ -547,8 +583,12 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
|
"description": "Tägliche Gespräche und schnelle Fragen & Antworten",
|
||||||
"title": "Assistent hinzufügen"
|
"title": "Assistent hinzufügen"
|
||||||
},
|
},
|
||||||
|
"option": {
|
||||||
|
"title": "Typ auswählen"
|
||||||
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "Neues Thema erstellen"
|
"title": "Neues Thema erstellen"
|
||||||
}
|
}
|
||||||
@@ -1606,7 +1646,7 @@
|
|||||||
},
|
},
|
||||||
"assistant": {
|
"assistant": {
|
||||||
"added": {
|
"added": {
|
||||||
"content": "Agent erfolgreich hinzugefügt"
|
"content": "Assistent erfolgreich hinzugefügt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"attachments": {
|
"attachments": {
|
||||||
@@ -2471,12 +2511,13 @@
|
|||||||
"openrouter": "OpenRouter",
|
"openrouter": "OpenRouter",
|
||||||
"ovms": "Intel OVMS",
|
"ovms": "Intel OVMS",
|
||||||
"perplexity": "Perplexity",
|
"perplexity": "Perplexity",
|
||||||
"ph8": "PH8 Großmodell-Plattform",
|
"ph8": "PH8",
|
||||||
"poe": "Poe",
|
"poe": "Poe",
|
||||||
"ppio": "PPIO Cloud",
|
"ppio": "PPIO Cloud",
|
||||||
"qiniu": "Qiniu Cloud KI-Inferenz",
|
"qiniu": "Qiniu Cloud KI-Inferenz",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "SiliconFlow",
|
"silicon": "SiliconFlow",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "StepFun",
|
"stepfun": "StepFun",
|
||||||
"tencent-cloud-ti": "Tencent Cloud TI",
|
"tencent-cloud-ti": "Tencent Cloud TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@@ -2923,15 +2964,14 @@
|
|||||||
},
|
},
|
||||||
"description": "Ein KI-Assistent für Kreative",
|
"description": "Ein KI-Assistent für Kreative",
|
||||||
"downloading": "Update wird heruntergeladen...",
|
"downloading": "Update wird heruntergeladen...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "Unternehmen"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "Feedback",
|
"button": "Feedback",
|
||||||
"title": "Feedback"
|
"title": "Feedback"
|
||||||
},
|
},
|
||||||
"label": "Über uns",
|
"label": "Über uns",
|
||||||
"license": {
|
|
||||||
"button": "Anzeigen",
|
|
||||||
"title": "Lizenz"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "Anzeigen",
|
"button": "Anzeigen",
|
||||||
"title": "Changelog"
|
"title": "Changelog"
|
||||||
@@ -3761,6 +3801,7 @@
|
|||||||
"description": "MCP-Service-Funktion nicht aktivieren",
|
"description": "MCP-Service-Funktion nicht aktivieren",
|
||||||
"label": "MCP-Server nicht verwenden"
|
"label": "MCP-Server nicht verwenden"
|
||||||
},
|
},
|
||||||
|
"discover": "Entdecken",
|
||||||
"duplicateName": "Server mit gleichem Namen existiert bereits",
|
"duplicateName": "Server mit gleichem Namen existiert bereits",
|
||||||
"editJson": "JSON bearbeiten",
|
"editJson": "JSON bearbeiten",
|
||||||
"editMcpJson": "MCP-Konfiguration bearbeiten",
|
"editMcpJson": "MCP-Konfiguration bearbeiten",
|
||||||
@@ -3771,6 +3812,10 @@
|
|||||||
"32000": "MCP-Server starten fehlgeschlagen, bitte überprüfen Sie, ob alle Parameter vollständig ausgefüllt sind",
|
"32000": "MCP-Server starten fehlgeschlagen, bitte überprüfen Sie, ob alle Parameter vollständig ausgefüllt sind",
|
||||||
"toolNotFound": "Tool {{name}} nicht gefunden"
|
"toolNotFound": "Tool {{name}} nicht gefunden"
|
||||||
},
|
},
|
||||||
|
"fetch": {
|
||||||
|
"button": "Server abrufen",
|
||||||
|
"success": "MCP-Server erfolgreich abgerufen"
|
||||||
|
},
|
||||||
"findMore": "Mehr MCP",
|
"findMore": "Mehr MCP",
|
||||||
"headers": "Request-Header",
|
"headers": "Request-Header",
|
||||||
"headersTooltip": "Benutzerdefinierte Request-Header für HTTP-Anfragen",
|
"headersTooltip": "Benutzerdefinierte Request-Header für HTTP-Anfragen",
|
||||||
@@ -3786,6 +3831,7 @@
|
|||||||
"logoUrl": "Logo-URL",
|
"logoUrl": "Logo-URL",
|
||||||
"longRunning": "Lang laufender Modus",
|
"longRunning": "Lang laufender Modus",
|
||||||
"longRunningTooltip": "Nach Aktivierung unterstützt der Server lange Aufgaben. Wenn ein Fortschrittsbenachrichtigung empfangen wird, wird der Timeout-Timer zurückgesetzt und die maximale Timeout-Zeit auf 10 Minuten verlängert",
|
"longRunningTooltip": "Nach Aktivierung unterstützt der Server lange Aufgaben. Wenn ein Fortschrittsbenachrichtigung empfangen wird, wird der Timeout-Timer zurückgesetzt und die maximale Timeout-Zeit auf 10 Minuten verlängert",
|
||||||
|
"marketplaces": "Marktplätze",
|
||||||
"missingDependencies": "Abhängigkeiten fehlen, bitte installieren Sie sie, um fortzufahren",
|
"missingDependencies": "Abhängigkeiten fehlen, bitte installieren Sie sie, um fortzufahren",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "Kuratierte MCP-Serverliste",
|
"awesome": "Kuratierte MCP-Serverliste",
|
||||||
@@ -3834,6 +3880,7 @@
|
|||||||
"provider": "Anbieter",
|
"provider": "Anbieter",
|
||||||
"providerPlaceholder": "Anbietername",
|
"providerPlaceholder": "Anbietername",
|
||||||
"providerUrl": "Anbieter-Website",
|
"providerUrl": "Anbieter-Website",
|
||||||
|
"providers": "Anbieter",
|
||||||
"registry": "Paketverwaltungsquelle",
|
"registry": "Paketverwaltungsquelle",
|
||||||
"registryDefault": "Standard",
|
"registryDefault": "Standard",
|
||||||
"registryTooltip": "Quelle für Paketinstallation auswählen um Netzwerkprobleme der Standardquelle zu lösen",
|
"registryTooltip": "Quelle für Paketinstallation auswählen um Netzwerkprobleme der Standardquelle zu lösen",
|
||||||
@@ -3856,6 +3903,7 @@
|
|||||||
"searchNpx": "MCP durchsuchen",
|
"searchNpx": "MCP durchsuchen",
|
||||||
"serverPlural": "Server",
|
"serverPlural": "Server",
|
||||||
"serverSingular": "Server",
|
"serverSingular": "Server",
|
||||||
|
"servers": "MCP-Server",
|
||||||
"sse": "Server-Sende-Ereignisse (sse)",
|
"sse": "Server-Sende-Ereignisse (sse)",
|
||||||
"startError": "Start fehlgeschlagen",
|
"startError": "Start fehlgeschlagen",
|
||||||
"stdio": "Standard-Eingabe / -Ausgabe (stdio)",
|
"stdio": "Standard-Eingabe / -Ausgabe (stdio)",
|
||||||
@@ -4255,6 +4303,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "AWS-Zugriffsschlüssel-ID",
|
"access_key_id": "AWS-Zugriffsschlüssel-ID",
|
||||||
"access_key_id_help": "Ihre AWS-Zugriffsschlüssel-ID, um auf AWS Bedrock-Dienste zuzugreifen",
|
"access_key_id_help": "Ihre AWS-Zugriffsschlüssel-ID, um auf AWS Bedrock-Dienste zuzugreifen",
|
||||||
|
"api_key": "Bedrock-API-Schlüssel",
|
||||||
|
"api_key_help": "Ihr AWS Bedrock-API-Schlüssel für die Authentifizierung",
|
||||||
|
"auth_type": "Authentifizierungstyp",
|
||||||
|
"auth_type_api_key": "Bedrock-API-Schlüssel",
|
||||||
|
"auth_type_help": "Wählen Sie zwischen IAM-Anmeldeinformationen oder Bedrock-API-Schlüssel-Authentifizierung",
|
||||||
|
"auth_type_iam": "IAM-Anmeldeinformationen",
|
||||||
"description": "AWS Bedrock ist ein vollständig verwalteter Basismodell-Dienst von Amazon, der eine Vielzahl moderner großer Sprachmodelle unterstützt",
|
"description": "AWS Bedrock ist ein vollständig verwalteter Basismodell-Dienst von Amazon, der eine Vielzahl moderner großer Sprachmodelle unterstützt",
|
||||||
"region": "AWS-Region",
|
"region": "AWS-Region",
|
||||||
"region_help": "Ihre AWS-Serviceregion, z.B. us-east-1",
|
"region_help": "Ihre AWS-Serviceregion, z.B. us-east-1",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
|
"description": "Χειριστείτε πολύπλοκες εργασίες με διάφορα εργαλεία",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "Αποτυχία προσθήκης πράκτορα",
|
"failed": "Αποτυχία προσθήκης πράκτορα",
|
||||||
"invalid_agent": "Μη έγκυρος Agent"
|
"invalid_agent": "Μη έγκυρος Agent"
|
||||||
@@ -338,6 +339,41 @@
|
|||||||
},
|
},
|
||||||
"title": "Διακομιστής API"
|
"title": "Διακομιστής API"
|
||||||
},
|
},
|
||||||
|
"appMenu": {
|
||||||
|
"about": "Σχετικά",
|
||||||
|
"close": "Κλείσιμο Παραθύρου",
|
||||||
|
"copy": "Αντιγραφή",
|
||||||
|
"cut": "Κόψε",
|
||||||
|
"delete": "Διαγραφή",
|
||||||
|
"documentation": "Τεκμηρίωση",
|
||||||
|
"edit": "Επεξεργασία",
|
||||||
|
"feedback": "Σχόλια",
|
||||||
|
"file": "Αρχείο",
|
||||||
|
"forceReload": "Εξαναγκασμένη επαναφόρτωση",
|
||||||
|
"front": "Μεταφορά Όλων Μπροστά",
|
||||||
|
"help": "Βοήθεια",
|
||||||
|
"hide": "Κρύψε",
|
||||||
|
"hideOthers": "Απόκρυψη Άλλων",
|
||||||
|
"minimize": "Ελαχιστοποίηση",
|
||||||
|
"paste": "Επικόλληση",
|
||||||
|
"quit": "Παραιτήσου",
|
||||||
|
"redo": "Ξανακάνε",
|
||||||
|
"releases": "Κυκλοφορίες",
|
||||||
|
"reload": "Επαναφόρτωση",
|
||||||
|
"resetZoom": "Πραγματικό Μέγεθος",
|
||||||
|
"selectAll": "Επιλογή Όλων",
|
||||||
|
"services": "Υπηρεσίες",
|
||||||
|
"toggleDevTools": "Εναλλαγή Εργαλείων Προγραμματιστή",
|
||||||
|
"toggleFullscreen": "Εναλλαγή πλήρους οθόνης",
|
||||||
|
"undo": "Αναίρεση",
|
||||||
|
"unhide": "Εμφάνιση Όλων",
|
||||||
|
"view": "Προβολή",
|
||||||
|
"website": "Ιστοσελίδα",
|
||||||
|
"window": "Παράθυρο",
|
||||||
|
"zoom": "Ζουμ",
|
||||||
|
"zoomIn": "Μεγέθυνση",
|
||||||
|
"zoomOut": "Σμίκρυνση"
|
||||||
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"abbr": "Βοηθός",
|
"abbr": "Βοηθός",
|
||||||
"clear": {
|
"clear": {
|
||||||
@@ -547,8 +583,12 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
|
"description": "Καθημερινές συνομιλίες και γρήγορες ερωταπαντήσεις",
|
||||||
"title": "Προσθήκη βοηθού"
|
"title": "Προσθήκη βοηθού"
|
||||||
},
|
},
|
||||||
|
"option": {
|
||||||
|
"title": "Επιλέξτε Τύπο"
|
||||||
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "Δημιουργία νέου θέματος"
|
"title": "Δημιουργία νέου θέματος"
|
||||||
}
|
}
|
||||||
@@ -1606,7 +1646,7 @@
|
|||||||
},
|
},
|
||||||
"assistant": {
|
"assistant": {
|
||||||
"added": {
|
"added": {
|
||||||
"content": "Ο ενεργοποιημένος αστρόναυτης προστέθηκε επιτυχώς"
|
"content": "Ο βοηθός προστέθηκε επιτυχώς"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"attachments": {
|
"attachments": {
|
||||||
@@ -2471,12 +2511,13 @@
|
|||||||
"openrouter": "OpenRouter",
|
"openrouter": "OpenRouter",
|
||||||
"ovms": "Intel OVMS",
|
"ovms": "Intel OVMS",
|
||||||
"perplexity": "Perplexity",
|
"perplexity": "Perplexity",
|
||||||
"ph8": "Πλατφόρμα Ανοιχτής Μεγάλης Μοντέλου PH8",
|
"ph8": "PH8",
|
||||||
"poe": "Poe",
|
"poe": "Poe",
|
||||||
"ppio": "PPIO Piao Yun",
|
"ppio": "PPIO Piao Yun",
|
||||||
"qiniu": "Qiniu AI",
|
"qiniu": "Qiniu AI",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "Σιδηρική Παρουσία",
|
"silicon": "Σιδηρική Παρουσία",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "Βήμα Ουράς",
|
"stepfun": "Βήμα Ουράς",
|
||||||
"tencent-cloud-ti": "Tencent Cloud TI",
|
"tencent-cloud-ti": "Tencent Cloud TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@@ -2923,15 +2964,14 @@
|
|||||||
},
|
},
|
||||||
"description": "Ένα AI ασιστάντα που έχει σχεδιαστεί για δημιουργούς",
|
"description": "Ένα AI ασιστάντα που έχει σχεδιαστεί για δημιουργούς",
|
||||||
"downloading": "Λήψη ενημερώσεων...",
|
"downloading": "Λήψη ενημερώσεων...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "Επιχείρηση"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "Σχόλια και Παρατηρήσεις",
|
"button": "Σχόλια και Παρατηρήσεις",
|
||||||
"title": "Αποστολή σχολίων"
|
"title": "Αποστολή σχολίων"
|
||||||
},
|
},
|
||||||
"label": "Περί μας",
|
"label": "Περί μας",
|
||||||
"license": {
|
|
||||||
"button": "Προβολή",
|
|
||||||
"title": "Licenses"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "Προβολή",
|
"button": "Προβολή",
|
||||||
"title": "Ημερολόγιο Ενημερώσεων"
|
"title": "Ημερολόγιο Ενημερώσεων"
|
||||||
@@ -3761,6 +3801,7 @@
|
|||||||
"description": "Να μην ενεργοποιείται η λειτουργία υπηρεσίας MCP",
|
"description": "Να μην ενεργοποιείται η λειτουργία υπηρεσίας MCP",
|
||||||
"label": "Να μην χρησιμοποιείται διακομιστής MCP"
|
"label": "Να μην χρησιμοποιείται διακομιστής MCP"
|
||||||
},
|
},
|
||||||
|
"discover": "Ανακαλύψτε",
|
||||||
"duplicateName": "Υπάρχει ήδη ένας διακομιστής με αυτό το όνομα",
|
"duplicateName": "Υπάρχει ήδη ένας διακομιστής με αυτό το όνομα",
|
||||||
"editJson": "Επεξεργασία JSON",
|
"editJson": "Επεξεργασία JSON",
|
||||||
"editMcpJson": "Επεξεργασία ρύθμισης MCP",
|
"editMcpJson": "Επεξεργασία ρύθμισης MCP",
|
||||||
@@ -3771,6 +3812,10 @@
|
|||||||
"32000": "Η εκκίνηση του MCP απέτυχε. Παρακαλώ ελέγξτε αν όλες οι παράμετροι έχουν συμπληρωθεί σύμφωνα με τον οδηγό.",
|
"32000": "Η εκκίνηση του MCP απέτυχε. Παρακαλώ ελέγξτε αν όλες οι παράμετροι έχουν συμπληρωθεί σύμφωνα με τον οδηγό.",
|
||||||
"toolNotFound": "Δεν βρέθηκε το εργαλείο {{name}}"
|
"toolNotFound": "Δεν βρέθηκε το εργαλείο {{name}}"
|
||||||
},
|
},
|
||||||
|
"fetch": {
|
||||||
|
"button": "Λήψη Διακομιστών",
|
||||||
|
"success": "Επιτυχής ανάκτηση διακομιστών MCP"
|
||||||
|
},
|
||||||
"findMore": "Περισσότεροι διακομιστές MCP",
|
"findMore": "Περισσότεροι διακομιστές MCP",
|
||||||
"headers": "Κεφαλίδες",
|
"headers": "Κεφαλίδες",
|
||||||
"headersTooltip": "Προσαρμοσμένες κεφαλίδες HTTP αιτήσεων",
|
"headersTooltip": "Προσαρμοσμένες κεφαλίδες HTTP αιτήσεων",
|
||||||
@@ -3786,6 +3831,7 @@
|
|||||||
"logoUrl": "URL Λογότυπου",
|
"logoUrl": "URL Λογότυπου",
|
||||||
"longRunning": "Μακροχρόνια λειτουργία",
|
"longRunning": "Μακροχρόνια λειτουργία",
|
||||||
"longRunningTooltip": "Όταν ενεργοποιηθεί, ο διακομιστής υποστηρίζει μακροχρόνιες εργασίες, επαναφέρει το χρονικό όριο μετά από λήψη ειδοποίησης προόδου και επεκτείνει το μέγιστο χρονικό όριο σε 10 λεπτά.",
|
"longRunningTooltip": "Όταν ενεργοποιηθεί, ο διακομιστής υποστηρίζει μακροχρόνιες εργασίες, επαναφέρει το χρονικό όριο μετά από λήψη ειδοποίησης προόδου και επεκτείνει το μέγιστο χρονικό όριο σε 10 λεπτά.",
|
||||||
|
"marketplaces": "Αγορές",
|
||||||
"missingDependencies": "Λείπει, παρακαλώ εγκαταστήστε το για να συνεχίσετε",
|
"missingDependencies": "Λείπει, παρακαλώ εγκαταστήστε το για να συνεχίσετε",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "Επιλεγμένος κατάλογος διακομιστών MCP",
|
"awesome": "Επιλεγμένος κατάλογος διακομιστών MCP",
|
||||||
@@ -3834,6 +3880,7 @@
|
|||||||
"provider": "Πάροχος",
|
"provider": "Πάροχος",
|
||||||
"providerPlaceholder": "Όνομα παρόχου",
|
"providerPlaceholder": "Όνομα παρόχου",
|
||||||
"providerUrl": "URL Παρόχου",
|
"providerUrl": "URL Παρόχου",
|
||||||
|
"providers": "Πάροχοι",
|
||||||
"registry": "Πηγή Διαχείρισης πακέτων",
|
"registry": "Πηγή Διαχείρισης πακέτων",
|
||||||
"registryDefault": "Προεπιλεγμένη",
|
"registryDefault": "Προεπιλεγμένη",
|
||||||
"registryTooltip": "Επιλέξτε την πηγή για την εγκατάσταση πακέτων, για να αντιμετωπιστούν προβλήματα δικτύου από την προεπιλεγμένη πηγή.",
|
"registryTooltip": "Επιλέξτε την πηγή για την εγκατάσταση πακέτων, για να αντιμετωπιστούν προβλήματα δικτύου από την προεπιλεγμένη πηγή.",
|
||||||
@@ -3856,6 +3903,7 @@
|
|||||||
"searchNpx": "Αναζήτηση MCP",
|
"searchNpx": "Αναζήτηση MCP",
|
||||||
"serverPlural": "Διακομιστές",
|
"serverPlural": "Διακομιστές",
|
||||||
"serverSingular": "Διακομιστής",
|
"serverSingular": "Διακομιστής",
|
||||||
|
"servers": "Διακομιστές MCP",
|
||||||
"sse": "Συμβάντα Αποστολής από τον Διακομιστή (sse)",
|
"sse": "Συμβάντα Αποστολής από τον Διακομιστή (sse)",
|
||||||
"startError": "Εκκίνηση Απέτυχε",
|
"startError": "Εκκίνηση Απέτυχε",
|
||||||
"stdio": "Πρότυπη Είσοδος/Έξοδος (stdio)",
|
"stdio": "Πρότυπη Είσοδος/Έξοδος (stdio)",
|
||||||
@@ -4255,6 +4303,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "Αναγνωριστικό κλειδιού πρόσβασης AWS",
|
"access_key_id": "Αναγνωριστικό κλειδιού πρόσβασης AWS",
|
||||||
"access_key_id_help": "Το ID του κλειδιού πρόσβασης AWS που χρησιμοποιείται για την πρόσβαση στην υπηρεσία AWS Bedrock",
|
"access_key_id_help": "Το ID του κλειδιού πρόσβασης AWS που χρησιμοποιείται για την πρόσβαση στην υπηρεσία AWS Bedrock",
|
||||||
|
"api_key": "Κλειδί API Bedrock",
|
||||||
|
"api_key_help": "Το κλειδί API του AWS Bedrock για έλεγχο ταυτότητας",
|
||||||
|
"auth_type": "Τύπος Πιστοποίησης",
|
||||||
|
"auth_type_api_key": "Κλειδί API Bedrock",
|
||||||
|
"auth_type_help": "Επιλέξτε μεταξύ πιστοποιητικών IAM ή πιστοποίησης με κλειδί API Bedrock",
|
||||||
|
"auth_type_iam": "Διαπιστευτήρια IAM",
|
||||||
"description": "Η AWS Bedrock είναι μια πλήρως διαχειριζόμενη υπηρεσία βασικών μοντέλων που παρέχεται από την Amazon και υποστηρίζει διάφορα προηγμένα μεγάλα γλωσσικά μοντέλα.",
|
"description": "Η AWS Bedrock είναι μια πλήρως διαχειριζόμενη υπηρεσία βασικών μοντέλων που παρέχεται από την Amazon και υποστηρίζει διάφορα προηγμένα μεγάλα γλωσσικά μοντέλα.",
|
||||||
"region": "Περιοχές AWS",
|
"region": "Περιοχές AWS",
|
||||||
"region_help": "Η περιοχή υπηρεσίας AWS σας, για παράδειγμα us-east-1",
|
"region_help": "Η περιοχή υπηρεσίας AWS σας, για παράδειγμα us-east-1",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
|
"description": "Maneja tareas complejas con varias herramientas",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "Error al añadir agente",
|
"failed": "Error al añadir agente",
|
||||||
"invalid_agent": "Agent inválido"
|
"invalid_agent": "Agent inválido"
|
||||||
@@ -338,6 +339,41 @@
|
|||||||
},
|
},
|
||||||
"title": "Servidor API"
|
"title": "Servidor API"
|
||||||
},
|
},
|
||||||
|
"appMenu": {
|
||||||
|
"about": "Acerca de",
|
||||||
|
"close": "Cerrar ventana",
|
||||||
|
"copy": "Copiar",
|
||||||
|
"cut": "Cortar",
|
||||||
|
"delete": "Eliminar",
|
||||||
|
"documentation": "Documentación",
|
||||||
|
"edit": "Editar",
|
||||||
|
"feedback": "Retroalimentación",
|
||||||
|
"file": "Archivo",
|
||||||
|
"forceReload": "Forzar recarga",
|
||||||
|
"front": "Traer todo al frente",
|
||||||
|
"help": "Ayuda",
|
||||||
|
"hide": "Ocultar",
|
||||||
|
"hideOthers": "Ocultar Otros",
|
||||||
|
"minimize": "Minimizar",
|
||||||
|
"paste": "Pegar",
|
||||||
|
"quit": "Abandonar",
|
||||||
|
"redo": "Rehacer",
|
||||||
|
"releases": "Lanzamientos",
|
||||||
|
"reload": "Recargar",
|
||||||
|
"resetZoom": "Tamaño Real",
|
||||||
|
"selectAll": "Seleccionar todo",
|
||||||
|
"services": "Servicios",
|
||||||
|
"toggleDevTools": "Alternar herramientas de desarrollo",
|
||||||
|
"toggleFullscreen": "Activar pantalla completa",
|
||||||
|
"undo": "Deshacer",
|
||||||
|
"unhide": "Mostrar todo",
|
||||||
|
"view": "Vista",
|
||||||
|
"website": "Sitio web",
|
||||||
|
"window": "Ventana",
|
||||||
|
"zoom": "Zoom",
|
||||||
|
"zoomIn": "Acercar",
|
||||||
|
"zoomOut": "Alejar"
|
||||||
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"abbr": "Asistente",
|
"abbr": "Asistente",
|
||||||
"clear": {
|
"clear": {
|
||||||
@@ -547,8 +583,12 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
|
"description": "Conversaciones diarias y preguntas y respuestas rápidas",
|
||||||
"title": "Agregar asistente"
|
"title": "Agregar asistente"
|
||||||
},
|
},
|
||||||
|
"option": {
|
||||||
|
"title": "Seleccionar Tipo"
|
||||||
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "Crear nuevo tema"
|
"title": "Crear nuevo tema"
|
||||||
}
|
}
|
||||||
@@ -2471,12 +2511,13 @@
|
|||||||
"openrouter": "OpenRouter",
|
"openrouter": "OpenRouter",
|
||||||
"ovms": "Intel OVMS",
|
"ovms": "Intel OVMS",
|
||||||
"perplexity": "Perplejidad",
|
"perplexity": "Perplejidad",
|
||||||
"ph8": "Plataforma Abierta de Grandes Modelos PH8",
|
"ph8": "PH8",
|
||||||
"poe": "Poe",
|
"poe": "Poe",
|
||||||
"ppio": "PPIO Cloud Piao",
|
"ppio": "PPIO Cloud Piao",
|
||||||
"qiniu": "Qiniu AI",
|
"qiniu": "Qiniu AI",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "Silicio Fluido",
|
"silicon": "Silicio Fluido",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "Función Salto",
|
"stepfun": "Función Salto",
|
||||||
"tencent-cloud-ti": "Tencent Nube TI",
|
"tencent-cloud-ti": "Tencent Nube TI",
|
||||||
"together": "Juntos",
|
"together": "Juntos",
|
||||||
@@ -2923,15 +2964,14 @@
|
|||||||
},
|
},
|
||||||
"description": "Una asistente de IA creada para los creadores",
|
"description": "Una asistente de IA creada para los creadores",
|
||||||
"downloading": "Descargando actualización...",
|
"downloading": "Descargando actualización...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "Empresa"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "Enviar feedback",
|
"button": "Enviar feedback",
|
||||||
"title": "Enviar comentarios"
|
"title": "Enviar comentarios"
|
||||||
},
|
},
|
||||||
"label": "Acerca de nosotros",
|
"label": "Acerca de nosotros",
|
||||||
"license": {
|
|
||||||
"button": "Ver",
|
|
||||||
"title": "Licencia"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "Ver",
|
"button": "Ver",
|
||||||
"title": "Registro de cambios"
|
"title": "Registro de cambios"
|
||||||
@@ -3761,6 +3801,7 @@
|
|||||||
"description": "No habilitar funciones del servicio MCP",
|
"description": "No habilitar funciones del servicio MCP",
|
||||||
"label": "No utilizar servidor MCP"
|
"label": "No utilizar servidor MCP"
|
||||||
},
|
},
|
||||||
|
"discover": "Descubrir",
|
||||||
"duplicateName": "Ya existe un servidor con el mismo nombre",
|
"duplicateName": "Ya existe un servidor con el mismo nombre",
|
||||||
"editJson": "Editar JSON",
|
"editJson": "Editar JSON",
|
||||||
"editMcpJson": "Editar configuración MCP",
|
"editMcpJson": "Editar configuración MCP",
|
||||||
@@ -3771,6 +3812,10 @@
|
|||||||
"32000": "El servidor MCP no se pudo iniciar, verifique si los parámetros están completos según la guía",
|
"32000": "El servidor MCP no se pudo iniciar, verifique si los parámetros están completos según la guía",
|
||||||
"toolNotFound": "Herramienta no encontrada {{name}}"
|
"toolNotFound": "Herramienta no encontrada {{name}}"
|
||||||
},
|
},
|
||||||
|
"fetch": {
|
||||||
|
"button": "Obtener Servidores",
|
||||||
|
"success": "Servidores MCP obtenidos con éxito"
|
||||||
|
},
|
||||||
"findMore": "Más servidores MCP",
|
"findMore": "Más servidores MCP",
|
||||||
"headers": "Encabezados",
|
"headers": "Encabezados",
|
||||||
"headersTooltip": "Encabezados personalizados para solicitudes HTTP",
|
"headersTooltip": "Encabezados personalizados para solicitudes HTTP",
|
||||||
@@ -3786,6 +3831,7 @@
|
|||||||
"logoUrl": "URL del logotipo",
|
"logoUrl": "URL del logotipo",
|
||||||
"longRunning": "Modo de ejecución prolongada",
|
"longRunning": "Modo de ejecución prolongada",
|
||||||
"longRunningTooltip": "Una vez habilitado, el servidor admite tareas de larga duración, reinicia el temporizador de tiempo de espera al recibir notificaciones de progreso y amplía el tiempo máximo de espera hasta 10 minutos.",
|
"longRunningTooltip": "Una vez habilitado, el servidor admite tareas de larga duración, reinicia el temporizador de tiempo de espera al recibir notificaciones de progreso y amplía el tiempo máximo de espera hasta 10 minutos.",
|
||||||
|
"marketplaces": "Mercados",
|
||||||
"missingDependencies": "Faltan, instalelas para continuar",
|
"missingDependencies": "Faltan, instalelas para continuar",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "Lista seleccionada de servidores MCP",
|
"awesome": "Lista seleccionada de servidores MCP",
|
||||||
@@ -3834,6 +3880,7 @@
|
|||||||
"provider": "Proveedor",
|
"provider": "Proveedor",
|
||||||
"providerPlaceholder": "Nombre del proveedor",
|
"providerPlaceholder": "Nombre del proveedor",
|
||||||
"providerUrl": "URL del proveedor",
|
"providerUrl": "URL del proveedor",
|
||||||
|
"providers": "Proveedores",
|
||||||
"registry": "Repositorio de paquetes",
|
"registry": "Repositorio de paquetes",
|
||||||
"registryDefault": "Predeterminado",
|
"registryDefault": "Predeterminado",
|
||||||
"registryTooltip": "Seleccione un repositorio para instalar paquetes, útil para resolver problemas de red con el repositorio predeterminado.",
|
"registryTooltip": "Seleccione un repositorio para instalar paquetes, útil para resolver problemas de red con el repositorio predeterminado.",
|
||||||
@@ -3856,6 +3903,7 @@
|
|||||||
"searchNpx": "Buscar MCP",
|
"searchNpx": "Buscar MCP",
|
||||||
"serverPlural": "Servidores",
|
"serverPlural": "Servidores",
|
||||||
"serverSingular": "Servidor",
|
"serverSingular": "Servidor",
|
||||||
|
"servers": "Servidores MCP",
|
||||||
"sse": "Eventos enviados por el servidor (sse)",
|
"sse": "Eventos enviados por el servidor (sse)",
|
||||||
"startError": "Inicio fallido",
|
"startError": "Inicio fallido",
|
||||||
"stdio": "Entrada/Salida estándar (stdio)",
|
"stdio": "Entrada/Salida estándar (stdio)",
|
||||||
@@ -4255,6 +4303,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "ID de clave de acceso de AWS",
|
"access_key_id": "ID de clave de acceso de AWS",
|
||||||
"access_key_id_help": "Su ID de clave de acceso de AWS, utilizado para acceder al servicio AWS Bedrock",
|
"access_key_id_help": "Su ID de clave de acceso de AWS, utilizado para acceder al servicio AWS Bedrock",
|
||||||
|
"api_key": "Clave de API de Bedrock",
|
||||||
|
"api_key_help": "Tu clave de API de AWS Bedrock para autenticación",
|
||||||
|
"auth_type": "Tipo de autenticación",
|
||||||
|
"auth_type_api_key": "Clave de API de Bedrock",
|
||||||
|
"auth_type_help": "Elige entre credenciales IAM o autenticación con clave API de Bedrock",
|
||||||
|
"auth_type_iam": "Credenciales de IAM",
|
||||||
"description": "AWS Bedrock es un servicio de modelos fundamentales completamente gestionado proporcionado por Amazon, que admite diversos modelos avanzados de lenguaje de gran tamaño.",
|
"description": "AWS Bedrock es un servicio de modelos fundamentales completamente gestionado proporcionado por Amazon, que admite diversos modelos avanzados de lenguaje de gran tamaño.",
|
||||||
"region": "Región de AWS",
|
"region": "Región de AWS",
|
||||||
"region_help": "Su región de servicio AWS, por ejemplo us-east-1",
|
"region_help": "Su región de servicio AWS, por ejemplo us-east-1",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
|
"description": "Gérez des tâches complexes avec divers outils",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "Échec de l'ajout de l'agent",
|
"failed": "Échec de l'ajout de l'agent",
|
||||||
"invalid_agent": "Agent invalide"
|
"invalid_agent": "Agent invalide"
|
||||||
@@ -338,6 +339,41 @@
|
|||||||
},
|
},
|
||||||
"title": "Serveur API"
|
"title": "Serveur API"
|
||||||
},
|
},
|
||||||
|
"appMenu": {
|
||||||
|
"about": "À propos",
|
||||||
|
"close": "Fermer la fenêtre",
|
||||||
|
"copy": "Copier",
|
||||||
|
"cut": "Couper",
|
||||||
|
"delete": "Supprimer",
|
||||||
|
"documentation": "Documentation",
|
||||||
|
"edit": "Modifier",
|
||||||
|
"feedback": "Retour d'information",
|
||||||
|
"file": "Fichier",
|
||||||
|
"forceReload": "Rechargement forcé",
|
||||||
|
"front": "Tout ramener au premier plan",
|
||||||
|
"help": "Aide",
|
||||||
|
"hide": "Cacher",
|
||||||
|
"hideOthers": "Masquer les autres",
|
||||||
|
"minimize": "Minimiser",
|
||||||
|
"paste": "Coller",
|
||||||
|
"quit": "Quitter",
|
||||||
|
"redo": "Refaire",
|
||||||
|
"releases": "Sorties",
|
||||||
|
"reload": "Recharger",
|
||||||
|
"resetZoom": "Taille réelle",
|
||||||
|
"selectAll": "Tout sélectionner",
|
||||||
|
"services": "Services",
|
||||||
|
"toggleDevTools": "Basculer les outils de développement",
|
||||||
|
"toggleFullscreen": "Basculer en plein écran",
|
||||||
|
"undo": "Annuler",
|
||||||
|
"unhide": "Tout afficher",
|
||||||
|
"view": "Vue",
|
||||||
|
"website": "Site web",
|
||||||
|
"window": "Fenêtre",
|
||||||
|
"zoom": "Zoom",
|
||||||
|
"zoomIn": "Zoom Avant",
|
||||||
|
"zoomOut": "Dézoomer"
|
||||||
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"abbr": "Aide",
|
"abbr": "Aide",
|
||||||
"clear": {
|
"clear": {
|
||||||
@@ -547,8 +583,12 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
|
"description": "Conversations quotidiennes et Q&R rapides",
|
||||||
"title": "Ajouter un assistant"
|
"title": "Ajouter un assistant"
|
||||||
},
|
},
|
||||||
|
"option": {
|
||||||
|
"title": "Sélectionner le type"
|
||||||
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "Nouveau sujet"
|
"title": "Nouveau sujet"
|
||||||
}
|
}
|
||||||
@@ -2471,12 +2511,13 @@
|
|||||||
"openrouter": "OpenRouter",
|
"openrouter": "OpenRouter",
|
||||||
"ovms": "Intel OVMS",
|
"ovms": "Intel OVMS",
|
||||||
"perplexity": "Perplexité",
|
"perplexity": "Perplexité",
|
||||||
"ph8": "Plateforme ouverte de grands modèles PH8",
|
"ph8": "PH8",
|
||||||
"poe": "Poe",
|
"poe": "Poe",
|
||||||
"ppio": "PPIO Cloud Piou",
|
"ppio": "PPIO Cloud Piou",
|
||||||
"qiniu": "Qiniu AI",
|
"qiniu": "Qiniu AI",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "Silicium Fluide",
|
"silicon": "Silicium Fluide",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "Échelon Étoile",
|
"stepfun": "Échelon Étoile",
|
||||||
"tencent-cloud-ti": "Tencent Cloud TI",
|
"tencent-cloud-ti": "Tencent Cloud TI",
|
||||||
"together": "Ensemble",
|
"together": "Ensemble",
|
||||||
@@ -2923,15 +2964,14 @@
|
|||||||
},
|
},
|
||||||
"description": "Un assistant IA conçu pour les créateurs",
|
"description": "Un assistant IA conçu pour les créateurs",
|
||||||
"downloading": "Téléchargement de la mise à jour en cours...",
|
"downloading": "Téléchargement de la mise à jour en cours...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "Entreprise"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "Faire un retour",
|
"button": "Faire un retour",
|
||||||
"title": "Retour d'information"
|
"title": "Retour d'information"
|
||||||
},
|
},
|
||||||
"label": "À propos de nous",
|
"label": "À propos de nous",
|
||||||
"license": {
|
|
||||||
"button": "Afficher",
|
|
||||||
"title": "Licence"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "Afficher",
|
"button": "Afficher",
|
||||||
"title": "Journal des mises à jour"
|
"title": "Journal des mises à jour"
|
||||||
@@ -3761,6 +3801,7 @@
|
|||||||
"description": "Désactiver les fonctionnalités du service MCP",
|
"description": "Désactiver les fonctionnalités du service MCP",
|
||||||
"label": "Ne pas utiliser le serveur MCP"
|
"label": "Ne pas utiliser le serveur MCP"
|
||||||
},
|
},
|
||||||
|
"discover": "Découvrir",
|
||||||
"duplicateName": "Un serveur portant le même nom existe déjà",
|
"duplicateName": "Un serveur portant le même nom existe déjà",
|
||||||
"editJson": "Modifier le JSON",
|
"editJson": "Modifier le JSON",
|
||||||
"editMcpJson": "Редактировать конфигурацию MCP",
|
"editMcpJson": "Редактировать конфигурацию MCP",
|
||||||
@@ -3771,6 +3812,10 @@
|
|||||||
"32000": "Échec du démarrage du serveur MCP, veuillez vérifier si tous les paramètres sont correctement remplis conformément au tutoriel",
|
"32000": "Échec du démarrage du serveur MCP, veuillez vérifier si tous les paramètres sont correctement remplis conformément au tutoriel",
|
||||||
"toolNotFound": "Outil non trouvé {{name}}"
|
"toolNotFound": "Outil non trouvé {{name}}"
|
||||||
},
|
},
|
||||||
|
"fetch": {
|
||||||
|
"button": "Récupérer les serveurs",
|
||||||
|
"success": "Serveurs MCP récupérés avec succès"
|
||||||
|
},
|
||||||
"findMore": "Plus de serveurs MCP",
|
"findMore": "Plus de serveurs MCP",
|
||||||
"headers": "Заголовки запроса",
|
"headers": "Заголовки запроса",
|
||||||
"headersTooltip": "Пользовательские заголовки HTTP-запроса",
|
"headersTooltip": "Пользовательские заголовки HTTP-запроса",
|
||||||
@@ -3786,6 +3831,7 @@
|
|||||||
"logoUrl": "Адрес логотипа",
|
"logoUrl": "Адрес логотипа",
|
||||||
"longRunning": "Mode d'exécution prolongée",
|
"longRunning": "Mode d'exécution prolongée",
|
||||||
"longRunningTooltip": "Une fois activé, le serveur prend en charge les tâches de longue durée, réinitialise le minuteur de temporisation à la réception des notifications de progression, et prolonge le délai d'expiration maximal à 10 minutes.",
|
"longRunningTooltip": "Une fois activé, le serveur prend en charge les tâches de longue durée, réinitialise le minuteur de temporisation à la réception des notifications de progression, et prolonge le délai d'expiration maximal à 10 minutes.",
|
||||||
|
"marketplaces": "Places de marché",
|
||||||
"missingDependencies": "Manquantes, veuillez les installer pour continuer",
|
"missingDependencies": "Manquantes, veuillez les installer pour continuer",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "Liste sélectionnée de serveurs MCP",
|
"awesome": "Liste sélectionnée de serveurs MCP",
|
||||||
@@ -3834,6 +3880,7 @@
|
|||||||
"provider": "Поставщик",
|
"provider": "Поставщик",
|
||||||
"providerPlaceholder": "Название поставщика",
|
"providerPlaceholder": "Название поставщика",
|
||||||
"providerUrl": "Адрес поставщика",
|
"providerUrl": "Адрес поставщика",
|
||||||
|
"providers": "Fournisseurs",
|
||||||
"registry": "Источник управления пакетами",
|
"registry": "Источник управления пакетами",
|
||||||
"registryDefault": "По умолчанию",
|
"registryDefault": "По умолчанию",
|
||||||
"registryTooltip": "Выберите источник для установки пакетов, чтобы решить проблемы с сетью по умолчанию.",
|
"registryTooltip": "Выберите источник для установки пакетов, чтобы решить проблемы с сетью по умолчанию.",
|
||||||
@@ -3856,6 +3903,7 @@
|
|||||||
"searchNpx": "Поиск MCP",
|
"searchNpx": "Поиск MCP",
|
||||||
"serverPlural": "Serveurs",
|
"serverPlural": "Serveurs",
|
||||||
"serverSingular": "Serveur",
|
"serverSingular": "Serveur",
|
||||||
|
"servers": "Serveurs MCP",
|
||||||
"sse": "Серверные отправляемые события (sse)",
|
"sse": "Серверные отправляемые события (sse)",
|
||||||
"startError": "Ошибка запуска",
|
"startError": "Ошибка запуска",
|
||||||
"stdio": "Стандартный ввод/вывод (stdio)",
|
"stdio": "Стандартный ввод/вывод (stdio)",
|
||||||
@@ -4255,6 +4303,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "Identifiant de clé d'accès AWS",
|
"access_key_id": "Identifiant de clé d'accès AWS",
|
||||||
"access_key_id_help": "Votre identifiant de clé d'accès AWS, utilisé pour accéder au service AWS Bedrock",
|
"access_key_id_help": "Votre identifiant de clé d'accès AWS, utilisé pour accéder au service AWS Bedrock",
|
||||||
|
"api_key": "Clé API Bedrock",
|
||||||
|
"api_key_help": "Votre clé API AWS Bedrock pour l'authentification",
|
||||||
|
"auth_type": "Type d'authentification",
|
||||||
|
"auth_type_api_key": "Clé API Bedrock",
|
||||||
|
"auth_type_help": "Choisissez entre l'authentification par identifiants IAM ou par clé API Bedrock",
|
||||||
|
"auth_type_iam": "Identifiants IAM",
|
||||||
"description": "AWS Bedrock est un service de modèles de base entièrement géré proposé par Amazon, prenant en charge divers grands modèles linguistiques avancés.",
|
"description": "AWS Bedrock est un service de modèles de base entièrement géré proposé par Amazon, prenant en charge divers grands modèles linguistiques avancés.",
|
||||||
"region": "Région AWS",
|
"region": "Région AWS",
|
||||||
"region_help": "Votre région de service AWS, par exemple us-east-1",
|
"region_help": "Votre région de service AWS, par exemple us-east-1",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
|
"description": "さまざまなツールを使って複雑なタスクを処理する",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "エージェントの追加に失敗しました",
|
"failed": "エージェントの追加に失敗しました",
|
||||||
"invalid_agent": "無効なエージェント"
|
"invalid_agent": "無効なエージェント"
|
||||||
@@ -338,6 +339,41 @@
|
|||||||
},
|
},
|
||||||
"title": "API サーバー"
|
"title": "API サーバー"
|
||||||
},
|
},
|
||||||
|
"appMenu": {
|
||||||
|
"about": "について",
|
||||||
|
"close": "ウィンドウを閉じる",
|
||||||
|
"copy": "コピー",
|
||||||
|
"cut": "切る",
|
||||||
|
"delete": "削除",
|
||||||
|
"documentation": "ドキュメント",
|
||||||
|
"edit": "編集",
|
||||||
|
"feedback": "フィードバック",
|
||||||
|
"file": "ファイル",
|
||||||
|
"forceReload": "強制再読み込み",
|
||||||
|
"front": "すべてを前面に移動",
|
||||||
|
"help": "助け",
|
||||||
|
"hide": "隠す",
|
||||||
|
"hideOthers": "他を隠す",
|
||||||
|
"minimize": "最小化",
|
||||||
|
"paste": "ペースト",
|
||||||
|
"quit": "やめる",
|
||||||
|
"redo": "やり直し",
|
||||||
|
"releases": "リリース",
|
||||||
|
"reload": "リロード",
|
||||||
|
"resetZoom": "実寸",
|
||||||
|
"selectAll": "すべて選択",
|
||||||
|
"services": "サービス",
|
||||||
|
"toggleDevTools": "開発者ツールを切り替え",
|
||||||
|
"toggleFullscreen": "全画面表示を切り替え",
|
||||||
|
"undo": "元に戻す",
|
||||||
|
"unhide": "すべて表示",
|
||||||
|
"view": "表示",
|
||||||
|
"website": "ウェブサイト",
|
||||||
|
"window": "窓",
|
||||||
|
"zoom": "ズーム",
|
||||||
|
"zoomIn": "ズームイン",
|
||||||
|
"zoomOut": "ズームアウト"
|
||||||
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"abbr": "アシスタント",
|
"abbr": "アシスタント",
|
||||||
"clear": {
|
"clear": {
|
||||||
@@ -547,8 +583,12 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
|
"description": "日常会話と簡単なQ&A",
|
||||||
"title": "アシスタントを追加"
|
"title": "アシスタントを追加"
|
||||||
},
|
},
|
||||||
|
"option": {
|
||||||
|
"title": "種類を選択"
|
||||||
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "新しいトピック"
|
"title": "新しいトピック"
|
||||||
}
|
}
|
||||||
@@ -1606,7 +1646,7 @@
|
|||||||
},
|
},
|
||||||
"assistant": {
|
"assistant": {
|
||||||
"added": {
|
"added": {
|
||||||
"content": "アシスタントが追加されました"
|
"content": "助手が追加されました"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"attachments": {
|
"attachments": {
|
||||||
@@ -2477,6 +2517,7 @@
|
|||||||
"qiniu": "七牛云 AI 推理",
|
"qiniu": "七牛云 AI 推理",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "SiliconFlow",
|
"silicon": "SiliconFlow",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "StepFun",
|
"stepfun": "StepFun",
|
||||||
"tencent-cloud-ti": "Tencent Cloud TI",
|
"tencent-cloud-ti": "Tencent Cloud TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@@ -2923,15 +2964,14 @@
|
|||||||
},
|
},
|
||||||
"description": "クリエイターのための強力なAIアシスタント",
|
"description": "クリエイターのための強力なAIアシスタント",
|
||||||
"downloading": "ダウンロード中...",
|
"downloading": "ダウンロード中...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "エンタープライズ"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "フィードバック",
|
"button": "フィードバック",
|
||||||
"title": "フィードバック"
|
"title": "フィードバック"
|
||||||
},
|
},
|
||||||
"label": "について",
|
"label": "について",
|
||||||
"license": {
|
|
||||||
"button": "ライセンス",
|
|
||||||
"title": "ライセンス"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "リリース",
|
"button": "リリース",
|
||||||
"title": "リリースノート"
|
"title": "リリースノート"
|
||||||
@@ -3761,6 +3801,7 @@
|
|||||||
"description": "MCP機能を有効にしない",
|
"description": "MCP機能を有効にしない",
|
||||||
"label": "MCPサーバーを無効にする"
|
"label": "MCPサーバーを無効にする"
|
||||||
},
|
},
|
||||||
|
"discover": "発見",
|
||||||
"duplicateName": "同じ名前のサーバーが既に存在します",
|
"duplicateName": "同じ名前のサーバーが既に存在します",
|
||||||
"editJson": "JSONを編集",
|
"editJson": "JSONを編集",
|
||||||
"editMcpJson": "MCP 設定を編集",
|
"editMcpJson": "MCP 設定を編集",
|
||||||
@@ -3771,6 +3812,10 @@
|
|||||||
"32000": "MCP サーバーが起動しませんでした。パラメーターを確認してください",
|
"32000": "MCP サーバーが起動しませんでした。パラメーターを確認してください",
|
||||||
"toolNotFound": "ツール {{name}} が見つかりません"
|
"toolNotFound": "ツール {{name}} が見つかりません"
|
||||||
},
|
},
|
||||||
|
"fetch": {
|
||||||
|
"button": "サーバーを取得",
|
||||||
|
"success": "MCPサーバーの取得に成功しました"
|
||||||
|
},
|
||||||
"findMore": "MCP を見つける",
|
"findMore": "MCP を見つける",
|
||||||
"headers": "ヘッダー",
|
"headers": "ヘッダー",
|
||||||
"headersTooltip": "HTTP リクエストのカスタムヘッダー",
|
"headersTooltip": "HTTP リクエストのカスタムヘッダー",
|
||||||
@@ -3786,6 +3831,7 @@
|
|||||||
"logoUrl": "ロゴURL",
|
"logoUrl": "ロゴURL",
|
||||||
"longRunning": "長時間運行モード",
|
"longRunning": "長時間運行モード",
|
||||||
"longRunningTooltip": "このオプションを有効にすると、サーバーは長時間のタスクをサポートします。進行状況通知を受信すると、タイムアウトがリセットされ、最大実行時間が10分に延長されます。",
|
"longRunningTooltip": "このオプションを有効にすると、サーバーは長時間のタスクをサポートします。進行状況通知を受信すると、タイムアウトがリセットされ、最大実行時間が10分に延長されます。",
|
||||||
|
"marketplaces": "マーケットプレイス",
|
||||||
"missingDependencies": "が不足しています。続行するにはインストールしてください。",
|
"missingDependencies": "が不足しています。続行するにはインストールしてください。",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "厳選された MCP サーバーリスト",
|
"awesome": "厳選された MCP サーバーリスト",
|
||||||
@@ -3834,6 +3880,7 @@
|
|||||||
"provider": "プロバイダー",
|
"provider": "プロバイダー",
|
||||||
"providerPlaceholder": "プロバイダー名",
|
"providerPlaceholder": "プロバイダー名",
|
||||||
"providerUrl": "プロバイダーURL",
|
"providerUrl": "プロバイダーURL",
|
||||||
|
"providers": "プロバイダー",
|
||||||
"registry": "パッケージ管理レジストリ",
|
"registry": "パッケージ管理レジストリ",
|
||||||
"registryDefault": "デフォルト",
|
"registryDefault": "デフォルト",
|
||||||
"registryTooltip": "デフォルトのレジストリでネットワークの問題が発生した場合、パッケージインストールに使用するレジストリを選択してください。",
|
"registryTooltip": "デフォルトのレジストリでネットワークの問題が発生した場合、パッケージインストールに使用するレジストリを選択してください。",
|
||||||
@@ -3856,6 +3903,7 @@
|
|||||||
"searchNpx": "MCP を検索",
|
"searchNpx": "MCP を検索",
|
||||||
"serverPlural": "サーバー",
|
"serverPlural": "サーバー",
|
||||||
"serverSingular": "サーバー",
|
"serverSingular": "サーバー",
|
||||||
|
"servers": "MCPサーバー",
|
||||||
"sse": "サーバー送信イベント (sse)",
|
"sse": "サーバー送信イベント (sse)",
|
||||||
"startError": "起動に失敗しました",
|
"startError": "起動に失敗しました",
|
||||||
"stdio": "標準入力/出力 (stdio)",
|
"stdio": "標準入力/出力 (stdio)",
|
||||||
@@ -4255,6 +4303,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "AWS アクセスキー ID",
|
"access_key_id": "AWS アクセスキー ID",
|
||||||
"access_key_id_help": "あなたの AWS アクセスキー ID は、AWS Bedrock サービスへのアクセスに使用されます",
|
"access_key_id_help": "あなたの AWS アクセスキー ID は、AWS Bedrock サービスへのアクセスに使用されます",
|
||||||
|
"api_key": "Bedrock APIキー",
|
||||||
|
"api_key_help": "認証用のAWS Bedrock APIキー",
|
||||||
|
"auth_type": "認証タイプ",
|
||||||
|
"auth_type_api_key": "Bedrock APIキー",
|
||||||
|
"auth_type_help": "IAM認証情報とBedrock APIキー認証のどちらかを選択してください",
|
||||||
|
"auth_type_iam": "IAM認証情報",
|
||||||
"description": "AWS Bedrock は、Amazon が提供する完全に管理されたベースモデルサービスで、さまざまな最先端の大言語モデルをサポートしています",
|
"description": "AWS Bedrock は、Amazon が提供する完全に管理されたベースモデルサービスで、さまざまな最先端の大言語モデルをサポートしています",
|
||||||
"region": "AWS リージョン",
|
"region": "AWS リージョン",
|
||||||
"region_help": "あなたの AWS サービスリージョン、例:us-east-1",
|
"region_help": "あなたの AWS サービスリージョン、例:us-east-1",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
|
"description": "Lide com tarefas complexas usando várias ferramentas",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "Falha ao adicionar agente",
|
"failed": "Falha ao adicionar agente",
|
||||||
"invalid_agent": "Agent inválido"
|
"invalid_agent": "Agent inválido"
|
||||||
@@ -338,6 +339,41 @@
|
|||||||
},
|
},
|
||||||
"title": "Servidor API"
|
"title": "Servidor API"
|
||||||
},
|
},
|
||||||
|
"appMenu": {
|
||||||
|
"about": "Sobre",
|
||||||
|
"close": "Fechar Janela",
|
||||||
|
"copy": "Copiar",
|
||||||
|
"cut": "Corte",
|
||||||
|
"delete": "Excluir",
|
||||||
|
"documentation": "Documentação",
|
||||||
|
"edit": "Editar",
|
||||||
|
"feedback": "Feedback",
|
||||||
|
"file": "Arquivo",
|
||||||
|
"forceReload": "Forçar Recarregamento",
|
||||||
|
"front": "Trazer Tudo para a Frente",
|
||||||
|
"help": "Ajuda",
|
||||||
|
"hide": "Esconder",
|
||||||
|
"hideOthers": "Ocultar Outros",
|
||||||
|
"minimize": "Minimizar",
|
||||||
|
"paste": "Colar",
|
||||||
|
"quit": "Sair",
|
||||||
|
"redo": "Refazer",
|
||||||
|
"releases": "Lançamentos",
|
||||||
|
"reload": "Recarregar",
|
||||||
|
"resetZoom": "Tamanho Real",
|
||||||
|
"selectAll": "Selecionar Todos",
|
||||||
|
"services": "Serviços",
|
||||||
|
"toggleDevTools": "Alternar Ferramentas de Desenvolvedor",
|
||||||
|
"toggleFullscreen": "Alternar Tela Cheia",
|
||||||
|
"undo": "Desfazer",
|
||||||
|
"unhide": "Mostrar Todos",
|
||||||
|
"view": "Visualizar",
|
||||||
|
"website": "Site",
|
||||||
|
"window": "Janela",
|
||||||
|
"zoom": "Zoom",
|
||||||
|
"zoomIn": "Ampliar",
|
||||||
|
"zoomOut": "Reduzir Zoom"
|
||||||
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"abbr": "Assistente",
|
"abbr": "Assistente",
|
||||||
"clear": {
|
"clear": {
|
||||||
@@ -547,8 +583,12 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
|
"description": "Conversas diárias e perguntas e respostas rápidas",
|
||||||
"title": "Adicionar assistente"
|
"title": "Adicionar assistente"
|
||||||
},
|
},
|
||||||
|
"option": {
|
||||||
|
"title": "Selecionar Tipo"
|
||||||
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "Novo Tópico"
|
"title": "Novo Tópico"
|
||||||
}
|
}
|
||||||
@@ -2471,12 +2511,13 @@
|
|||||||
"openrouter": "OpenRouter",
|
"openrouter": "OpenRouter",
|
||||||
"ovms": "Intel OVMS",
|
"ovms": "Intel OVMS",
|
||||||
"perplexity": "Perplexidade",
|
"perplexity": "Perplexidade",
|
||||||
"ph8": "Plataforma Aberta de Grandes Modelos PH8",
|
"ph8": "PH8",
|
||||||
"poe": "Poe",
|
"poe": "Poe",
|
||||||
"ppio": "PPIO Nuvem Piao",
|
"ppio": "PPIO Nuvem Piao",
|
||||||
"qiniu": "Qiniu AI",
|
"qiniu": "Qiniu AI",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "Silício em Fluxo",
|
"silicon": "Silício em Fluxo",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "Função de Passo Estelar",
|
"stepfun": "Função de Passo Estelar",
|
||||||
"tencent-cloud-ti": "Nuvem TI da Tencent",
|
"tencent-cloud-ti": "Nuvem TI da Tencent",
|
||||||
"together": "Juntos",
|
"together": "Juntos",
|
||||||
@@ -2923,15 +2964,14 @@
|
|||||||
},
|
},
|
||||||
"description": "Um assistente de IA criado para criadores",
|
"description": "Um assistente de IA criado para criadores",
|
||||||
"downloading": "Baixando atualizações...",
|
"downloading": "Baixando atualizações...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "Empresa"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "Feedback",
|
"button": "Feedback",
|
||||||
"title": "Enviar feedback"
|
"title": "Enviar feedback"
|
||||||
},
|
},
|
||||||
"label": "Sobre Nós",
|
"label": "Sobre Nós",
|
||||||
"license": {
|
|
||||||
"button": "Ver",
|
|
||||||
"title": "Licença"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "Ver",
|
"button": "Ver",
|
||||||
"title": "Registro de alterações"
|
"title": "Registro de alterações"
|
||||||
@@ -3761,6 +3801,7 @@
|
|||||||
"description": "Não ativar a funcionalidade do serviço MCP",
|
"description": "Não ativar a funcionalidade do serviço MCP",
|
||||||
"label": "Não usar servidor MCP"
|
"label": "Não usar servidor MCP"
|
||||||
},
|
},
|
||||||
|
"discover": "Descobrir",
|
||||||
"duplicateName": "Já existe um servidor com o mesmo nome",
|
"duplicateName": "Já existe um servidor com o mesmo nome",
|
||||||
"editJson": "Editar JSON",
|
"editJson": "Editar JSON",
|
||||||
"editMcpJson": "Editar Configuração MCP",
|
"editMcpJson": "Editar Configuração MCP",
|
||||||
@@ -3771,6 +3812,10 @@
|
|||||||
"32000": "Falha ao iniciar o servidor MCP, verifique se todos os parâmetros foram preenchidos corretamente conforme o tutorial",
|
"32000": "Falha ao iniciar o servidor MCP, verifique se todos os parâmetros foram preenchidos corretamente conforme o tutorial",
|
||||||
"toolNotFound": "Ferramenta não encontrada {{name}}"
|
"toolNotFound": "Ferramenta não encontrada {{name}}"
|
||||||
},
|
},
|
||||||
|
"fetch": {
|
||||||
|
"button": "Buscar Servidores",
|
||||||
|
"success": "Servidores MCP obtidos com sucesso"
|
||||||
|
},
|
||||||
"findMore": "Mais servidores MCP",
|
"findMore": "Mais servidores MCP",
|
||||||
"headers": "Cabeçalhos da Requisição",
|
"headers": "Cabeçalhos da Requisição",
|
||||||
"headersTooltip": "Cabeçalhos HTTP personalizados para as requisições",
|
"headersTooltip": "Cabeçalhos HTTP personalizados para as requisições",
|
||||||
@@ -3786,6 +3831,7 @@
|
|||||||
"logoUrl": "URL do Logotipo",
|
"logoUrl": "URL do Logotipo",
|
||||||
"longRunning": "Modo de execução prolongada",
|
"longRunning": "Modo de execução prolongada",
|
||||||
"longRunningTooltip": "Quando ativado, o servidor suporta tarefas de longa duração, redefinindo o temporizador de tempo limite ao receber notificações de progresso e estendendo o tempo máximo de tempo limite para 10 minutos.",
|
"longRunningTooltip": "Quando ativado, o servidor suporta tarefas de longa duração, redefinindo o temporizador de tempo limite ao receber notificações de progresso e estendendo o tempo máximo de tempo limite para 10 minutos.",
|
||||||
|
"marketplaces": "Mercados",
|
||||||
"missingDependencies": "Ausente, instale para continuar",
|
"missingDependencies": "Ausente, instale para continuar",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "Lista selecionada de servidores MCP",
|
"awesome": "Lista selecionada de servidores MCP",
|
||||||
@@ -3834,6 +3880,7 @@
|
|||||||
"provider": "Fornecedor",
|
"provider": "Fornecedor",
|
||||||
"providerPlaceholder": "Nome do Fornecedor",
|
"providerPlaceholder": "Nome do Fornecedor",
|
||||||
"providerUrl": "URL do Fornecedor",
|
"providerUrl": "URL do Fornecedor",
|
||||||
|
"providers": "Fornecedores",
|
||||||
"registry": "Fonte de Gerenciamento de Pacotes",
|
"registry": "Fonte de Gerenciamento de Pacotes",
|
||||||
"registryDefault": "Padrão",
|
"registryDefault": "Padrão",
|
||||||
"registryTooltip": "Selecione uma fonte alternativa para instalar pacotes, caso tenha problemas de rede com a fonte padrão.",
|
"registryTooltip": "Selecione uma fonte alternativa para instalar pacotes, caso tenha problemas de rede com a fonte padrão.",
|
||||||
@@ -3856,6 +3903,7 @@
|
|||||||
"searchNpx": "Buscar MCP",
|
"searchNpx": "Buscar MCP",
|
||||||
"serverPlural": "Servidores",
|
"serverPlural": "Servidores",
|
||||||
"serverSingular": "Servidor",
|
"serverSingular": "Servidor",
|
||||||
|
"servers": "Servidores MCP",
|
||||||
"sse": "Eventos do Servidor (sse)",
|
"sse": "Eventos do Servidor (sse)",
|
||||||
"startError": "Falha ao Iniciar",
|
"startError": "Falha ao Iniciar",
|
||||||
"stdio": "Entrada/Saída Padrão (stdio)",
|
"stdio": "Entrada/Saída Padrão (stdio)",
|
||||||
@@ -4255,6 +4303,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "ID da chave de acesso da AWS",
|
"access_key_id": "ID da chave de acesso da AWS",
|
||||||
"access_key_id_help": "O seu ID da chave de acesso AWS, utilizado para aceder ao serviço AWS Bedrock",
|
"access_key_id_help": "O seu ID da chave de acesso AWS, utilizado para aceder ao serviço AWS Bedrock",
|
||||||
|
"api_key": "Chave de API do Bedrock",
|
||||||
|
"api_key_help": "Sua Chave de API AWS Bedrock para autenticação",
|
||||||
|
"auth_type": "Tipo de Autenticação",
|
||||||
|
"auth_type_api_key": "Chave de API do Bedrock",
|
||||||
|
"auth_type_help": "Escolha entre credenciais IAM ou autenticação por chave de API do Bedrock",
|
||||||
|
"auth_type_iam": "Credenciais IAM",
|
||||||
"description": "A AWS Bedrock é um serviço de modelos fundamentais totalmente gerido fornecido pela Amazon, que suporta diversos modelos avançados de linguagem.",
|
"description": "A AWS Bedrock é um serviço de modelos fundamentais totalmente gerido fornecido pela Amazon, que suporta diversos modelos avançados de linguagem.",
|
||||||
"region": "Região da AWS",
|
"region": "Região da AWS",
|
||||||
"region_help": "A sua região de serviço da AWS, por exemplo, us-east-1",
|
"region_help": "A sua região de serviço da AWS, por exemplo, us-east-1",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
|
"description": "Справляйтесь со сложными задачами с помощью различных инструментов",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "Не удалось добавить агента",
|
"failed": "Не удалось добавить агента",
|
||||||
"invalid_agent": "Недействительный агент"
|
"invalid_agent": "Недействительный агент"
|
||||||
@@ -338,6 +339,41 @@
|
|||||||
},
|
},
|
||||||
"title": "API Сервер"
|
"title": "API Сервер"
|
||||||
},
|
},
|
||||||
|
"appMenu": {
|
||||||
|
"about": "О",
|
||||||
|
"close": "Закрыть окно",
|
||||||
|
"copy": "Копировать",
|
||||||
|
"cut": "Резать",
|
||||||
|
"delete": "Удалить",
|
||||||
|
"documentation": "Документация",
|
||||||
|
"edit": "Редактировать",
|
||||||
|
"feedback": "Обратная связь",
|
||||||
|
"file": "Файл",
|
||||||
|
"forceReload": "Принудительная перезагрузка",
|
||||||
|
"front": "Показать все поверх других",
|
||||||
|
"help": "Помощь",
|
||||||
|
"hide": "Скрыть",
|
||||||
|
"hideOthers": "Скрыть остальные",
|
||||||
|
"minimize": "Минимизировать",
|
||||||
|
"paste": "Вставить",
|
||||||
|
"quit": "Выйти",
|
||||||
|
"redo": "Переделать",
|
||||||
|
"releases": "Релизы",
|
||||||
|
"reload": "Перезагрузка",
|
||||||
|
"resetZoom": "Настоящий размер",
|
||||||
|
"selectAll": "Выбрать все",
|
||||||
|
"services": "Услуги",
|
||||||
|
"toggleDevTools": "Переключить инструменты разработчика",
|
||||||
|
"toggleFullscreen": "Переключить полноэкранный режим",
|
||||||
|
"undo": "Отменить",
|
||||||
|
"unhide": "Показать все",
|
||||||
|
"view": "Вид",
|
||||||
|
"website": "Веб-сайт",
|
||||||
|
"window": "Окно",
|
||||||
|
"zoom": "Zoom",
|
||||||
|
"zoomIn": "Увеличить",
|
||||||
|
"zoomOut": "Отдалить"
|
||||||
|
},
|
||||||
"assistants": {
|
"assistants": {
|
||||||
"abbr": "Ассистент",
|
"abbr": "Ассистент",
|
||||||
"clear": {
|
"clear": {
|
||||||
@@ -547,8 +583,12 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
|
"description": "Ежедневные разговоры и быстрые вопросы и ответы",
|
||||||
"title": "Добавить ассистента"
|
"title": "Добавить ассистента"
|
||||||
},
|
},
|
||||||
|
"option": {
|
||||||
|
"title": "Выберите тип"
|
||||||
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "Новый топик"
|
"title": "Новый топик"
|
||||||
}
|
}
|
||||||
@@ -2477,6 +2517,7 @@
|
|||||||
"qiniu": "Qiniu AI",
|
"qiniu": "Qiniu AI",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "SiliconFlow",
|
"silicon": "SiliconFlow",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "StepFun",
|
"stepfun": "StepFun",
|
||||||
"tencent-cloud-ti": "Tencent Cloud TI",
|
"tencent-cloud-ti": "Tencent Cloud TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@@ -2923,15 +2964,14 @@
|
|||||||
},
|
},
|
||||||
"description": "Мощный AI-ассистент для созидания",
|
"description": "Мощный AI-ассистент для созидания",
|
||||||
"downloading": "Загрузка...",
|
"downloading": "Загрузка...",
|
||||||
|
"enterprise": {
|
||||||
|
"title": "Предприятие"
|
||||||
|
},
|
||||||
"feedback": {
|
"feedback": {
|
||||||
"button": "Обратная связь",
|
"button": "Обратная связь",
|
||||||
"title": "Обратная связь"
|
"title": "Обратная связь"
|
||||||
},
|
},
|
||||||
"label": "О программе и обратная связь",
|
"label": "О программе и обратная связь",
|
||||||
"license": {
|
|
||||||
"button": "Лицензия",
|
|
||||||
"title": "Лицензия"
|
|
||||||
},
|
|
||||||
"releases": {
|
"releases": {
|
||||||
"button": "Релизы",
|
"button": "Релизы",
|
||||||
"title": "Заметки о релизах"
|
"title": "Заметки о релизах"
|
||||||
@@ -3043,7 +3083,7 @@
|
|||||||
"confirm": {
|
"confirm": {
|
||||||
"button": "Выберите файл резервной копии"
|
"button": "Выберите файл резервной копии"
|
||||||
},
|
},
|
||||||
"content": "Экспорт части данных, включая чат и настройки. Пожалуйста, обратите внимание, что процесс резервного копирования может занять некоторое время. Благодарим за ваше терпение.",
|
"content": "Экспорт части данных, включая историю чатов и настройки. Обратите внимание, процесс резервного копирования может занять некоторое время, благодарим за ваше терпение.",
|
||||||
"lan": {
|
"lan": {
|
||||||
"auto_close_tip": "Автоматическое закрытие через {{seconds}} секунд...",
|
"auto_close_tip": "Автоматическое закрытие через {{seconds}} секунд...",
|
||||||
"confirm_close_message": "Передача файла в процессе. Закрытие прервет передачу. Вы уверены, что хотите принудительно закрыть?",
|
"confirm_close_message": "Передача файла в процессе. Закрытие прервет передачу. Вы уверены, что хотите принудительно закрыть?",
|
||||||
@@ -3761,6 +3801,7 @@
|
|||||||
"description": "Не включать функциональность сервера MCP",
|
"description": "Не включать функциональность сервера MCP",
|
||||||
"label": "Отключить сервер MCP"
|
"label": "Отключить сервер MCP"
|
||||||
},
|
},
|
||||||
|
"discover": "Откройте",
|
||||||
"duplicateName": "Сервер с таким именем уже существует",
|
"duplicateName": "Сервер с таким именем уже существует",
|
||||||
"editJson": "Редактировать JSON",
|
"editJson": "Редактировать JSON",
|
||||||
"editMcpJson": "Редактировать MCP",
|
"editMcpJson": "Редактировать MCP",
|
||||||
@@ -3771,6 +3812,10 @@
|
|||||||
"32000": "MCP сервер не запущен, пожалуйста, проверьте параметры",
|
"32000": "MCP сервер не запущен, пожалуйста, проверьте параметры",
|
||||||
"toolNotFound": "Инструмент {{name}} не найден"
|
"toolNotFound": "Инструмент {{name}} не найден"
|
||||||
},
|
},
|
||||||
|
"fetch": {
|
||||||
|
"button": "Получить серверы",
|
||||||
|
"success": "Успешно получены MCP-серверы"
|
||||||
|
},
|
||||||
"findMore": "Найти больше MCP",
|
"findMore": "Найти больше MCP",
|
||||||
"headers": "Заголовки",
|
"headers": "Заголовки",
|
||||||
"headersTooltip": "Пользовательские заголовки для HTTP-запросов",
|
"headersTooltip": "Пользовательские заголовки для HTTP-запросов",
|
||||||
@@ -3786,6 +3831,7 @@
|
|||||||
"logoUrl": "URL логотипа",
|
"logoUrl": "URL логотипа",
|
||||||
"longRunning": "Длительный режим работы",
|
"longRunning": "Длительный режим работы",
|
||||||
"longRunningTooltip": "Включив эту опцию, сервер будет поддерживать длительные задачи. При получении уведомлений о ходе выполнения будет сброшен тайм-аут и максимальное время выполнения будет увеличено до 10 минут.",
|
"longRunningTooltip": "Включив эту опцию, сервер будет поддерживать длительные задачи. При получении уведомлений о ходе выполнения будет сброшен тайм-аут и максимальное время выполнения будет увеличено до 10 минут.",
|
||||||
|
"marketplaces": "Торговые площадки",
|
||||||
"missingDependencies": "отсутствует, пожалуйста, установите для продолжения.",
|
"missingDependencies": "отсутствует, пожалуйста, установите для продолжения.",
|
||||||
"more": {
|
"more": {
|
||||||
"awesome": "Кураторский список серверов MCP",
|
"awesome": "Кураторский список серверов MCP",
|
||||||
@@ -3834,6 +3880,7 @@
|
|||||||
"provider": "Провайдер",
|
"provider": "Провайдер",
|
||||||
"providerPlaceholder": "Имя провайдера",
|
"providerPlaceholder": "Имя провайдера",
|
||||||
"providerUrl": "URL провайдера",
|
"providerUrl": "URL провайдера",
|
||||||
|
"providers": "Поставщики",
|
||||||
"registry": "Реестр пакетов",
|
"registry": "Реестр пакетов",
|
||||||
"registryDefault": "По умолчанию",
|
"registryDefault": "По умолчанию",
|
||||||
"registryTooltip": "Выберите реестр для установки пакетов, если возникают проблемы с сетью при использовании реестра по умолчанию.",
|
"registryTooltip": "Выберите реестр для установки пакетов, если возникают проблемы с сетью при использовании реестра по умолчанию.",
|
||||||
@@ -3856,6 +3903,7 @@
|
|||||||
"searchNpx": "Найти MCP",
|
"searchNpx": "Найти MCP",
|
||||||
"serverPlural": "серверы",
|
"serverPlural": "серверы",
|
||||||
"serverSingular": "сервер",
|
"serverSingular": "сервер",
|
||||||
|
"servers": "Серверы MCP",
|
||||||
"sse": "События, отправляемые сервером (sse)",
|
"sse": "События, отправляемые сервером (sse)",
|
||||||
"startError": "Запуск не удалось",
|
"startError": "Запуск не удалось",
|
||||||
"stdio": "Стандартный ввод/вывод (stdio)",
|
"stdio": "Стандартный ввод/вывод (stdio)",
|
||||||
@@ -4255,6 +4303,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "AWS Ключ доступа ID",
|
"access_key_id": "AWS Ключ доступа ID",
|
||||||
"access_key_id_help": "Ваш AWS Ключ доступа ID для доступа к AWS Bedrock",
|
"access_key_id_help": "Ваш AWS Ключ доступа ID для доступа к AWS Bedrock",
|
||||||
|
"api_key": "Ключ API Bedrock",
|
||||||
|
"api_key_help": "Ваш ключ API AWS Bedrock для аутентификации",
|
||||||
|
"auth_type": "Тип аутентификации",
|
||||||
|
"auth_type_api_key": "Ключ API Bedrock",
|
||||||
|
"auth_type_help": "Выберите между аутентификацией с помощью учетных данных IAM или ключа API Bedrock",
|
||||||
|
"auth_type_iam": "Учетные данные IAM",
|
||||||
"description": "AWS Bedrock — это полное управляемое сервисное предложение для моделей, поддерживающее различные современные модели языка",
|
"description": "AWS Bedrock — это полное управляемое сервисное предложение для моделей, поддерживающее различные современные модели языка",
|
||||||
"region": "AWS регион",
|
"region": "AWS регион",
|
||||||
"region_help": "Ваш регион AWS, например us-east-1",
|
"region_help": "Ваш регион AWS, например us-east-1",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Alert } from '@heroui/react'
|
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import type { ContentSearchRef } from '@renderer/components/ContentSearch'
|
import type { ContentSearchRef } from '@renderer/components/ContentSearch'
|
||||||
import { ContentSearch } from '@renderer/components/ContentSearch'
|
import { ContentSearch } from '@renderer/components/ContentSearch'
|
||||||
@@ -17,7 +16,7 @@ import { useTimer } from '@renderer/hooks/useTimer'
|
|||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
import type { Assistant, Topic } from '@renderer/types'
|
import type { Assistant, Topic } from '@renderer/types'
|
||||||
import { classNames } from '@renderer/utils'
|
import { classNames } from '@renderer/utils'
|
||||||
import { Flex } from 'antd'
|
import { Alert, Flex } from 'antd'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import { AnimatePresence, motion } from 'motion/react'
|
import { AnimatePresence, motion } from 'motion/react'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
@@ -170,11 +169,7 @@ const Chat: FC<Props> = (props) => {
|
|||||||
return () => <div> Active Session ID is invalid.</div>
|
return () => <div> Active Session ID is invalid.</div>
|
||||||
}
|
}
|
||||||
if (!apiServer.enabled) {
|
if (!apiServer.enabled) {
|
||||||
return () => (
|
return () => <Alert type="warning" message={t('agent.warning.enable_server')} style={{ margin: '5px 16px' }} />
|
||||||
<div>
|
|
||||||
<Alert color="warning" title={t('agent.warning.enable_server')} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return () => <AgentSessionMessages agentId={activeAgentId} sessionId={activeSessionId} />
|
return () => <AgentSessionMessages agentId={activeAgentId} sessionId={activeSessionId} />
|
||||||
}, [activeAgentId, activeSessionId, apiServer.enabled, t])
|
}, [activeAgentId, activeSessionId, apiServer.enabled, t])
|
||||||
@@ -191,22 +186,14 @@ const Chat: FC<Props> = (props) => {
|
|||||||
|
|
||||||
// TODO: more info
|
// TODO: more info
|
||||||
const AgentInvalid = useCallback(() => {
|
const AgentInvalid = useCallback(() => {
|
||||||
return (
|
return <Alert type="warning" message="Select an agent" style={{ margin: '5px 16px' }} />
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
|
||||||
<div>
|
|
||||||
<Alert color="warning" title="Select an agent" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// TODO: more info
|
// TODO: more info
|
||||||
const SessionInvalid = useCallback(() => {
|
const SessionInvalid = useCallback(() => {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center">
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
<div>
|
<Alert type="warning" message="Create a session" style={{ margin: '5px 16px' }} />
|
||||||
<Alert color="warning" title="Create a session" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}, [])
|
}, [])
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Tooltip } from '@heroui/react'
|
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { ActionIconButton } from '@renderer/components/Buttons'
|
import { ActionIconButton } from '@renderer/components/Buttons'
|
||||||
import { QuickPanelView } from '@renderer/components/QuickPanel'
|
import { QuickPanelView } from '@renderer/components/QuickPanel'
|
||||||
@@ -11,6 +10,7 @@ import { useShortcutDisplay } from '@renderer/hooks/useShortcuts'
|
|||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import PasteService from '@renderer/services/PasteService'
|
import PasteService from '@renderer/services/PasteService'
|
||||||
import { pauseTrace } from '@renderer/services/SpanManagerService'
|
import { pauseTrace } from '@renderer/services/SpanManagerService'
|
||||||
|
import { estimateUserPromptUsage } from '@renderer/services/TokenService'
|
||||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import { newMessagesActions, selectMessagesForTopic } from '@renderer/store/newMessage'
|
import { newMessagesActions, selectMessagesForTopic } from '@renderer/store/newMessage'
|
||||||
import { sendMessage as dispatchSendMessage } from '@renderer/store/thunk/messageThunk'
|
import { sendMessage as dispatchSendMessage } from '@renderer/store/thunk/messageThunk'
|
||||||
@@ -22,6 +22,7 @@ import { abortCompletion } from '@renderer/utils/abortController'
|
|||||||
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
|
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
|
||||||
import { getSendMessageShortcutLabel, isSendMessageKeyPressed } from '@renderer/utils/input'
|
import { getSendMessageShortcutLabel, isSendMessageKeyPressed } from '@renderer/utils/input'
|
||||||
import { createMainTextBlock, createMessage } from '@renderer/utils/messageUtils/create'
|
import { createMainTextBlock, createMessage } from '@renderer/utils/messageUtils/create'
|
||||||
|
import { Tooltip } from 'antd'
|
||||||
import type { TextAreaRef } from 'antd/es/input/TextArea'
|
import type { TextAreaRef } from 'antd/es/input/TextArea'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
@@ -199,11 +200,15 @@ const AgentSessionInputbar: FC<Props> = ({ agentId, sessionId }) => {
|
|||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
|
// Calculate token usage for the user message
|
||||||
|
const usage = await estimateUserPromptUsage({ content: text })
|
||||||
|
|
||||||
const userMessage: Message = createMessage('user', sessionTopicId, agentId, {
|
const userMessage: Message = createMessage('user', sessionTopicId, agentId, {
|
||||||
id: userMessageId,
|
id: userMessageId,
|
||||||
blocks: userMessageBlocks.map((block) => block?.id),
|
blocks: userMessageBlocks.map((block) => block?.id),
|
||||||
model,
|
model,
|
||||||
modelId: model?.id
|
modelId: model?.id,
|
||||||
|
usage
|
||||||
})
|
})
|
||||||
|
|
||||||
const assistantStub: Assistant = {
|
const assistantStub: Assistant = {
|
||||||
@@ -309,7 +314,7 @@ const AgentSessionInputbar: FC<Props> = ({ agentId, sessionId }) => {
|
|||||||
/>
|
/>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<ToolbarGroup>
|
<ToolbarGroup>
|
||||||
<Tooltip placement="top" content={t('chat.input.new_topic', { Command: newTopicShortcut })} delay={0}>
|
<Tooltip placement="top" title={t('chat.input.new_topic', { Command: newTopicShortcut })}>
|
||||||
<ActionIconButton
|
<ActionIconButton
|
||||||
onClick={handleCreateSession}
|
onClick={handleCreateSession}
|
||||||
disabled={createSessionDisabled}
|
disabled={createSessionDisabled}
|
||||||
@@ -321,7 +326,7 @@ const AgentSessionInputbar: FC<Props> = ({ agentId, sessionId }) => {
|
|||||||
<ToolbarGroup>
|
<ToolbarGroup>
|
||||||
<SendMessageButton sendMessage={sendMessage} disabled={sendDisabled} />
|
<SendMessageButton sendMessage={sendMessage} disabled={sendDisabled} />
|
||||||
{canAbort && (
|
{canAbort && (
|
||||||
<Tooltip placement="top" content={t('chat.input.pause')}>
|
<Tooltip placement="top" title={t('chat.input.pause')}>
|
||||||
<ActionIconButton onClick={abortAgentSession} style={{ marginRight: -2 }}>
|
<ActionIconButton onClick={abortAgentSession} style={{ marginRight: -2 }}>
|
||||||
<CirclePause size={20} color="var(--color-error)" />
|
<CirclePause size={20} color="var(--color-error)" />
|
||||||
</ActionIconButton>
|
</ActionIconButton>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { DropResult } from '@hello-pangea/dnd'
|
|||||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
|
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { ActionIconButton } from '@renderer/components/Buttons'
|
import { ActionIconButton } from '@renderer/components/Buttons'
|
||||||
|
import { MdiLightbulbOn } from '@renderer/components/Icons'
|
||||||
import type { QuickPanelListItem } from '@renderer/components/QuickPanel'
|
import type { QuickPanelListItem } from '@renderer/components/QuickPanel'
|
||||||
import {
|
import {
|
||||||
isAnthropicModel,
|
isAnthropicModel,
|
||||||
@@ -230,6 +231,15 @@ const InputbarTools = ({
|
|||||||
quickPhrasesButtonRef.current?.openQuickPanel()
|
quickPhrasesButtonRef.current?.openQuickPanel()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t('assistants.settings.reasoning_effort.label'),
|
||||||
|
description: '',
|
||||||
|
icon: <MdiLightbulbOn />,
|
||||||
|
isMenu: true,
|
||||||
|
action: () => {
|
||||||
|
thinkingButtonRef.current?.openQuickPanel()
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t('assistants.presets.edit.model.select.title'),
|
label: t('assistants.presets.edit.model.select.title'),
|
||||||
description: '',
|
description: '',
|
||||||
@@ -245,6 +255,7 @@ const InputbarTools = ({
|
|||||||
icon: <FileSearch />,
|
icon: <FileSearch />,
|
||||||
isMenu: true,
|
isMenu: true,
|
||||||
disabled: files.length > 0,
|
disabled: files.length > 0,
|
||||||
|
hidden: !showKnowledgeBaseButton,
|
||||||
action: () => {
|
action: () => {
|
||||||
knowledgeBaseButtonRef.current?.openQuickPanel()
|
knowledgeBaseButtonRef.current?.openQuickPanel()
|
||||||
}
|
}
|
||||||
@@ -312,7 +323,7 @@ const InputbarTools = ({
|
|||||||
translate()
|
translate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
] satisfies QuickPanelListItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDragEnd = (result: DropResult) => {
|
const handleDragEnd = (result: DropResult) => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { Button } from '@heroui/button'
|
|
||||||
import CodeViewer from '@renderer/components/CodeViewer'
|
import CodeViewer from '@renderer/components/CodeViewer'
|
||||||
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
|
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
@@ -33,6 +32,7 @@ import {
|
|||||||
} from '@renderer/types/error'
|
} from '@renderer/types/error'
|
||||||
import type { ErrorMessageBlock, Message } from '@renderer/types/newMessage'
|
import type { ErrorMessageBlock, Message } from '@renderer/types/newMessage'
|
||||||
import { formatAiSdkError, formatError, safeToString } from '@renderer/utils/error'
|
import { formatAiSdkError, formatError, safeToString } from '@renderer/utils/error'
|
||||||
|
import { Button } from 'antd'
|
||||||
import { Alert as AntdAlert, Modal } from 'antd'
|
import { Alert as AntdAlert, Modal } from 'antd'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
@@ -144,9 +144,11 @@ const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock; message: Message }>
|
|||||||
onClick={showErrorDetail}
|
onClick={showErrorDetail}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer' }}
|
||||||
action={
|
action={
|
||||||
<Button size="sm" className="p-0" variant="light" onPress={showErrorDetail}>
|
<>
|
||||||
{t('common.detail')}
|
<Button size="middle" color="default" variant="text" onClick={showErrorDetail}>
|
||||||
</Button>
|
{t('common.detail')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ErrorDetailModal open={showDetailModal} onClose={() => setShowDetailModal(false)} error={block.error} />
|
<ErrorDetailModal open={showDetailModal} onClose={() => setShowDetailModal(false)} error={block.error} />
|
||||||
@@ -198,10 +200,10 @@ const ErrorDetailModal: React.FC<ErrorDetailModalProps> = ({ open, onClose, erro
|
|||||||
open={open}
|
open={open}
|
||||||
onCancel={onClose}
|
onCancel={onClose}
|
||||||
footer={[
|
footer={[
|
||||||
<Button key="copy" size="sm" variant="light" onPress={copyErrorDetails}>
|
<Button key="copy" variant="text" color="default" onClick={copyErrorDetails}>
|
||||||
{t('common.copy')}
|
{t('common.copy')}
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button key="close" size="sm" variant="light" onPress={onClose}>
|
<Button key="close" variant="text" color={'default'} onClick={onClose}>
|
||||||
{t('common.close')}
|
{t('common.close')}
|
||||||
</Button>
|
</Button>
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Spinner } from '@heroui/react'
|
|
||||||
import { MessageBlockStatus, MessageBlockType, type PlaceholderMessageBlock } from '@renderer/types/newMessage'
|
import { MessageBlockStatus, MessageBlockType, type PlaceholderMessageBlock } from '@renderer/types/newMessage'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { BeatLoader } from 'react-spinners'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
interface PlaceholderBlockProps {
|
interface PlaceholderBlockProps {
|
||||||
@@ -10,7 +10,7 @@ const PlaceholderBlock: React.FC<PlaceholderBlockProps> = ({ block }) => {
|
|||||||
if (block.status === MessageBlockStatus.PROCESSING && block.type === MessageBlockType.UNKNOWN) {
|
if (block.status === MessageBlockStatus.PROCESSING && block.type === MessageBlockType.UNKNOWN) {
|
||||||
return (
|
return (
|
||||||
<MessageContentLoading>
|
<MessageContentLoading>
|
||||||
<Spinner color="current" variant="dots" />
|
<BeatLoader color="var(--color-text-1)" size={8} speedMultiplier={0.8} />
|
||||||
</MessageContentLoading>
|
</MessageContentLoading>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { cn } from '@heroui/react'
|
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer'
|
import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer'
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
@@ -15,7 +14,7 @@ import { getModelUniqId } from '@renderer/services/ModelService'
|
|||||||
import { estimateMessageUsage } from '@renderer/services/TokenService'
|
import { estimateMessageUsage } from '@renderer/services/TokenService'
|
||||||
import type { Assistant, Topic } from '@renderer/types'
|
import type { Assistant, Topic } from '@renderer/types'
|
||||||
import type { Message, MessageBlock } from '@renderer/types/newMessage'
|
import type { Message, MessageBlock } from '@renderer/types/newMessage'
|
||||||
import { classNames } from '@renderer/utils'
|
import { classNames, cn } from '@renderer/utils'
|
||||||
import { isMessageProcessing } from '@renderer/utils/messageUtils/is'
|
import { isMessageProcessing } from '@renderer/utils/messageUtils/is'
|
||||||
import { Divider } from 'antd'
|
import { Divider } from 'antd'
|
||||||
import type { Dispatch, FC, SetStateAction } from 'react'
|
import type { Dispatch, FC, SetStateAction } from 'react'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { AccordionItem, Chip, Code } from '@heroui/react'
|
import type { CollapseProps } from 'antd'
|
||||||
|
import { Tag } from 'antd'
|
||||||
import { CheckCircle, Terminal, XCircle } from 'lucide-react'
|
import { CheckCircle, Terminal, XCircle } from 'lucide-react'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
@@ -15,7 +16,13 @@ interface ParsedBashOutput {
|
|||||||
tool_use_error?: string
|
tool_use_error?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BashOutputTool({ input, output }: { input: BashOutputToolInput; output?: BashOutputToolOutput }) {
|
export function BashOutputTool({
|
||||||
|
input,
|
||||||
|
output
|
||||||
|
}: {
|
||||||
|
input: BashOutputToolInput
|
||||||
|
output?: BashOutputToolOutput
|
||||||
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
// 解析 XML 输出
|
// 解析 XML 输出
|
||||||
const parsedOutput = useMemo(() => {
|
const parsedOutput = useMemo(() => {
|
||||||
if (!output) return null
|
if (!output) return null
|
||||||
@@ -84,93 +91,88 @@ export function BashOutputTool({ input, output }: { input: BashOutputToolInput;
|
|||||||
} as const
|
} as const
|
||||||
}, [parsedOutput])
|
}, [parsedOutput])
|
||||||
|
|
||||||
return (
|
const children = parsedOutput ? (
|
||||||
<AccordionItem
|
<div className="flex flex-col gap-4">
|
||||||
key={AgentToolsType.BashOutput}
|
{/* Status Info */}
|
||||||
aria-label="BashOutput Tool"
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
title={
|
{parsedOutput.exit_code !== undefined && (
|
||||||
|
<Tag color={parsedOutput.exit_code === 0 ? 'success' : 'danger'}>Exit Code: {parsedOutput.exit_code}</Tag>
|
||||||
|
)}
|
||||||
|
{parsedOutput.timestamp && (
|
||||||
|
<Tag className="py-0 font-mono text-xs">{new Date(parsedOutput.timestamp).toLocaleString()}</Tag>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Standard Output */}
|
||||||
|
{parsedOutput.stdout && (
|
||||||
|
<div>
|
||||||
|
<div className="mb-2 font-medium text-default-600 text-xs">stdout:</div>
|
||||||
|
<pre className="whitespace-pre-wrap font-mono text-default-700 text-xs dark:text-default-300">
|
||||||
|
{parsedOutput.stdout}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Standard Error */}
|
||||||
|
{parsedOutput.stderr && (
|
||||||
|
<div className="border border-danger-200">
|
||||||
|
<div className="mb-2 font-medium text-danger-600 text-xs">stderr:</div>
|
||||||
|
<pre className="whitespace-pre-wrap font-mono text-danger-600 text-xs dark:text-danger-400">
|
||||||
|
{parsedOutput.stderr}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Tool Use Error */}
|
||||||
|
{parsedOutput.tool_use_error && (
|
||||||
|
<div className="border border-danger-200">
|
||||||
|
<div className="mb-2 flex items-center gap-2">
|
||||||
|
<XCircle className="h-4 w-4 text-danger" />
|
||||||
|
<span className="font-medium text-danger-600 text-xs">Error:</span>
|
||||||
|
</div>
|
||||||
|
<pre className="whitespace-pre-wrap font-mono text-danger-600 text-xs dark:text-danger-400">
|
||||||
|
{parsedOutput.tool_use_error}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// 原始输出(如果解析失败或非 XML 格式)
|
||||||
|
output && (
|
||||||
|
<div>
|
||||||
|
<pre className="whitespace-pre-wrap font-mono text-default-700 text-xs dark:text-default-300">{output}</pre>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
key: AgentToolsType.BashOutput,
|
||||||
|
label: (
|
||||||
|
<>
|
||||||
<ToolTitle
|
<ToolTitle
|
||||||
icon={<Terminal className="h-4 w-4" />}
|
icon={<Terminal className="h-4 w-4" />}
|
||||||
label="Bash Output"
|
label="Bash Output"
|
||||||
params={
|
params={
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Code size="sm" className="py-0 text-xs">
|
<Tag className="py-0 font-mono text-xs">{input.bash_id}</Tag>
|
||||||
{input.bash_id}
|
|
||||||
</Code>
|
|
||||||
{statusConfig && (
|
{statusConfig && (
|
||||||
<Chip
|
<Tag
|
||||||
size="sm"
|
|
||||||
color={statusConfig.color}
|
color={statusConfig.color}
|
||||||
variant="flat"
|
icon={statusConfig.icon}
|
||||||
startContent={statusConfig.icon}
|
style={{
|
||||||
className="h-5">
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '2px'
|
||||||
|
}}>
|
||||||
{statusConfig.text}
|
{statusConfig.text}
|
||||||
</Chip>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
</>
|
||||||
classNames={{
|
),
|
||||||
content: 'space-y-3 px-1'
|
|
||||||
}}>
|
|
||||||
{parsedOutput ? (
|
|
||||||
<>
|
|
||||||
{/* Status Info */}
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
|
||||||
{parsedOutput.exit_code !== undefined && (
|
|
||||||
<Chip size="sm" color={parsedOutput.exit_code === 0 ? 'success' : 'danger'} variant="flat">
|
|
||||||
Exit Code: {parsedOutput.exit_code}
|
|
||||||
</Chip>
|
|
||||||
)}
|
|
||||||
{parsedOutput.timestamp && (
|
|
||||||
<Code size="sm" className="py-0 text-xs">
|
|
||||||
{new Date(parsedOutput.timestamp).toLocaleString()}
|
|
||||||
</Code>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Standard Output */}
|
children: children
|
||||||
{parsedOutput.stdout && (
|
}
|
||||||
<div>
|
|
||||||
<div className="mb-2 font-medium text-default-600 text-xs">stdout:</div>
|
|
||||||
<pre className="whitespace-pre-wrap font-mono text-default-700 text-xs dark:text-default-300">
|
|
||||||
{parsedOutput.stdout}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Standard Error */}
|
|
||||||
{parsedOutput.stderr && (
|
|
||||||
<div className="border border-danger-200">
|
|
||||||
<div className="mb-2 font-medium text-danger-600 text-xs">stderr:</div>
|
|
||||||
<pre className="whitespace-pre-wrap font-mono text-danger-600 text-xs dark:text-danger-400">
|
|
||||||
{parsedOutput.stderr}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Tool Use Error */}
|
|
||||||
{parsedOutput.tool_use_error && (
|
|
||||||
<div className="border border-danger-200">
|
|
||||||
<div className="mb-2 flex items-center gap-2">
|
|
||||||
<XCircle className="h-4 w-4 text-danger" />
|
|
||||||
<span className="font-medium text-danger-600 text-xs">Error:</span>
|
|
||||||
</div>
|
|
||||||
<pre className="whitespace-pre-wrap font-mono text-danger-600 text-xs dark:text-danger-400">
|
|
||||||
{parsedOutput.tool_use_error}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
// 原始输出(如果解析失败或非 XML 格式)
|
|
||||||
output && (
|
|
||||||
<div>
|
|
||||||
<pre className="whitespace-pre-wrap font-mono text-default-700 text-xs dark:text-default-300">{output}</pre>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</AccordionItem>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,35 @@
|
|||||||
import { AccordionItem, Code } from '@heroui/react'
|
import type { CollapseProps } from 'antd'
|
||||||
|
import { Tag } from 'antd'
|
||||||
import { Terminal } from 'lucide-react'
|
import { Terminal } from 'lucide-react'
|
||||||
|
|
||||||
import { ToolTitle } from './GenericTools'
|
import { ToolTitle } from './GenericTools'
|
||||||
import type { BashToolInput as BashToolInputType, BashToolOutput as BashToolOutputType } from './types'
|
import type { BashToolInput as BashToolInputType, BashToolOutput as BashToolOutputType } from './types'
|
||||||
|
|
||||||
export function BashTool({ input, output }: { input: BashToolInputType; output?: BashToolOutputType }) {
|
export function BashTool({
|
||||||
|
input,
|
||||||
|
output
|
||||||
|
}: {
|
||||||
|
input: BashToolInputType
|
||||||
|
output?: BashToolOutputType
|
||||||
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
// 如果有输出,计算输出行数
|
// 如果有输出,计算输出行数
|
||||||
const outputLines = output ? output.split('\n').length : 0
|
const outputLines = output ? output.split('\n').length : 0
|
||||||
|
|
||||||
return (
|
return {
|
||||||
<AccordionItem
|
key: 'tool',
|
||||||
key="tool"
|
label: (
|
||||||
aria-label="Bash Tool"
|
<>
|
||||||
title={
|
|
||||||
<ToolTitle
|
<ToolTitle
|
||||||
icon={<Terminal className="h-4 w-4" />}
|
icon={<Terminal className="h-4 w-4" />}
|
||||||
label="Bash"
|
label="Bash"
|
||||||
params={input.description}
|
params={input.description}
|
||||||
stats={output ? `${outputLines} ${outputLines === 1 ? 'line' : 'lines'}` : undefined}
|
stats={output ? `${outputLines} ${outputLines === 1 ? 'line' : 'lines'}` : undefined}
|
||||||
/>
|
/>
|
||||||
}
|
<div className="mt-1">
|
||||||
subtitle={
|
<Tag className="whitespace-pre-wrap break-all font-mono">{input.command}</Tag>
|
||||||
<Code size="sm" className="line-clamp-1 w-max max-w-full text-ellipsis py-0 text-xs">
|
</div>
|
||||||
{input.command}
|
</>
|
||||||
</Code>
|
),
|
||||||
}>
|
children: <div className="whitespace-pre-line">{output}</div>
|
||||||
<div className="whitespace-pre-line">{output}</div>
|
}
|
||||||
</AccordionItem>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AccordionItem } from '@heroui/react'
|
import type { CollapseProps } from 'antd'
|
||||||
import { FileEdit } from 'lucide-react'
|
import { FileEdit } from 'lucide-react'
|
||||||
|
|
||||||
import { ToolTitle } from './GenericTools'
|
import { ToolTitle } from './GenericTools'
|
||||||
@@ -28,19 +28,26 @@ export const renderCodeBlock = (content: string, variant: 'old' | 'new') => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EditTool({ input, output }: { input: EditToolInput; output?: EditToolOutput }) {
|
export function EditTool({
|
||||||
return (
|
input,
|
||||||
<AccordionItem
|
output
|
||||||
key={AgentToolsType.Edit}
|
}: {
|
||||||
aria-label="Edit Tool"
|
input: EditToolInput
|
||||||
title={<ToolTitle icon={<FileEdit className="h-4 w-4" />} label="Edit" params={input.file_path} />}>
|
output?: EditToolOutput
|
||||||
{/* Diff View */}
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
{/* Old Content */}
|
return {
|
||||||
{renderCodeBlock(input.old_string, 'old')}
|
key: AgentToolsType.Edit,
|
||||||
{/* New Content */}
|
label: <ToolTitle icon={<FileEdit className="h-4 w-4" />} label="Edit" params={input.file_path} />,
|
||||||
{renderCodeBlock(input.new_string, 'new')}
|
children: (
|
||||||
{/* Output */}
|
<>
|
||||||
{output}
|
{/* Diff View */}
|
||||||
</AccordionItem>
|
{/* Old Content */}
|
||||||
)
|
{renderCodeBlock(input.old_string, 'old')}
|
||||||
|
{/* New Content */}
|
||||||
|
{renderCodeBlock(input.new_string, 'new')}
|
||||||
|
{/* Output */}
|
||||||
|
{output}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AccordionItem } from '@heroui/react'
|
import type { CollapseProps } from 'antd'
|
||||||
import { DoorOpen } from 'lucide-react'
|
import { DoorOpen } from 'lucide-react'
|
||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from 'react-markdown'
|
||||||
|
|
||||||
@@ -6,19 +6,22 @@ import { ToolTitle } from './GenericTools'
|
|||||||
import type { ExitPlanModeToolInput, ExitPlanModeToolOutput } from './types'
|
import type { ExitPlanModeToolInput, ExitPlanModeToolOutput } from './types'
|
||||||
import { AgentToolsType } from './types'
|
import { AgentToolsType } from './types'
|
||||||
|
|
||||||
export function ExitPlanModeTool({ input, output }: { input: ExitPlanModeToolInput; output?: ExitPlanModeToolOutput }) {
|
export function ExitPlanModeTool({
|
||||||
return (
|
input,
|
||||||
<AccordionItem
|
output
|
||||||
key={AgentToolsType.ExitPlanMode}
|
}: {
|
||||||
aria-label="ExitPlanMode Tool"
|
input: ExitPlanModeToolInput
|
||||||
title={
|
output?: ExitPlanModeToolOutput
|
||||||
<ToolTitle
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
icon={<DoorOpen className="h-4 w-4" />}
|
return {
|
||||||
label="ExitPlanMode"
|
key: AgentToolsType.ExitPlanMode,
|
||||||
stats={`${input.plan.split('\n\n').length} plans`}
|
label: (
|
||||||
/>
|
<ToolTitle
|
||||||
}>
|
icon={<DoorOpen className="h-4 w-4" />}
|
||||||
{<ReactMarkdown>{input.plan + '\n\n' + (output ?? '')}</ReactMarkdown>}
|
label="ExitPlanMode"
|
||||||
</AccordionItem>
|
stats={`${input.plan.split('\n\n').length} plans`}
|
||||||
)
|
/>
|
||||||
|
),
|
||||||
|
children: <ReactMarkdown>{input.plan + '\n\n' + (output ?? '')}</ReactMarkdown>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
import { AccordionItem } from '@heroui/react'
|
import type { CollapseProps } from 'antd'
|
||||||
import { FolderSearch } from 'lucide-react'
|
import { FolderSearch } from 'lucide-react'
|
||||||
|
|
||||||
import { ToolTitle } from './GenericTools'
|
import { ToolTitle } from './GenericTools'
|
||||||
import type { GlobToolInput as GlobToolInputType, GlobToolOutput as GlobToolOutputType } from './types'
|
import type { GlobToolInput as GlobToolInputType, GlobToolOutput as GlobToolOutputType } from './types'
|
||||||
|
|
||||||
export function GlobTool({ input, output }: { input: GlobToolInputType; output?: GlobToolOutputType }) {
|
export function GlobTool({
|
||||||
|
input,
|
||||||
|
output
|
||||||
|
}: {
|
||||||
|
input: GlobToolInputType
|
||||||
|
output?: GlobToolOutputType
|
||||||
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
// 如果有输出,计算文件数量
|
// 如果有输出,计算文件数量
|
||||||
const lineCount = output ? output.split('\n').filter((line) => line.trim()).length : 0
|
const lineCount = output ? output.split('\n').filter((line) => line.trim()).length : 0
|
||||||
|
|
||||||
return (
|
return {
|
||||||
<AccordionItem
|
key: 'tool',
|
||||||
key="tool"
|
label: (
|
||||||
aria-label="Glob Tool"
|
<ToolTitle
|
||||||
title={
|
icon={<FolderSearch className="h-4 w-4" />}
|
||||||
<ToolTitle
|
label="Glob"
|
||||||
icon={<FolderSearch className="h-4 w-4" />}
|
params={input.pattern}
|
||||||
label="Glob"
|
stats={output ? `${lineCount} ${lineCount === 1 ? 'file' : 'files'}` : undefined}
|
||||||
params={input.pattern}
|
/>
|
||||||
stats={output ? `${lineCount} of output` : undefined}
|
),
|
||||||
/>
|
children: <div>{output}</div>
|
||||||
}>
|
}
|
||||||
<div>{output}</div>
|
|
||||||
</AccordionItem>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,34 @@
|
|||||||
import { AccordionItem } from '@heroui/react'
|
import type { CollapseProps } from 'antd'
|
||||||
import { FileSearch } from 'lucide-react'
|
import { FileSearch } from 'lucide-react'
|
||||||
|
|
||||||
import { ToolTitle } from './GenericTools'
|
import { ToolTitle } from './GenericTools'
|
||||||
import type { GrepToolInput, GrepToolOutput } from './types'
|
import type { GrepToolInput, GrepToolOutput } from './types'
|
||||||
|
|
||||||
export function GrepTool({ input, output }: { input: GrepToolInput; output?: GrepToolOutput }) {
|
export function GrepTool({
|
||||||
|
input,
|
||||||
|
output
|
||||||
|
}: {
|
||||||
|
input: GrepToolInput
|
||||||
|
output?: GrepToolOutput
|
||||||
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
// 如果有输出,计算结果行数
|
// 如果有输出,计算结果行数
|
||||||
const resultLines = output ? output.split('\n').filter((line) => line.trim()).length : 0
|
const resultLines = output ? output.split('\n').filter((line) => line.trim()).length : 0
|
||||||
|
|
||||||
return (
|
return {
|
||||||
<AccordionItem
|
key: 'tool',
|
||||||
key="tool"
|
label: (
|
||||||
aria-label="Grep Tool"
|
<ToolTitle
|
||||||
title={
|
icon={<FileSearch className="h-4 w-4" />}
|
||||||
<ToolTitle
|
label="Grep"
|
||||||
icon={<FileSearch className="h-4 w-4" />}
|
params={
|
||||||
label="Grep"
|
<>
|
||||||
params={
|
{input.pattern}
|
||||||
<>
|
{input.output_mode && <span className="ml-1">({input.output_mode})</span>}
|
||||||
{input.pattern}
|
</>
|
||||||
{input.output_mode && <span className="ml-1">({input.output_mode})</span>}
|
}
|
||||||
</>
|
stats={output ? `${resultLines} ${resultLines === 1 ? 'line' : 'lines'}` : undefined}
|
||||||
}
|
/>
|
||||||
stats={output ? `${resultLines} ${resultLines === 1 ? 'line' : 'lines'}` : undefined}
|
),
|
||||||
/>
|
children: <div>{output}</div>
|
||||||
}>
|
}
|
||||||
<div>{output}</div>
|
|
||||||
</AccordionItem>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user