mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-12 00:49:14 +08:00
Merge remote-tracking branch 'origin/main' into feat/proxy-api-server
This commit is contained in:
commit
4c43cca740
29
.github/workflows/auto-i18n.yml
vendored
29
.github/workflows/auto-i18n.yml
vendored
@ -32,38 +32,37 @@ jobs:
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: 📦 Install corepack
|
||||
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||
- name: 📦 Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: 📂 Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
- name: 📂 Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 💾 Cache yarn dependencies
|
||||
- name: 💾 Cache pnpm dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
node_modules
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: 📦 Install dependencies
|
||||
run: |
|
||||
yarn install
|
||||
pnpm install
|
||||
|
||||
- name: 🏃♀️ Translate
|
||||
run: yarn i18n:sync && yarn i18n:translate
|
||||
run: pnpm i18n:sync && pnpm i18n:translate
|
||||
|
||||
- name: 🔍 Format
|
||||
run: yarn format
|
||||
run: pnpm format
|
||||
|
||||
- name: 🔍 Check for changes
|
||||
id: git_status
|
||||
run: |
|
||||
# Check if there are any uncommitted changes
|
||||
git reset -- package.json yarn.lock # 不提交 package.json 和 yarn.lock 的更改
|
||||
git reset -- package.json pnpm-lock.yaml # 不提交 package.json 和 pnpm-lock.yaml 的更改
|
||||
git diff --exit-code --quiet || echo "::set-output name=has_changes::true"
|
||||
git status --porcelain
|
||||
|
||||
|
||||
29
.github/workflows/nightly-build.yml
vendored
29
.github/workflows/nightly-build.yml
vendored
@ -65,25 +65,24 @@ jobs:
|
||||
run: |
|
||||
brew install python-setuptools
|
||||
|
||||
- name: Install corepack
|
||||
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache yarn dependencies
|
||||
- name: Cache pnpm dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
node_modules
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn install
|
||||
run: pnpm install
|
||||
|
||||
- name: Generate date tag
|
||||
id: date
|
||||
@ -94,7 +93,7 @@ jobs:
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get install -y rpm
|
||||
yarn build:linux
|
||||
pnpm build:linux
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
@ -106,7 +105,7 @@ jobs:
|
||||
- name: Build Mac
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
yarn build:mac
|
||||
pnpm build:mac
|
||||
env:
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
@ -123,7 +122,7 @@ jobs:
|
||||
- name: Build Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
yarn build:win
|
||||
pnpm build:win
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
|
||||
33
.github/workflows/pr-ci.yml
vendored
33
.github/workflows/pr-ci.yml
vendored
@ -28,37 +28,36 @@ jobs:
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install corepack
|
||||
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache yarn dependencies
|
||||
- name: Cache pnpm dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
node_modules
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn install
|
||||
run: pnpm install
|
||||
|
||||
- name: Lint Check
|
||||
run: yarn test:lint
|
||||
run: pnpm test:lint
|
||||
|
||||
- name: Format Check
|
||||
run: yarn format:check
|
||||
run: pnpm format:check
|
||||
|
||||
- name: Type Check
|
||||
run: yarn typecheck
|
||||
run: pnpm typecheck
|
||||
|
||||
- name: i18n Check
|
||||
run: yarn i18n:check
|
||||
run: pnpm i18n:check
|
||||
|
||||
- name: Test
|
||||
run: yarn test
|
||||
run: pnpm test
|
||||
|
||||
29
.github/workflows/release.yml
vendored
29
.github/workflows/release.yml
vendored
@ -56,31 +56,30 @@ jobs:
|
||||
run: |
|
||||
brew install python-setuptools
|
||||
|
||||
- name: Install corepack
|
||||
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache yarn dependencies
|
||||
- name: Cache pnpm dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
node_modules
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn install
|
||||
run: pnpm install
|
||||
|
||||
- name: Build Linux
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get install -y rpm
|
||||
yarn build:linux
|
||||
pnpm build:linux
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@ -94,7 +93,7 @@ jobs:
|
||||
if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
sudo -H pip install setuptools
|
||||
yarn build:mac
|
||||
pnpm build:mac
|
||||
env:
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
@ -111,7 +110,7 @@ jobs:
|
||||
- name: Build Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
yarn build:win
|
||||
pnpm build:win
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
|
||||
9
.github/workflows/sync-to-gitcode.yml
vendored
9
.github/workflows/sync-to-gitcode.yml
vendored
@ -48,9 +48,8 @@ jobs:
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install corepack
|
||||
shell: bash
|
||||
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Clean node_modules
|
||||
if: ${{ github.event.inputs.clean == 'true' }}
|
||||
@ -59,11 +58,11 @@ jobs:
|
||||
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
run: yarn install
|
||||
run: pnpm install
|
||||
|
||||
- name: Build Windows with code signing
|
||||
shell: bash
|
||||
run: yarn build:win
|
||||
run: pnpm build:win
|
||||
env:
|
||||
WIN_SIGN: true
|
||||
CHERRY_CERT_PATH: ${{ secrets.CHERRY_CERT_PATH }}
|
||||
|
||||
@ -154,14 +154,14 @@ jobs:
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Enable Corepack
|
||||
- name: Install pnpm
|
||||
if: steps.check.outputs.should_run == 'true'
|
||||
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.check.outputs.should_run == 'true'
|
||||
working-directory: main
|
||||
run: yarn install --immutable
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Update upgrade config
|
||||
if: steps.check.outputs.should_run == 'true'
|
||||
@ -170,7 +170,7 @@ jobs:
|
||||
RELEASE_TAG: ${{ steps.meta.outputs.tag }}
|
||||
IS_PRERELEASE: ${{ steps.check.outputs.is_prerelease }}
|
||||
run: |
|
||||
yarn tsx scripts/update-app-upgrade-config.ts \
|
||||
pnpm tsx scripts/update-app-upgrade-config.ts \
|
||||
--tag "$RELEASE_TAG" \
|
||||
--config ../cs/app-upgrade-config.json \
|
||||
--is-prerelease "$IS_PRERELEASE"
|
||||
|
||||
@ -1 +1 @@
|
||||
yarn lint-staged
|
||||
pnpm lint-staged
|
||||
|
||||
2
.npmrc
2
.npmrc
@ -1 +1 @@
|
||||
electron_mirror=https://npmmirror.com/mirrors/electron/
|
||||
electron_mirror=https://npmmirror.com/mirrors/electron/
|
||||
|
||||
BIN
.yarn/releases/yarn-4.9.1.cjs
vendored
BIN
.yarn/releases/yarn-4.9.1.cjs
vendored
Binary file not shown.
@ -1,9 +0,0 @@
|
||||
enableImmutableInstalls: false
|
||||
|
||||
httpTimeout: 300000
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.9.1.cjs
|
||||
npmRegistryServer: https://registry.npmjs.org
|
||||
npmPublishRegistry: https://registry.npmjs.org
|
||||
26
CLAUDE.md
26
CLAUDE.md
@ -10,7 +10,7 @@ This file provides guidance to AI coding assistants when working with code in th
|
||||
- **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.
|
||||
- **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.
|
||||
- **Lint, test, and format before completion**: Coding tasks are only complete after running `yarn lint`, `yarn test`, and `yarn format` successfully.
|
||||
- **Lint, test, and format before completion**: Coding tasks are only complete after running `pnpm lint`, `pnpm test`, and `pnpm format` successfully.
|
||||
- **Write conventional commits**: Commit small, focused changes using Conventional Commit messages (e.g., `feat:`, `fix:`, `refactor:`, `docs:`).
|
||||
|
||||
## Pull Request Workflow (CRITICAL)
|
||||
@ -24,18 +24,18 @@ When creating a Pull Request, you MUST:
|
||||
|
||||
## Development Commands
|
||||
|
||||
- **Install**: `yarn install` - Install all project dependencies
|
||||
- **Development**: `yarn dev` - Runs Electron app in development mode with hot reload
|
||||
- **Debug**: `yarn debug` - Starts with debugging enabled, use `chrome://inspect` to attach debugger
|
||||
- **Build Check**: `yarn build:check` - **REQUIRED** before commits (lint + test + typecheck)
|
||||
- If having i18n sort issues, run `yarn i18n:sync` first to sync template
|
||||
- If having formatting issues, run `yarn format` first
|
||||
- **Test**: `yarn test` - Run all tests (Vitest) across main and renderer processes
|
||||
- **Install**: `pnpm install` - Install all project dependencies
|
||||
- **Development**: `pnpm dev` - Runs Electron app in development mode with hot reload
|
||||
- **Debug**: `pnpm debug` - Starts with debugging enabled, use `chrome://inspect` to attach debugger
|
||||
- **Build Check**: `pnpm build:check` - **REQUIRED** before commits (lint + test + typecheck)
|
||||
- If having i18n sort issues, run `pnpm i18n:sync` first to sync template
|
||||
- If having formatting issues, run `pnpm format` first
|
||||
- **Test**: `pnpm test` - Run all tests (Vitest) across main and renderer processes
|
||||
- **Single Test**:
|
||||
- `yarn test:main` - Run tests for main process only
|
||||
- `yarn test:renderer` - Run tests for renderer process only
|
||||
- **Lint**: `yarn lint` - Fix linting issues and run TypeScript type checking
|
||||
- **Format**: `yarn format` - Auto-format code using Biome
|
||||
- `pnpm test:main` - Run tests for main process only
|
||||
- `pnpm test:renderer` - Run tests for renderer process only
|
||||
- **Lint**: `pnpm lint` - Fix linting issues and run TypeScript type checking
|
||||
- **Format**: `pnpm format` - Auto-format code using Biome
|
||||
|
||||
## Project Architecture
|
||||
|
||||
@ -49,7 +49,7 @@ When creating a Pull Request, you MUST:
|
||||
|
||||
- **AI Core** (`src/renderer/src/aiCore/`): Middleware pipeline for multiple AI providers.
|
||||
- **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, pnpm workspaces.
|
||||
- **State Management**: Redux Toolkit (`src/renderer/src/store/`) for predictable state.
|
||||
|
||||
### Logging
|
||||
|
||||
@ -50,7 +50,8 @@
|
||||
"!*.json",
|
||||
"!src/main/integration/**",
|
||||
"!**/tailwind.css",
|
||||
"!**/package.json"
|
||||
"!**/package.json",
|
||||
"!.zed/**"
|
||||
],
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
### Install
|
||||
|
||||
```bash
|
||||
yarn
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Development
|
||||
@ -20,17 +20,17 @@ yarn
|
||||
|
||||
Download and install [Node.js v22.x.x](https://nodejs.org/en/download)
|
||||
|
||||
### Setup Yarn
|
||||
### Setup pnpm
|
||||
|
||||
```bash
|
||||
corepack enable
|
||||
corepack prepare yarn@4.9.1 --activate
|
||||
corepack prepare pnpm@10.27.0 --activate
|
||||
```
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### ENV
|
||||
@ -42,13 +42,13 @@ cp .env.example .env
|
||||
### Start
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Debug
|
||||
|
||||
```bash
|
||||
yarn debug
|
||||
pnpm debug
|
||||
```
|
||||
|
||||
Then input chrome://inspect in browser
|
||||
@ -56,18 +56,18 @@ Then input chrome://inspect in browser
|
||||
### Test
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
pnpm test
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
# For windows
|
||||
$ yarn build:win
|
||||
$ pnpm build:win
|
||||
|
||||
# For macOS
|
||||
$ yarn build:mac
|
||||
$ pnpm build:mac
|
||||
|
||||
# For Linux
|
||||
$ yarn build:linux
|
||||
$ pnpm build:linux
|
||||
```
|
||||
|
||||
@ -116,7 +116,7 @@ This script checks:
|
||||
- Whether keys are properly sorted
|
||||
|
||||
```bash
|
||||
yarn i18n:check
|
||||
pnpm i18n:check
|
||||
```
|
||||
|
||||
### `i18n:sync` - Synchronize JSON Structure and Sort Order
|
||||
@ -128,7 +128,7 @@ This script uses `zh-cn.json` as the source of truth to sync structure across al
|
||||
3. Sorting keys automatically
|
||||
|
||||
```bash
|
||||
yarn i18n:sync
|
||||
pnpm i18n:sync
|
||||
```
|
||||
|
||||
### `i18n:translate` - Automatically Translate Pending Texts
|
||||
@ -148,20 +148,20 @@ MODEL="qwen-plus-latest"
|
||||
Alternatively, add these variables directly to your `.env` file.
|
||||
|
||||
```bash
|
||||
yarn i18n:translate
|
||||
pnpm i18n:translate
|
||||
```
|
||||
|
||||
### Workflow
|
||||
|
||||
1. During development, first add the required text in `zh-cn.json`
|
||||
2. Confirm it displays correctly in the Chinese environment
|
||||
3. Run `yarn i18n:sync` to propagate the keys to other language files
|
||||
4. Run `yarn i18n:translate` to perform machine translation
|
||||
3. Run `pnpm i18n:sync` to propagate the keys to other language files
|
||||
4. Run `pnpm i18n:translate` to perform machine translation
|
||||
5. Grab a coffee and let the magic happen!
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use Chinese as Source Language**: All development starts in Chinese, then translates to other languages.
|
||||
2. **Run Check Script Before Commit**: Use `yarn i18n:check` to catch i18n issues early.
|
||||
2. **Run Check Script Before Commit**: Use `pnpm i18n:check` to catch i18n issues early.
|
||||
3. **Translate in Small Increments**: Avoid accumulating a large backlog of untranslated content.
|
||||
4. **Keep Keys Semantically Clear**: Keys should clearly express their purpose, e.g., `user.profile.avatar.upload.error`
|
||||
|
||||
@ -37,8 +37,8 @@ The `x-files/app-upgrade-config/app-upgrade-config.json` file is synchronized by
|
||||
|
||||
1. **Guard + metadata preparation** – the `Check if should proceed` and `Prepare metadata` steps compute the target tag, prerelease flag, whether the tag is the newest release, and a `safe_tag` slug used for branch names. When any rule fails, the workflow stops without touching the config.
|
||||
2. **Checkout source branches** – the default branch is checked out into `main/`, while the long-lived `x-files/app-upgrade-config` branch lives in `cs/`. All modifications happen in the latter directory.
|
||||
3. **Install toolchain** – Node.js 22, Corepack, and frozen Yarn dependencies are installed inside `main/`.
|
||||
4. **Run the update script** – `yarn tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json --is-prerelease <flag>` updates the JSON in-place.
|
||||
3. **Install toolchain** – Node.js 22, Corepack, and frozen pnpm dependencies are installed inside `main/`.
|
||||
4. **Run the update script** – `pnpm tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json --is-prerelease <flag>` updates the JSON in-place.
|
||||
- The script normalizes the tag (e.g., strips `v` prefix), detects the release channel (`latest`, `rc`, `beta`), and loads segment rules from `config/app-upgrade-segments.json`.
|
||||
- It validates that prerelease flags and semantic suffixes agree, enforces locked segments, builds mirror feed URLs, and performs release-availability checks (GitHub HEAD request for every channel; GitCode GET for latest channels, falling back to `https://releases.cherry-ai.com` when gitcode is delayed).
|
||||
- After updating the relevant channel entry, the script rewrites the config with semver-sort order and a new `lastUpdated` timestamp.
|
||||
@ -223,10 +223,10 @@ interface ChannelConfig {
|
||||
Starting from this change, `.github/workflows/update-app-upgrade-config.yml` listens to GitHub release events (published + prerelease). The workflow:
|
||||
|
||||
1. Checks out the default branch (for scripts) and the `x-files/app-upgrade-config` branch (where the config is hosted).
|
||||
2. Runs `yarn tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json` to regenerate the config directly inside the `x-files/app-upgrade-config` working tree.
|
||||
2. Runs `pnpm tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json` to regenerate the config directly inside the `x-files/app-upgrade-config` working tree.
|
||||
3. If the file changed, it opens a PR against `x-files/app-upgrade-config` via `peter-evans/create-pull-request`, with the generated diff limited to `app-upgrade-config.json`.
|
||||
|
||||
You can run the same script locally via `yarn update:upgrade-config --tag v2.1.6 --config ../cs/app-upgrade-config.json` (add `--dry-run` to preview) to reproduce or debug whatever the workflow does. Passing `--skip-release-checks` along with `--dry-run` lets you bypass the release-page existence check (useful when the GitHub/GitCode pages aren’t published yet). Running without `--config` continues to update the copy in your current working directory (main branch) for documentation purposes.
|
||||
You can run the same script locally via `pnpm update:upgrade-config --tag v2.1.6 --config ../cs/app-upgrade-config.json` (add `--dry-run` to preview) to reproduce or debug whatever the workflow does. Passing `--skip-release-checks` along with `--dry-run` lets you bypass the release-page existence check (useful when the GitHub/GitCode pages aren't published yet). Running without `--config` continues to update the copy in your current working directory (main branch) for documentation purposes.
|
||||
|
||||
## Version Matching Logic
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
### Install
|
||||
|
||||
```bash
|
||||
yarn
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Development
|
||||
@ -20,17 +20,17 @@ yarn
|
||||
|
||||
Download and install [Node.js v22.x.x](https://nodejs.org/en/download)
|
||||
|
||||
### Setup Yarn
|
||||
### Setup pnpm
|
||||
|
||||
```bash
|
||||
corepack enable
|
||||
corepack prepare yarn@4.9.1 --activate
|
||||
corepack prepare pnpm@10.27.0 --activate
|
||||
```
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### ENV
|
||||
@ -42,13 +42,13 @@ cp .env.example .env
|
||||
### Start
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Debug
|
||||
|
||||
```bash
|
||||
yarn debug
|
||||
pnpm debug
|
||||
```
|
||||
|
||||
Then input chrome://inspect in browser
|
||||
@ -56,18 +56,18 @@ Then input chrome://inspect in browser
|
||||
### Test
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
pnpm test
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
# For windows
|
||||
$ yarn build:win
|
||||
$ pnpm build:win
|
||||
|
||||
# For macOS
|
||||
$ yarn build:mac
|
||||
$ pnpm build:mac
|
||||
|
||||
# For Linux
|
||||
$ yarn build:linux
|
||||
$ pnpm build:linux
|
||||
```
|
||||
|
||||
@ -111,7 +111,7 @@ export const getThemeModeLabel = (key: string): string => {
|
||||
- 是否已经有序
|
||||
|
||||
```bash
|
||||
yarn i18n:check
|
||||
pnpm i18n:check
|
||||
```
|
||||
|
||||
### `i18n:sync` - 同步 json 结构与排序
|
||||
@ -123,7 +123,7 @@ yarn i18n:check
|
||||
3. 自动排序
|
||||
|
||||
```bash
|
||||
yarn i18n:sync
|
||||
pnpm i18n:sync
|
||||
```
|
||||
|
||||
### `i18n:translate` - 自动翻译待翻译文本
|
||||
@ -143,19 +143,19 @@ MODEL="qwen-plus-latest"
|
||||
你也可以通过直接编辑`.env`文件来添加环境变量。
|
||||
|
||||
```bash
|
||||
yarn i18n:translate
|
||||
pnpm i18n:translate
|
||||
```
|
||||
|
||||
### 工作流
|
||||
|
||||
1. 开发阶段,先在`zh-cn.json`中添加所需文案
|
||||
2. 确认在中文环境下显示无误后,使用`yarn i18n:sync`将文案同步到其他语言文件
|
||||
3. 使用`yarn i18n:translate`进行自动翻译
|
||||
2. 确认在中文环境下显示无误后,使用`pnpm i18n:sync`将文案同步到其他语言文件
|
||||
3. 使用`pnpm i18n:translate`进行自动翻译
|
||||
4. 喝杯咖啡,等翻译完成吧!
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **以中文为源语言**:所有开发首先使用中文,再翻译为其他语言
|
||||
2. **提交前运行检查脚本**:使用`yarn i18n:check`检查 i18n 是否有问题
|
||||
2. **提交前运行检查脚本**:使用`pnpm i18n:check`检查 i18n 是否有问题
|
||||
3. **小步提交翻译**:避免积累大量未翻译文本
|
||||
4. **保持 key 语义明确**:key 应能清晰表达其用途,如`user.profile.avatar.upload.error`
|
||||
|
||||
@ -37,8 +37,8 @@
|
||||
|
||||
1. **检查与元数据准备**:`Check if should proceed` 和 `Prepare metadata` 步骤会计算 tag、prerelease 标志、是否最新版本以及用于分支名的 `safe_tag`。若任意校验失败,工作流立即退出。
|
||||
2. **检出分支**:默认分支被检出到 `main/`,长期维护的 `x-files/app-upgrade-config` 分支则在 `cs/` 中,所有改动都发生在 `cs/`。
|
||||
3. **安装工具链**:安装 Node.js 22、启用 Corepack,并在 `main/` 目录执行 `yarn install --immutable`。
|
||||
4. **运行更新脚本**:执行 `yarn tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json --is-prerelease <flag>`。
|
||||
3. **安装工具链**:安装 Node.js 22、启用 Corepack,并在 `main/` 目录执行 `pnpm install --frozen-lockfile`。
|
||||
4. **运行更新脚本**:执行 `pnpm tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json --is-prerelease <flag>`。
|
||||
- 脚本会标准化 tag(去掉 `v` 前缀等)、识别渠道、加载 `config/app-upgrade-segments.json` 中的分段规则。
|
||||
- 校验 prerelease 标志与语义后缀是否匹配、强制锁定的 segment 是否满足、生成镜像的下载地址,并检查 release 是否已经在 GitHub/GitCode 可用(latest 渠道在 GitCode 不可用时会回退到 `https://releases.cherry-ai.com`)。
|
||||
- 更新对应的渠道配置后,脚本会按 semver 排序写回 JSON,并刷新 `lastUpdated`。
|
||||
@ -223,10 +223,10 @@ interface ChannelConfig {
|
||||
`.github/workflows/update-app-upgrade-config.yml` 会在 GitHub Release(包含正常发布与 Pre Release)触发:
|
||||
|
||||
1. 同时 Checkout 仓库默认分支(用于脚本)和 `x-files/app-upgrade-config` 分支(真实托管配置的分支)。
|
||||
2. 在默认分支目录执行 `yarn tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json`,直接重写 `x-files/app-upgrade-config` 分支里的配置文件。
|
||||
2. 在默认分支目录执行 `pnpm tsx scripts/update-app-upgrade-config.ts --tag <tag> --config ../cs/app-upgrade-config.json`,直接重写 `x-files/app-upgrade-config` 分支里的配置文件。
|
||||
3. 如果 `app-upgrade-config.json` 有变化,则通过 `peter-evans/create-pull-request` 自动创建一个指向 `x-files/app-upgrade-config` 的 PR,Diff 仅包含该文件。
|
||||
|
||||
如需本地调试,可执行 `yarn update:upgrade-config --tag v2.1.6 --config ../cs/app-upgrade-config.json`(加 `--dry-run` 仅打印结果)来复现 CI 行为。若需要暂时跳过 GitHub/GitCode Release 页面是否就绪的校验,可在 `--dry-run` 的同时附加 `--skip-release-checks`。不加 `--config` 时默认更新当前工作目录(通常是 main 分支)下的副本,方便文档/审查。
|
||||
如需本地调试,可执行 `pnpm update:upgrade-config --tag v2.1.6 --config ../cs/app-upgrade-config.json`(加 `--dry-run` 仅打印结果)来复现 CI 行为。若需要暂时跳过 GitHub/GitCode Release 页面是否就绪的校验,可在 `--dry-run` 的同时附加 `--skip-release-checks`。不加 `--config` 时默认更新当前工作目录(通常是 main 分支)下的副本,方便文档/审查。
|
||||
|
||||
## 版本匹配逻辑
|
||||
|
||||
|
||||
@ -134,38 +134,44 @@ artifactBuildCompleted: scripts/artifact-build-completed.js
|
||||
releaseInfo:
|
||||
releaseNotes: |
|
||||
<!--LANG:en-->
|
||||
Cherry Studio 1.7.8 - Bug Fixes & Performance Improvements
|
||||
Cherry Studio 1.7.9 - New Features & Bug Fixes
|
||||
|
||||
This release focuses on bug fixes and performance optimizations.
|
||||
|
||||
⚡ Performance
|
||||
- [ModelList] Improve model list loading performance
|
||||
✨ New Features
|
||||
- [Agent] Add 302.AI provider support
|
||||
- [Browser] Browser data now persists and supports multiple tabs
|
||||
- [Language] Add Romanian language support
|
||||
- [Search] Add fuzzy search for file list
|
||||
- [Models] Add latest Zhipu models
|
||||
- [Image] Improve text-to-image functionality
|
||||
|
||||
🐛 Bug Fixes
|
||||
- [Ollama] Fix new users unable to use Ollama models
|
||||
- [Ollama] Improve reasoningEffort handling
|
||||
- [Assistants] Prevent deleting last assistant and add error message
|
||||
- [Shortcut] Fix shortcut icons sorting disorder
|
||||
- [Memory] Fix global memory settings submit failure
|
||||
- [Windows] Fix remember size not working for SelectionAction window
|
||||
- [Anthropic] Fix API base URL handling
|
||||
- [Files] Allow more file extensions
|
||||
- [Mac] Fix mini window unexpected closing issue
|
||||
- [Preview] Fix HTML preview controls not working in fullscreen
|
||||
- [Translate] Fix translation duplicate execution issue
|
||||
- [Zoom] Fix page zoom reset issue during navigation
|
||||
- [Agent] Fix crash when switching between agent and assistant
|
||||
- [Agent] Fix navigation in agent mode
|
||||
- [Copy] Fix markdown copy button issue
|
||||
- [Windows] Fix compatibility issues on non-Windows systems
|
||||
|
||||
<!--LANG:zh-CN-->
|
||||
Cherry Studio 1.7.8 - 问题修复与性能优化
|
||||
Cherry Studio 1.7.9 - 新功能与问题修复
|
||||
|
||||
本次更新专注于问题修复和性能优化。
|
||||
|
||||
⚡ 性能优化
|
||||
- [模型列表] 提升模型列表加载性能
|
||||
✨ 新功能
|
||||
- [Agent] 新增 302.AI 服务商支持
|
||||
- [浏览器] 浏览器数据现在可以保存,支持多标签页
|
||||
- [语言] 新增罗马尼亚语支持
|
||||
- [搜索] 文件列表新增模糊搜索功能
|
||||
- [模型] 新增最新智谱模型
|
||||
- [图片] 优化文生图功能
|
||||
|
||||
🐛 问题修复
|
||||
- [Ollama] 修复新用户无法使用 Ollama 模型的问题
|
||||
- [Ollama] 改进推理参数处理
|
||||
- [助手] 防止删除最后一个助手并添加错误提示
|
||||
- [快捷方式] 修复快捷方式图标排序混乱
|
||||
- [记忆] 修复全局记忆设置提交失败
|
||||
- [窗口] 修复 SelectionAction 窗口记住尺寸不生效
|
||||
- [Anthropic] 修复 API 地址处理
|
||||
- [文件] 允许更多文件扩展名
|
||||
- [Mac] 修复迷你窗口意外关闭的问题
|
||||
- [预览] 修复全屏模式下 HTML 预览控件无法使用的问题
|
||||
- [翻译] 修复翻译重复执行的问题
|
||||
- [缩放] 修复页面导航时缩放被重置的问题
|
||||
- [智能体] 修复在智能体和助手间切换时崩溃的问题
|
||||
- [智能体] 修复智能体模式下的导航问题
|
||||
- [复制] 修复 Markdown 复制按钮问题
|
||||
- [兼容性] 修复非 Windows 系统的兼容性问题
|
||||
<!--LANG:END-->
|
||||
|
||||
@ -70,18 +70,7 @@ export default defineConfig({
|
||||
plugins: [
|
||||
(async () => (await import('@tailwindcss/vite')).default())(),
|
||||
react({
|
||||
tsDecorators: true,
|
||||
plugins: [
|
||||
[
|
||||
'@swc/plugin-styled-components',
|
||||
{
|
||||
displayName: true, // 开发环境下启用组件名称
|
||||
fileName: false, // 不在类名中包含文件名
|
||||
pure: true, // 优化性能
|
||||
ssr: false // 不需要服务端渲染
|
||||
}
|
||||
]
|
||||
]
|
||||
tsDecorators: true
|
||||
}),
|
||||
...(isDev ? [CodeInspectorPlugin({ bundler: 'vite' })] : []), // 只在开发环境下启用 CodeInspectorPlugin
|
||||
...visualizerPlugin('renderer')
|
||||
|
||||
307
package.json
307
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "CherryStudio",
|
||||
"version": "1.7.8",
|
||||
"version": "1.7.9",
|
||||
"private": true,
|
||||
"description": "A powerful AI assistant for producer.",
|
||||
"main": "./out/main/index.js",
|
||||
@ -9,28 +9,13 @@
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"local",
|
||||
"packages/*"
|
||||
],
|
||||
"installConfig": {
|
||||
"hoistingLimits": [
|
||||
"packages/database",
|
||||
"packages/mcp-trace/trace-core",
|
||||
"packages/mcp-trace/trace-node",
|
||||
"packages/mcp-trace/trace-web",
|
||||
"packages/extension-table-plus"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"start": "electron-vite preview",
|
||||
"dev": "dotenv electron-vite dev",
|
||||
"dev:watch": "dotenv electron-vite dev -- -w",
|
||||
"debug": "electron-vite -- --inspect --sourcemap --remote-debugging-port=9222",
|
||||
"build": "npm run typecheck && electron-vite build",
|
||||
"build:check": "yarn lint && yarn test",
|
||||
"build:check": "pnpm lint && pnpm test",
|
||||
"build:unpack": "dotenv npm run build && electron-builder --dir",
|
||||
"build:win": "dotenv npm run build && electron-builder --win --x64 --arm64",
|
||||
"build:win:x64": "dotenv npm run build && electron-builder --win --x64",
|
||||
@ -42,107 +27,116 @@
|
||||
"build:linux:arm64": "dotenv npm run build && electron-builder --linux --arm64",
|
||||
"build:linux:x64": "dotenv npm run build && electron-builder --linux --x64",
|
||||
"release": "node scripts/version.js",
|
||||
"publish": "yarn build:check && yarn release patch push",
|
||||
"publish": "pnpm build:check && pnpm release patch push",
|
||||
"pulish:artifacts": "cd packages/artifacts && npm publish && cd -",
|
||||
"agents:generate": "NODE_ENV='development' drizzle-kit generate --config src/main/services/agents/drizzle.config.ts",
|
||||
"agents:push": "NODE_ENV='development' drizzle-kit push --config src/main/services/agents/drizzle.config.ts",
|
||||
"agents:studio": "NODE_ENV='development' drizzle-kit studio --config src/main/services/agents/drizzle.config.ts",
|
||||
"agents:drop": "NODE_ENV='development' drizzle-kit drop --config src/main/services/agents/drizzle.config.ts",
|
||||
"generate:icons": "electron-icon-builder --input=./build/logo.png --output=build",
|
||||
"analyze:renderer": "VISUALIZER_RENDERER=true yarn build",
|
||||
"analyze:main": "VISUALIZER_MAIN=true yarn build",
|
||||
"analyze:renderer": "VISUALIZER_RENDERER=true pnpm build",
|
||||
"analyze:main": "VISUALIZER_MAIN=true pnpm build",
|
||||
"typecheck": "concurrently -n \"node,web\" -c \"cyan,magenta\" \"npm run typecheck:node\" \"npm run typecheck:web\"",
|
||||
"typecheck:node": "tsgo --noEmit -p tsconfig.node.json --composite false",
|
||||
"typecheck:web": "tsgo --noEmit -p tsconfig.web.json --composite false",
|
||||
"i18n:check": "dotenv -e .env -- tsx scripts/check-i18n.ts",
|
||||
"i18n:sync": "dotenv -e .env -- tsx scripts/sync-i18n.ts",
|
||||
"i18n:translate": "dotenv -e .env -- tsx scripts/auto-translate-i18n.ts",
|
||||
"i18n:all": "yarn i18n:check && yarn i18n:sync && yarn i18n:translate",
|
||||
"i18n:all": "pnpm i18n:check && pnpm i18n:sync && pnpm i18n:translate",
|
||||
"update:languages": "tsx scripts/update-languages.ts",
|
||||
"update:upgrade-config": "tsx scripts/update-app-upgrade-config.ts",
|
||||
"test": "vitest run --silent",
|
||||
"test:main": "vitest run --project main",
|
||||
"test:renderer": "vitest run --project renderer",
|
||||
"test:aicore": "vitest run --project aiCore",
|
||||
"test:update": "yarn test:renderer --update",
|
||||
"test:update": "pnpm test:renderer --update",
|
||||
"test:coverage": "vitest run --coverage --silent",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:watch": "vitest",
|
||||
"test:e2e": "yarn playwright test",
|
||||
"test:e2e": "pnpm playwright test",
|
||||
"test:lint": "oxlint --deny-warnings && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --cache",
|
||||
"test:scripts": "vitest scripts",
|
||||
"lint": "oxlint --fix && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --cache && yarn typecheck && yarn i18n:check && yarn format:check",
|
||||
"lint": "oxlint --fix && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --cache && pnpm typecheck && pnpm i18n:check && pnpm format:check",
|
||||
"format": "biome format --write && biome lint --write",
|
||||
"format:check": "biome format && biome lint",
|
||||
"prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky",
|
||||
"claude": "dotenv -e .env -- claude",
|
||||
"release:aicore:alpha": "yarn workspace @cherrystudio/ai-core version prerelease --preid alpha --immediate && yarn workspace @cherrystudio/ai-core build && yarn workspace @cherrystudio/ai-core npm publish --tag alpha --access public",
|
||||
"release:aicore:beta": "yarn workspace @cherrystudio/ai-core version prerelease --preid beta --immediate && yarn workspace @cherrystudio/ai-core build && yarn workspace @cherrystudio/ai-core npm publish --tag beta --access public",
|
||||
"release:aicore": "yarn workspace @cherrystudio/ai-core version patch --immediate && yarn workspace @cherrystudio/ai-core build && yarn workspace @cherrystudio/ai-core npm publish --access public",
|
||||
"release:ai-sdk-provider": "yarn workspace @cherrystudio/ai-sdk-provider version patch --immediate && yarn workspace @cherrystudio/ai-sdk-provider build && yarn workspace @cherrystudio/ai-sdk-provider npm publish --access public"
|
||||
"release:aicore:alpha": "pnpm --filter @cherrystudio/ai-core version prerelease --preid alpha && pnpm --filter @cherrystudio/ai-core build && pnpm --filter @cherrystudio/ai-core publish --tag alpha --access public",
|
||||
"release:aicore:beta": "pnpm --filter @cherrystudio/ai-core version prerelease --preid beta && pnpm --filter @cherrystudio/ai-core build && pnpm --filter @cherrystudio/ai-core publish --tag beta --access public",
|
||||
"release:aicore": "pnpm --filter @cherrystudio/ai-core version patch && pnpm --filter @cherrystudio/ai-core build && pnpm --filter @cherrystudio/ai-core publish --access public",
|
||||
"release:ai-sdk-provider": "pnpm --filter @cherrystudio/ai-sdk-provider version patch && pnpm --filter @cherrystudio/ai-sdk-provider build && pnpm --filter @cherrystudio/ai-sdk-provider publish --access public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.62#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.62-23ae56f8c8.patch",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.1.62",
|
||||
"@libsql/client": "0.14.0",
|
||||
"@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",
|
||||
"@paymoapp/electron-shutdown-handler": "^1.1.2",
|
||||
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
||||
"bonjour-service": "^1.3.0",
|
||||
"emoji-picker-element-data": "^1",
|
||||
"express": "^5.1.0",
|
||||
"font-list": "^2.0.0",
|
||||
"graceful-fs": "^4.2.11",
|
||||
"gray-matter": "^4.0.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"@napi-rs/system-ocr": "1.0.2",
|
||||
"@paymoapp/electron-shutdown-handler": "1.1.2",
|
||||
"express": "5.1.0",
|
||||
"font-list": "2.0.0",
|
||||
"graceful-fs": "4.2.11",
|
||||
"gray-matter": "4.0.3",
|
||||
"jsdom": "26.1.0",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"officeparser": "^4.2.0",
|
||||
"os-proxy-config": "^1.1.2",
|
||||
"selection-hook": "^1.0.12",
|
||||
"sharp": "^0.34.3",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"tesseract.js": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch",
|
||||
"node-stream-zip": "1.15.0",
|
||||
"officeparser": "4.2.0",
|
||||
"os-proxy-config": "1.1.2",
|
||||
"selection-hook": "1.0.12",
|
||||
"sharp": "0.34.3",
|
||||
"swagger-jsdoc": "6.2.8",
|
||||
"swagger-ui-express": "5.0.1",
|
||||
"tesseract.js": "6.0.1",
|
||||
"turndown": "7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"js-yaml": "4.1.0",
|
||||
"bonjour-service": "1.3.0",
|
||||
"emoji-picker-element-data": "1",
|
||||
"@agentic/exa": "^7.3.3",
|
||||
"@agentic/searxng": "^7.3.3",
|
||||
"@agentic/tavily": "^7.3.3",
|
||||
"@ai-sdk/amazon-bedrock": "^3.0.61",
|
||||
"@ai-sdk/anthropic": "^2.0.49",
|
||||
"@ai-sdk/azure": "2.0.87",
|
||||
"@ai-sdk/cerebras": "^1.0.31",
|
||||
"@ai-sdk/gateway": "^2.0.15",
|
||||
"@ai-sdk/google": "patch:@ai-sdk/google@npm%3A2.0.49#~/.yarn/patches/@ai-sdk-google-npm-2.0.49-84720f41bd.patch",
|
||||
"@ai-sdk/google": "2.0.49",
|
||||
"@ai-sdk/google-vertex": "^3.0.94",
|
||||
"@ai-sdk/huggingface": "^0.0.10",
|
||||
"@ai-sdk/mistral": "^2.0.24",
|
||||
"@ai-sdk/openai": "patch:@ai-sdk/openai@npm%3A2.0.85#~/.yarn/patches/@ai-sdk-openai-npm-2.0.85-27483d1d6a.patch",
|
||||
"@ai-sdk/openai": "2.0.85",
|
||||
"@ai-sdk/perplexity": "^2.0.20",
|
||||
"@ai-sdk/provider": "2.0.0",
|
||||
"@ai-sdk/provider-utils": "3.0.17",
|
||||
"@ai-sdk/test-server": "^0.0.1",
|
||||
"@ai-sdk/xai": "2.0.36",
|
||||
"@ant-design/cssinjs": "1.23.0",
|
||||
"@ant-design/icons": "5.6.1",
|
||||
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||
"@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": "0.11.4",
|
||||
"@aws-sdk/client-bedrock": "^3.910.0",
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.910.0",
|
||||
"@aws-sdk/client-s3": "^3.910.0",
|
||||
"@biomejs/biome": "2.2.4",
|
||||
"@cherrystudio/ai-core": "workspace:^1.0.9",
|
||||
"@cherrystudio/embedjs": "^0.1.31",
|
||||
"@cherrystudio/embedjs-libsql": "^0.1.31",
|
||||
"@cherrystudio/embedjs-loader-csv": "^0.1.31",
|
||||
"@cherrystudio/embedjs-loader-image": "^0.1.31",
|
||||
"@cherrystudio/embedjs-loader-markdown": "^0.1.31",
|
||||
"@cherrystudio/embedjs-loader-msoffice": "^0.1.31",
|
||||
"@cherrystudio/embedjs-loader-pdf": "^0.1.31",
|
||||
"@cherrystudio/embedjs-loader-sitemap": "^0.1.31",
|
||||
"@cherrystudio/embedjs-loader-web": "^0.1.31",
|
||||
"@cherrystudio/embedjs-loader-xml": "^0.1.31",
|
||||
"@cherrystudio/embedjs-ollama": "^0.1.31",
|
||||
"@cherrystudio/embedjs-openai": "^0.1.31",
|
||||
"@cherrystudio/embedjs": "0.1.31",
|
||||
"@cherrystudio/embedjs-interfaces": "0.1.31",
|
||||
"@cherrystudio/embedjs-libsql": "0.1.31",
|
||||
"@cherrystudio/embedjs-loader-csv": "0.1.31",
|
||||
"@cherrystudio/embedjs-loader-image": "0.1.31",
|
||||
"@cherrystudio/embedjs-loader-markdown": "0.1.31",
|
||||
"@cherrystudio/embedjs-loader-msoffice": "0.1.31",
|
||||
"@cherrystudio/embedjs-loader-pdf": "0.1.31",
|
||||
"@cherrystudio/embedjs-loader-sitemap": "0.1.31",
|
||||
"@cherrystudio/embedjs-loader-web": "0.1.31",
|
||||
"@cherrystudio/embedjs-loader-xml": "0.1.31",
|
||||
"@cherrystudio/embedjs-ollama": "0.1.31",
|
||||
"@cherrystudio/embedjs-openai": "0.1.31",
|
||||
"@cherrystudio/embedjs-utils": "0.1.31",
|
||||
"@cherrystudio/extension-table-plus": "workspace:^",
|
||||
"@cherrystudio/openai": "^6.12.0",
|
||||
"@cherrystudio/openai": "6.15.0",
|
||||
"@codemirror/lang-json": "6.0.1",
|
||||
"@codemirror/lint": "6.8.5",
|
||||
"@codemirror/view": "6.38.1",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/modifiers": "^9.0.0",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
@ -155,18 +149,21 @@
|
||||
"@emotion/is-prop-valid": "^1.3.1",
|
||||
"@eslint-react/eslint-plugin": "^1.36.1",
|
||||
"@eslint/js": "^9.22.0",
|
||||
"@google/genai": "patch:@google/genai@npm%3A1.0.1#~/.yarn/patches/@google-genai-npm-1.0.1-e26f0f9af7.patch",
|
||||
"@floating-ui/dom": "1.7.3",
|
||||
"@google/genai": "1.0.1",
|
||||
"@hello-pangea/dnd": "^18.0.1",
|
||||
"@kangfenmao/keyv-storage": "^0.1.0",
|
||||
"@kangfenmao/keyv-storage": "^0.1.3",
|
||||
"@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/openai": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
|
||||
"@langchain/core": "1.0.2",
|
||||
"@langchain/openai": "1.0.0",
|
||||
"@langchain/textsplitters": "0.1.0",
|
||||
"@mistralai/mistralai": "^1.7.5",
|
||||
"@modelcontextprotocol/sdk": "^1.23.0",
|
||||
"@modelcontextprotocol/sdk": "1.23.0",
|
||||
"@mozilla/readability": "^0.6.0",
|
||||
"@notionhq/client": "^2.2.15",
|
||||
"@openrouter/ai-sdk-provider": "^1.2.8",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/context-async-hooks": "2.0.1",
|
||||
"@opentelemetry/core": "2.0.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
|
||||
"@opentelemetry/sdk-trace-base": "^2.0.0",
|
||||
@ -177,6 +174,7 @@
|
||||
"@radix-ui/react-context-menu": "^2.2.16",
|
||||
"@reduxjs/toolkit": "^2.2.5",
|
||||
"@shikijs/markdown-it": "^3.12.0",
|
||||
"@swc/core": "^1.15.8",
|
||||
"@swc/plugin-styled-components": "^8.0.4",
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"@tanstack/react-query": "^5.85.5",
|
||||
@ -185,21 +183,25 @@
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@tiptap/extension-collaboration": "^3.2.0",
|
||||
"@tiptap/extension-drag-handle": "patch:@tiptap/extension-drag-handle@npm%3A3.2.0#~/.yarn/patches/@tiptap-extension-drag-handle-npm-3.2.0-5a9ebff7c9.patch",
|
||||
"@tiptap/extension-drag-handle-react": "^3.2.0",
|
||||
"@tiptap/extension-image": "^3.2.0",
|
||||
"@tiptap/extension-list": "^3.2.0",
|
||||
"@tiptap/extension-mathematics": "^3.2.0",
|
||||
"@tiptap/extension-mention": "^3.2.0",
|
||||
"@tiptap/extension-node-range": "^3.2.0",
|
||||
"@tiptap/extension-table-of-contents": "^3.2.0",
|
||||
"@tiptap/extension-typography": "^3.2.0",
|
||||
"@tiptap/extension-underline": "^3.2.0",
|
||||
"@tiptap/pm": "^3.2.0",
|
||||
"@tiptap/react": "^3.2.0",
|
||||
"@tiptap/starter-kit": "^3.2.0",
|
||||
"@tiptap/suggestion": "^3.2.0",
|
||||
"@tiptap/core": "3.2.0",
|
||||
"@tiptap/extension-code-block": "3.2.0",
|
||||
"@tiptap/extension-collaboration": "3.2.0",
|
||||
"@tiptap/extension-drag-handle": "3.2.0",
|
||||
"@tiptap/extension-drag-handle-react": "3.2.0",
|
||||
"@tiptap/extension-heading": "3.2.0",
|
||||
"@tiptap/extension-image": "3.2.0",
|
||||
"@tiptap/extension-link": "3.2.0",
|
||||
"@tiptap/extension-list": "3.2.0",
|
||||
"@tiptap/extension-mathematics": "3.2.0",
|
||||
"@tiptap/extension-mention": "3.2.0",
|
||||
"@tiptap/extension-node-range": "3.2.0",
|
||||
"@tiptap/extension-table-of-contents": "3.2.0",
|
||||
"@tiptap/extension-typography": "3.2.0",
|
||||
"@tiptap/extension-underline": "3.2.0",
|
||||
"@tiptap/pm": "3.2.0",
|
||||
"@tiptap/react": "3.2.0",
|
||||
"@tiptap/starter-kit": "3.2.0",
|
||||
"@tiptap/suggestion": "3.2.0",
|
||||
"@tiptap/y-tiptap": "^3.0.0",
|
||||
"@truto/turndown-plugin-gfm": "^1.0.2",
|
||||
"@tryfabric/martian": "^1.2.4",
|
||||
@ -210,14 +212,17 @@
|
||||
"@types/dotenv": "^8.2.3",
|
||||
"@types/express": "^5",
|
||||
"@types/fs-extra": "^11",
|
||||
"@types/hast": "^3.0.4",
|
||||
"@types/he": "^1",
|
||||
"@types/html-to-text": "^9",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/json-schema": "7.0.15",
|
||||
"@types/lodash": "^4.17.5",
|
||||
"@types/markdown-it": "^14",
|
||||
"@types/md5": "^2.3.5",
|
||||
"@types/mdast": "4.0.4",
|
||||
"@types/mime-types": "^3",
|
||||
"@types/node": "^22.17.1",
|
||||
"@types/node": "22.17.2",
|
||||
"@types/pako": "^1.0.2",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
@ -228,9 +233,10 @@
|
||||
"@types/swagger-ui-express": "^4.1.8",
|
||||
"@types/tinycolor2": "^1",
|
||||
"@types/turndown": "^5.0.5",
|
||||
"@types/unist": "3.0.3",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@types/word-extractor": "^1",
|
||||
"@typescript/native-preview": "latest",
|
||||
"@typescript/native-preview": "7.0.0-dev.20250915.1",
|
||||
"@uiw/codemirror-extensions-langs": "^4.25.1",
|
||||
"@uiw/codemirror-themes-all": "^4.25.1",
|
||||
"@uiw/react-codemirror": "^4.25.1",
|
||||
@ -242,12 +248,15 @@
|
||||
"@viz-js/lang-dot": "^1.0.5",
|
||||
"@viz-js/viz": "^3.14.0",
|
||||
"@xyflow/react": "^12.4.4",
|
||||
"adm-zip": "0.4.16",
|
||||
"ai": "^5.0.98",
|
||||
"antd": "patch:antd@npm%3A5.27.0#~/.yarn/patches/antd-npm-5.27.0-aa91c36546.patch",
|
||||
"antd": "5.27.0",
|
||||
"archiver": "^7.0.1",
|
||||
"async-mutex": "^0.5.0",
|
||||
"axios": "^1.7.3",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"builder-util-runtime": "9.5.0",
|
||||
"chalk": "4.1.2",
|
||||
"chardet": "^2.1.0",
|
||||
"check-disk-space": "3.4.0",
|
||||
"cheerio": "^1.1.2",
|
||||
@ -256,8 +265,10 @@
|
||||
"cli-progress": "^3.12.0",
|
||||
"clsx": "^2.1.1",
|
||||
"code-inspector-plugin": "^0.20.14",
|
||||
"codemirror-lang-mermaid": "0.5.0",
|
||||
"color": "^5.0.0",
|
||||
"concurrently": "^9.2.1",
|
||||
"cors": "2.8.5",
|
||||
"country-flag-emoji-polyfill": "0.1.8",
|
||||
"dayjs": "^1.11.11",
|
||||
"dexie": "^4.0.8",
|
||||
@ -265,6 +276,7 @@
|
||||
"diff": "^8.0.2",
|
||||
"docx": "^9.0.2",
|
||||
"dompurify": "^3.2.6",
|
||||
"dotenv": "16.6.1",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
"drizzle-kit": "^0.31.4",
|
||||
"drizzle-orm": "^0.44.5",
|
||||
@ -273,12 +285,12 @@
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-reload": "^2.0.0-alpha.1",
|
||||
"electron-store": "^8.2.0",
|
||||
"electron-updater": "patch:electron-updater@npm%3A6.7.0#~/.yarn/patches/electron-updater-npm-6.7.0-47b11bb0d4.patch",
|
||||
"electron-updater": "6.7.0",
|
||||
"electron-vite": "5.0.0",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"emittery": "^1.0.3",
|
||||
"emoji-picker-element": "^1.22.1",
|
||||
"epub": "patch:epub@npm%3A1.3.0#~/.yarn/patches/epub-npm-1.3.0-8325494ffe.patch",
|
||||
"epub": "1.3.0",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint-plugin-import-zod": "^1.2.0",
|
||||
"eslint-plugin-oxlint": "^1.15.0",
|
||||
@ -289,6 +301,7 @@
|
||||
"fast-diff": "^1.3.0",
|
||||
"fast-xml-parser": "^5.2.0",
|
||||
"fetch-socks": "1.3.2",
|
||||
"form-data": "4.0.4",
|
||||
"framer-motion": "^12.23.12",
|
||||
"franc-min": "^6.2.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
@ -305,6 +318,10 @@
|
||||
"isbinaryfile": "5.0.4",
|
||||
"jaison": "^2.0.2",
|
||||
"jest-styled-components": "^7.2.0",
|
||||
"js-base64": "3.7.7",
|
||||
"json-schema": "0.4.0",
|
||||
"katex": "0.16.22",
|
||||
"ky": "1.8.1",
|
||||
"linguist-languages": "^8.1.0",
|
||||
"lint-staged": "^15.5.0",
|
||||
"lodash": "^4.17.21",
|
||||
@ -312,19 +329,27 @@
|
||||
"lucide-react": "^0.525.0",
|
||||
"macos-release": "^3.4.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"md5": "2.3.0",
|
||||
"mermaid": "^11.10.1",
|
||||
"mime": "^4.0.4",
|
||||
"mime-types": "^3.0.1",
|
||||
"motion": "^12.10.5",
|
||||
"nanoid": "3.3.11",
|
||||
"notion-helper": "^1.3.22",
|
||||
"npx-scope-finder": "^1.2.0",
|
||||
"ollama-ai-provider-v2": "patch:ollama-ai-provider-v2@npm%3A1.5.5#~/.yarn/patches/ollama-ai-provider-v2-npm-1.5.5-8bef249af9.patch",
|
||||
"ollama-ai-provider-v2": "1.5.5",
|
||||
"open": "^8.4.2",
|
||||
"oxlint": "^1.22.0",
|
||||
"oxlint-tsgolint": "^0.2.0",
|
||||
"p-queue": "^8.1.0",
|
||||
"pako": "1.0.11",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"prosemirror-model": "1.25.2",
|
||||
"proxy-agent": "^6.5.0",
|
||||
"rc-input": "1.8.0",
|
||||
"rc-select": "14.16.6",
|
||||
"rc-virtual-list": "3.18.6",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
@ -351,8 +376,11 @@
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-github-blockquote-alert": "^2.0.0",
|
||||
"remark-math": "^6.0.0",
|
||||
"remark-parse": "11.0.0",
|
||||
"remark-stringify": "11.0.0",
|
||||
"remove-markdown": "^0.6.2",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"semver": "7.7.1",
|
||||
"shiki": "^3.12.0",
|
||||
"strict-url-sanitise": "^0.0.1",
|
||||
"string-width": "^7.2.0",
|
||||
@ -367,9 +395,10 @@
|
||||
"tsx": "^4.20.3",
|
||||
"turndown-plugin-gfm": "^1.0.2",
|
||||
"tw-animate-css": "^1.3.8",
|
||||
"typescript": "~5.8.2",
|
||||
"typescript": "~5.8.3",
|
||||
"undici": "6.21.2",
|
||||
"unified": "^11.0.5",
|
||||
"unist-util-visit": "5.0.0",
|
||||
"uuid": "^13.0.0",
|
||||
"vite": "npm:rolldown-vite@7.3.0",
|
||||
"vitest": "^3.2.4",
|
||||
@ -384,44 +413,66 @@
|
||||
"zipread": "^1.3.3",
|
||||
"zod": "^4.1.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"@smithy/types": "4.7.1",
|
||||
"@codemirror/language": "6.11.3",
|
||||
"@codemirror/lint": "6.8.5",
|
||||
"@codemirror/view": "6.38.1",
|
||||
"@langchain/core@npm:^0.3.26": "patch:@langchain/core@npm%3A1.0.2#~/.yarn/patches/@langchain-core-npm-1.0.2-183ef83fe4.patch",
|
||||
"atomically@npm:^1.7.0": "patch:atomically@npm%3A1.7.0#~/.yarn/patches/atomically-npm-1.7.0-e742e5293b.patch",
|
||||
"esbuild": "^0.25.0",
|
||||
"file-stream-rotator@npm:^0.6.1": "patch:file-stream-rotator@npm%3A0.6.1#~/.yarn/patches/file-stream-rotator-npm-0.6.1-eab45fb13d.patch",
|
||||
"libsql@npm:^0.4.4": "patch:libsql@npm%3A0.4.7#~/.yarn/patches/libsql-npm-0.4.7-444e260fb1.patch",
|
||||
"node-abi": "4.24.0",
|
||||
"openai@npm:^4.77.0": "npm:@cherrystudio/openai@6.5.0",
|
||||
"openai@npm:^4.87.3": "npm:@cherrystudio/openai@6.5.0",
|
||||
"pdf-parse@npm:1.1.1": "patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch",
|
||||
"pkce-challenge@npm:^4.1.0": "patch:pkce-challenge@npm%3A4.1.0#~/.yarn/patches/pkce-challenge-npm-4.1.0-fbc51695a3.patch",
|
||||
"tar-fs": "^2.1.4",
|
||||
"undici": "6.21.2",
|
||||
"vite": "npm:rolldown-vite@7.3.0",
|
||||
"tesseract.js@npm:*": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch",
|
||||
"@ai-sdk/openai@npm:^2.0.52": "patch:@ai-sdk/openai@npm%3A2.0.52#~/.yarn/patches/@ai-sdk-openai-npm-2.0.52-b36d949c76.patch",
|
||||
"@img/sharp-darwin-arm64": "0.34.3",
|
||||
"@img/sharp-darwin-x64": "0.34.3",
|
||||
"@img/sharp-linux-arm": "0.34.3",
|
||||
"@img/sharp-linux-arm64": "0.34.3",
|
||||
"@img/sharp-linux-x64": "0.34.3",
|
||||
"@img/sharp-win32-x64": "0.34.3",
|
||||
"openai@npm:5.12.2": "npm:@cherrystudio/openai@6.5.0",
|
||||
"@langchain/openai@npm:>=0.1.0 <0.6.0": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
|
||||
"@langchain/openai@npm:^0.3.16": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
|
||||
"@langchain/openai@npm:>=0.2.0 <0.7.0": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
|
||||
"@ai-sdk/openai@npm:^2.0.42": "patch:@ai-sdk/openai@npm%3A2.0.85#~/.yarn/patches/@ai-sdk-openai-npm-2.0.85-27483d1d6a.patch",
|
||||
"@ai-sdk/google@npm:^2.0.40": "patch:@ai-sdk/google@npm%3A2.0.40#~/.yarn/patches/@ai-sdk-google-npm-2.0.40-47e0eeee83.patch",
|
||||
"@ai-sdk/openai-compatible@npm:^1.0.27": "patch:@ai-sdk/openai-compatible@npm%3A1.0.27#~/.yarn/patches/@ai-sdk-openai-compatible-npm-1.0.27-06f74278cf.patch",
|
||||
"@ai-sdk/google@npm:2.0.49": "patch:@ai-sdk/google@npm%3A2.0.49#~/.yarn/patches/@ai-sdk-google-npm-2.0.49-84720f41bd.patch",
|
||||
"@ai-sdk/openai-compatible@npm:1.0.27": "patch:@ai-sdk/openai-compatible@npm%3A1.0.28#~/.yarn/patches/@ai-sdk-openai-compatible-npm-1.0.28-5705188855.patch",
|
||||
"@ai-sdk/openai-compatible@npm:^1.0.19": "patch:@ai-sdk/openai-compatible@npm%3A1.0.28#~/.yarn/patches/@ai-sdk-openai-compatible-npm-1.0.28-5705188855.patch"
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@smithy/types": "4.7.1",
|
||||
"@codemirror/language": "6.11.3",
|
||||
"@codemirror/lint": "6.8.5",
|
||||
"@codemirror/view": "6.38.1",
|
||||
"esbuild": "^0.25.0",
|
||||
"node-abi": "4.24.0",
|
||||
"openai": "npm:@cherrystudio/openai@6.15.0",
|
||||
"tar-fs": "^2.1.4",
|
||||
"undici": "6.21.2",
|
||||
"vite": "npm:rolldown-vite@7.3.0",
|
||||
"@img/sharp-darwin-arm64": "0.34.3",
|
||||
"@img/sharp-darwin-x64": "0.34.3",
|
||||
"@img/sharp-linux-arm": "0.34.3",
|
||||
"@img/sharp-linux-arm64": "0.34.3",
|
||||
"@img/sharp-linux-x64": "0.34.3",
|
||||
"@img/sharp-win32-x64": "0.34.3",
|
||||
"@langchain/core": "1.0.2",
|
||||
"@ai-sdk/openai-compatible@1.0.27": "1.0.28"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk@0.1.62": "patches/@anthropic-ai-claude-agent-sdk-npm-0.1.62-23ae56f8c8.patch",
|
||||
"@napi-rs/system-ocr@1.0.2": "patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
|
||||
"tesseract.js@6.0.1": "patches/tesseract.js-npm-6.0.1-2562a7e46d.patch",
|
||||
"@ai-sdk/google@2.0.49": "patches/@ai-sdk-google-npm-2.0.49-84720f41bd.patch",
|
||||
"@ai-sdk/openai@2.0.85": "patches/@ai-sdk-openai-npm-2.0.85-27483d1d6a.patch",
|
||||
"@anthropic-ai/vertex-sdk@0.11.4": "patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch",
|
||||
"@google/genai@1.0.1": "patches/@google-genai-npm-1.0.1-e26f0f9af7.patch",
|
||||
"@langchain/core@1.0.2": "patches/@langchain-core-npm-1.0.2-183ef83fe4.patch",
|
||||
"@langchain/openai@1.0.0": "patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
|
||||
"@tiptap/extension-drag-handle@3.2.0": "patches/@tiptap-extension-drag-handle-npm-3.2.0-5a9ebff7c9.patch",
|
||||
"antd@5.27.0": "patches/antd-npm-5.27.0-aa91c36546.patch",
|
||||
"electron-updater@6.7.0": "patches/electron-updater-npm-6.7.0-47b11bb0d4.patch",
|
||||
"epub@1.3.0": "patches/epub-npm-1.3.0-8325494ffe.patch",
|
||||
"ollama-ai-provider-v2@1.5.5": "patches/ollama-ai-provider-v2-npm-1.5.5-8bef249af9.patch",
|
||||
"atomically@1.7.0": "patches/atomically-npm-1.7.0-e742e5293b.patch",
|
||||
"file-stream-rotator@0.6.1": "patches/file-stream-rotator-npm-0.6.1-eab45fb13d.patch",
|
||||
"libsql@0.4.7": "patches/libsql-npm-0.4.7-444e260fb1.patch",
|
||||
"pdf-parse@1.1.1": "patches/pdf-parse-npm-1.1.1-04a6109b2a.patch",
|
||||
"@ai-sdk/openai-compatible@1.0.28": "patches/@ai-sdk-openai-compatible-npm-1.0.28-5705188855.patch"
|
||||
},
|
||||
"onlyBuiltDependencies": [
|
||||
"@kangfenmao/keyv-storage",
|
||||
"@paymoapp/electron-shutdown-handler",
|
||||
"@scarf/scarf",
|
||||
"@swc/core",
|
||||
"electron",
|
||||
"electron-winstaller",
|
||||
"esbuild",
|
||||
"msw",
|
||||
"protobufjs",
|
||||
"registry-js",
|
||||
"selection-hook",
|
||||
"sharp",
|
||||
"tesseract.js",
|
||||
"zipfile"
|
||||
]
|
||||
},
|
||||
"packageManager": "yarn@4.9.1",
|
||||
"packageManager": "pnpm@10.27.0",
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,cjs,mjs,cts,mts}": [
|
||||
"biome format --write --no-errors-on-unmatched",
|
||||
|
||||
@ -8,7 +8,7 @@ It exposes the CherryIN OpenAI-compatible entrypoints and dynamically routes Ant
|
||||
```bash
|
||||
npm install ai @cherrystudio/ai-sdk-provider @ai-sdk/anthropic @ai-sdk/google @ai-sdk/openai
|
||||
# or
|
||||
yarn add ai @cherrystudio/ai-sdk-provider @ai-sdk/anthropic @ai-sdk/google @ai-sdk/openai
|
||||
pnpm add ai @cherrystudio/ai-sdk-provider @ai-sdk/anthropic @ai-sdk/google @ai-sdk/openai
|
||||
```
|
||||
|
||||
> **Note**: This package requires peer dependencies `ai`, `@ai-sdk/anthropic`, `@ai-sdk/google`, and `@ai-sdk/openai` to be installed.
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
"ai": "^5.0.26"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai-compatible": "patch:@ai-sdk/openai-compatible@npm%3A1.0.28#~/.yarn/patches/@ai-sdk-openai-compatible-npm-1.0.28-5705188855.patch",
|
||||
"@ai-sdk/openai-compatible": "1.0.28",
|
||||
"@ai-sdk/provider": "^2.0.0",
|
||||
"@ai-sdk/provider-utils": "^3.0.17"
|
||||
},
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
"@ai-sdk/anthropic": "^2.0.49",
|
||||
"@ai-sdk/azure": "^2.0.87",
|
||||
"@ai-sdk/deepseek": "^1.0.31",
|
||||
"@ai-sdk/openai-compatible": "patch:@ai-sdk/openai-compatible@npm%3A1.0.28#~/.yarn/patches/@ai-sdk-openai-compatible-npm-1.0.28-5705188855.patch",
|
||||
"@ai-sdk/openai-compatible": "1.0.28",
|
||||
"@ai-sdk/provider": "^2.0.0",
|
||||
"@ai-sdk/provider-utils": "^3.0.17",
|
||||
"@ai-sdk/xai": "^2.0.36",
|
||||
|
||||
@ -68,8 +68,8 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.2.4",
|
||||
"@tiptap/core": "^3.2.0",
|
||||
"@tiptap/pm": "^3.2.0",
|
||||
"@tiptap/core": "3.2.0",
|
||||
"@tiptap/pm": "3.2.0",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
@ -89,5 +89,5 @@
|
||||
"build": "tsdown",
|
||||
"lint": "biome format ./src/ --write && eslint --fix ./src/"
|
||||
},
|
||||
"packageManager": "yarn@4.9.1"
|
||||
"packageManager": "pnpm@10.27.0"
|
||||
}
|
||||
|
||||
138
packages/shared/__tests__/utils.test.ts
Normal file
138
packages/shared/__tests__/utils.test.ts
Normal file
@ -0,0 +1,138 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { isBase64ImageDataUrl, isDataUrl, parseDataUrl } from '../utils'
|
||||
|
||||
describe('parseDataUrl', () => {
|
||||
it('parses a standard base64 image data URL', () => {
|
||||
const result = parseDataUrl('data:image/png;base64,iVBORw0KGgo=')
|
||||
expect(result).toEqual({
|
||||
mediaType: 'image/png',
|
||||
isBase64: true,
|
||||
data: 'iVBORw0KGgo='
|
||||
})
|
||||
})
|
||||
|
||||
it('parses a base64 data URL with additional parameters', () => {
|
||||
const result = parseDataUrl('data:image/jpeg;name=foo;base64,/9j/4AAQ')
|
||||
expect(result).toEqual({
|
||||
mediaType: 'image/jpeg',
|
||||
isBase64: true,
|
||||
data: '/9j/4AAQ'
|
||||
})
|
||||
})
|
||||
|
||||
it('parses a plain text data URL (non-base64)', () => {
|
||||
const result = parseDataUrl('data:text/plain,Hello%20World')
|
||||
expect(result).toEqual({
|
||||
mediaType: 'text/plain',
|
||||
isBase64: false,
|
||||
data: 'Hello%20World'
|
||||
})
|
||||
})
|
||||
|
||||
it('parses a data URL with empty media type', () => {
|
||||
const result = parseDataUrl('data:;base64,SGVsbG8=')
|
||||
expect(result).toEqual({
|
||||
mediaType: undefined,
|
||||
isBase64: true,
|
||||
data: 'SGVsbG8='
|
||||
})
|
||||
})
|
||||
|
||||
it('returns null for non-data URLs', () => {
|
||||
const result = parseDataUrl('https://example.com/image.png')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('returns null for malformed data URL without comma', () => {
|
||||
const result = parseDataUrl('data:image/png;base64')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('handles empty string', () => {
|
||||
const result = parseDataUrl('')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
it('handles large base64 data without performance issues', () => {
|
||||
// Simulate a 4K image base64 string (about 1MB)
|
||||
const largeData = 'A'.repeat(1024 * 1024)
|
||||
const dataUrl = `data:image/png;base64,${largeData}`
|
||||
|
||||
const start = performance.now()
|
||||
const result = parseDataUrl(dataUrl)
|
||||
const duration = performance.now() - start
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.mediaType).toBe('image/png')
|
||||
expect(result?.isBase64).toBe(true)
|
||||
expect(result?.data).toBe(largeData)
|
||||
// Should complete in under 10ms (string operations are fast)
|
||||
expect(duration).toBeLessThan(10)
|
||||
})
|
||||
|
||||
it('parses SVG data URL', () => {
|
||||
const result = parseDataUrl('data:image/svg+xml;base64,PHN2Zz4=')
|
||||
expect(result).toEqual({
|
||||
mediaType: 'image/svg+xml',
|
||||
isBase64: true,
|
||||
data: 'PHN2Zz4='
|
||||
})
|
||||
})
|
||||
|
||||
it('parses JSON data URL', () => {
|
||||
const result = parseDataUrl('data:application/json,{"key":"value"}')
|
||||
expect(result).toEqual({
|
||||
mediaType: 'application/json',
|
||||
isBase64: false,
|
||||
data: '{"key":"value"}'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('isDataUrl', () => {
|
||||
it('returns true for valid data URLs', () => {
|
||||
expect(isDataUrl('data:image/png;base64,ABC')).toBe(true)
|
||||
expect(isDataUrl('data:text/plain,hello')).toBe(true)
|
||||
expect(isDataUrl('data:,simple')).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false for non-data URLs', () => {
|
||||
expect(isDataUrl('https://example.com')).toBe(false)
|
||||
expect(isDataUrl('file:///path/to/file')).toBe(false)
|
||||
expect(isDataUrl('')).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for malformed data URLs', () => {
|
||||
expect(isDataUrl('data:')).toBe(false)
|
||||
expect(isDataUrl('data:image/png')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isBase64ImageDataUrl', () => {
|
||||
it('returns true for base64 image data URLs', () => {
|
||||
expect(isBase64ImageDataUrl('data:image/png;base64,ABC')).toBe(true)
|
||||
expect(isBase64ImageDataUrl('data:image/jpeg;base64,/9j/')).toBe(true)
|
||||
expect(isBase64ImageDataUrl('data:image/gif;base64,R0lG')).toBe(true)
|
||||
expect(isBase64ImageDataUrl('data:image/webp;base64,UklG')).toBe(true)
|
||||
})
|
||||
|
||||
it('returns false for non-base64 image data URLs', () => {
|
||||
expect(isBase64ImageDataUrl('data:image/svg+xml,<svg></svg>')).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for non-image data URLs', () => {
|
||||
expect(isBase64ImageDataUrl('data:text/plain;base64,SGVsbG8=')).toBe(false)
|
||||
expect(isBase64ImageDataUrl('data:application/json,{}')).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for regular URLs', () => {
|
||||
expect(isBase64ImageDataUrl('https://example.com/image.png')).toBe(false)
|
||||
expect(isBase64ImageDataUrl('file:///image.png')).toBe(false)
|
||||
})
|
||||
|
||||
it('returns false for malformed data URLs', () => {
|
||||
expect(isBase64ImageDataUrl('data:image/png')).toBe(false)
|
||||
expect(isBase64ImageDataUrl('')).toBe(false)
|
||||
})
|
||||
})
|
||||
@ -4,7 +4,7 @@
|
||||
*
|
||||
* ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
|
||||
* THIS FILE IS AUTOMATICALLY GENERATED BY A SCRIPT. DO NOT EDIT IT MANUALLY!
|
||||
* Run `yarn update:languages` to update this file.
|
||||
* Run `pnpm update:languages` to update this file.
|
||||
* ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
|
||||
*
|
||||
*/
|
||||
|
||||
@ -88,3 +88,81 @@ const TRAILING_VERSION_REGEX = /\/v\d+(?:alpha|beta)?\/?$/i
|
||||
export function withoutTrailingApiVersion(url: string): string {
|
||||
return url.replace(TRAILING_VERSION_REGEX, '')
|
||||
}
|
||||
|
||||
export interface DataUrlParts {
|
||||
/** The media type (e.g., 'image/png', 'text/plain') */
|
||||
mediaType?: string
|
||||
/** Whether the data is base64 encoded */
|
||||
isBase64: boolean
|
||||
/** The data portion (everything after the comma). This is the raw string, not decoded. */
|
||||
data: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a data URL into its component parts without using regex on the data portion.
|
||||
* This is memory-safe for large data URLs (e.g., 4K images) as it uses indexOf instead of regex.
|
||||
*
|
||||
* Data URL format: data:[<mediatype>][;base64],<data>
|
||||
*
|
||||
* @param url - The data URL string to parse
|
||||
* @returns DataUrlParts if valid, null if invalid
|
||||
*
|
||||
* @example
|
||||
* parseDataUrl('data:image/png;base64,iVBORw0KGgo...')
|
||||
* // { mediaType: 'image/png', isBase64: true, data: 'iVBORw0KGgo...' }
|
||||
*
|
||||
* parseDataUrl('data:text/plain,Hello')
|
||||
* // { mediaType: 'text/plain', isBase64: false, data: 'Hello' }
|
||||
*
|
||||
* parseDataUrl('invalid-url')
|
||||
* // null
|
||||
*/
|
||||
export function parseDataUrl(url: string): DataUrlParts | null {
|
||||
if (!url.startsWith('data:')) {
|
||||
return null
|
||||
}
|
||||
|
||||
const commaIndex = url.indexOf(',')
|
||||
if (commaIndex === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
const header = url.slice(5, commaIndex)
|
||||
|
||||
const isBase64 = header.includes(';base64')
|
||||
|
||||
const semicolonIndex = header.indexOf(';')
|
||||
const mediaType = (semicolonIndex === -1 ? header : header.slice(0, semicolonIndex)).trim() || undefined
|
||||
|
||||
const data = url.slice(commaIndex + 1)
|
||||
|
||||
return { mediaType, isBase64, data }
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string is a data URL.
|
||||
*
|
||||
* @param url - The string to check
|
||||
* @returns true if the string is a valid data URL
|
||||
*/
|
||||
export function isDataUrl(url: string): boolean {
|
||||
return url.startsWith('data:') && url.includes(',')
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a data URL contains base64-encoded image data.
|
||||
*
|
||||
* @param url - The data URL to check
|
||||
* @returns true if the URL is a base64-encoded image data URL
|
||||
*/
|
||||
export function isBase64ImageDataUrl(url: string): boolean {
|
||||
if (!url.startsWith('data:image/')) {
|
||||
return false
|
||||
}
|
||||
const commaIndex = url.indexOf(',')
|
||||
if (commaIndex === -1) {
|
||||
return false
|
||||
}
|
||||
const header = url.slice(5, commaIndex)
|
||||
return header.includes(';base64')
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ index 48e2f6263c6ee4c75d7e5c28733e64f6ebe92200..00d0729c4a3cbf9a48e8e1e962c7e2b2
|
||||
+ sendReasoning: z.ZodOptional<z.ZodBoolean>;
|
||||
}, z.core.$strip>;
|
||||
type OpenAICompatibleProviderOptions = z.infer<typeof openaiCompatibleProviderOptions>;
|
||||
|
||||
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index da237bb35b7fa8e24b37cd861ee73dfc51cdfc72..b3060fbaf010e30b64df55302807828e5bfe0f9a 100644
|
||||
--- a/dist/index.js
|
||||
@ -48,7 +48,7 @@ index da237bb35b7fa8e24b37cd861ee73dfc51cdfc72..b3060fbaf010e30b64df55302807828e
|
||||
messages.push({
|
||||
role: "assistant",
|
||||
content: text,
|
||||
+ reasoning_content: reasoning_text ?? undefined,
|
||||
+ reasoning_content: reasoning_text || undefined,
|
||||
tool_calls: toolCalls.length > 0 ? toolCalls : void 0,
|
||||
...metadata
|
||||
});
|
||||
@ -60,7 +60,7 @@ index da237bb35b7fa8e24b37cd861ee73dfc51cdfc72..b3060fbaf010e30b64df55302807828e
|
||||
+ textVerbosity: import_v4.z.string().optional(),
|
||||
+ sendReasoning: import_v4.z.boolean().optional()
|
||||
});
|
||||
|
||||
|
||||
// src/openai-compatible-error.ts
|
||||
@@ -378,7 +387,7 @@ var OpenAICompatibleChatLanguageModel = class {
|
||||
reasoning_effort: compatibleOptions.reasoningEffort,
|
||||
@ -175,7 +175,7 @@ index a809a7aa0e148bfd43e01dd7b018568b151c8ad5..565b605eeacd9830b2b0e817e58ad0c5
|
||||
messages.push({
|
||||
role: "assistant",
|
||||
content: text,
|
||||
+ reasoning_content: reasoning_text ?? undefined,
|
||||
+ reasoning_content: reasoning_text || undefined,
|
||||
tool_calls: toolCalls.length > 0 ? toolCalls : void 0,
|
||||
...metadata
|
||||
});
|
||||
@ -187,7 +187,7 @@ index a809a7aa0e148bfd43e01dd7b018568b151c8ad5..565b605eeacd9830b2b0e817e58ad0c5
|
||||
+ textVerbosity: z.string().optional(),
|
||||
+ sendReasoning: z.boolean().optional()
|
||||
});
|
||||
|
||||
|
||||
// src/openai-compatible-error.ts
|
||||
@@ -362,7 +371,7 @@ var OpenAICompatibleChatLanguageModel = class {
|
||||
reasoning_effort: compatibleOptions.reasoningEffort,
|
||||
25417
pnpm-lock.yaml
Normal file
25417
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- 'packages/*'
|
||||
@ -50,7 +50,7 @@ Usage Instructions:
|
||||
- pt-pt (Portuguese)
|
||||
|
||||
Run Command:
|
||||
yarn i18n:translate
|
||||
pnpm i18n:translate
|
||||
|
||||
Performance Optimization Recommendations:
|
||||
- For stable API services: MAX_CONCURRENT_TRANSLATIONS=8, TRANSLATION_DELAY_MS=50
|
||||
|
||||
@ -2,14 +2,14 @@ const { Arch } = require('electron-builder')
|
||||
const { downloadNpmPackage } = require('./utils')
|
||||
|
||||
// if you want to add new prebuild binaries packages with different architectures, you can add them here
|
||||
// please add to allX64 and allArm64 from yarn.lock
|
||||
// please add to allX64 and allArm64 from pnpm-lock.yaml
|
||||
const allArm64 = {
|
||||
'@img/sharp-darwin-arm64': '0.34.3',
|
||||
'@img/sharp-win32-arm64': '0.34.3',
|
||||
'@img/sharp-linux-arm64': '0.34.3',
|
||||
|
||||
'@img/sharp-libvips-darwin-arm64': '1.2.0',
|
||||
'@img/sharp-libvips-linux-arm64': '1.2.0',
|
||||
'@img/sharp-libvips-darwin-arm64': '1.2.4',
|
||||
'@img/sharp-libvips-linux-arm64': '1.2.4',
|
||||
|
||||
'@libsql/darwin-arm64': '0.4.7',
|
||||
'@libsql/linux-arm64-gnu': '0.4.7',
|
||||
@ -24,8 +24,8 @@ const allX64 = {
|
||||
'@img/sharp-linux-x64': '0.34.3',
|
||||
'@img/sharp-win32-x64': '0.34.3',
|
||||
|
||||
'@img/sharp-libvips-darwin-x64': '1.2.0',
|
||||
'@img/sharp-libvips-linux-x64': '1.2.0',
|
||||
'@img/sharp-libvips-darwin-x64': '1.2.4',
|
||||
'@img/sharp-libvips-linux-x64': '1.2.4',
|
||||
|
||||
'@libsql/darwin-x64': '0.4.7',
|
||||
'@libsql/linux-x64-gnu': '0.4.7',
|
||||
|
||||
@ -145,7 +145,7 @@ export function main() {
|
||||
console.log('i18n 检查已通过')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
throw new Error(`检查未通过。尝试运行 yarn i18n:sync 以解决问题。`)
|
||||
throw new Error(`检查未通过。尝试运行 pnpm i18n:sync 以解决问题。`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -57,7 +57,7 @@ function generateLanguagesFileContent(languages: Record<string, LanguageData>):
|
||||
*
|
||||
* ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
|
||||
* THIS FILE IS AUTOMATICALLY GENERATED BY A SCRIPT. DO NOT EDIT IT MANUALLY!
|
||||
* Run \`yarn update:languages\` to update this file.
|
||||
* Run \`pnpm update:languages\` to update this file.
|
||||
* ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
|
||||
*
|
||||
*/
|
||||
@ -81,7 +81,7 @@ export const languages: Record<string, LanguageData> = ${languagesObjectString};
|
||||
async function format(filePath: string): Promise<void> {
|
||||
console.log('🎨 Formatting file with Biome...')
|
||||
try {
|
||||
await execAsync(`yarn biome format --write ${filePath}`)
|
||||
await execAsync(`pnpm biome format --write ${filePath}`)
|
||||
console.log('✅ Biome formatting complete.')
|
||||
} catch (e: any) {
|
||||
console.error('❌ Biome formatting failed:', e.stdout || e.stderr)
|
||||
@ -96,7 +96,7 @@ async function format(filePath: string): Promise<void> {
|
||||
async function checkTypeScript(filePath: string): Promise<void> {
|
||||
console.log('🧐 Checking file with TypeScript compiler...')
|
||||
try {
|
||||
await execAsync(`yarn tsc --noEmit --skipLibCheck ${filePath}`)
|
||||
await execAsync(`pnpm tsc --noEmit --skipLibCheck ${filePath}`)
|
||||
console.log('✅ TypeScript check passed.')
|
||||
} catch (e: any) {
|
||||
console.error('❌ TypeScript check failed:', e.stdout || e.stderr)
|
||||
|
||||
@ -18,7 +18,7 @@ if (!['patch', 'minor', 'major'].includes(versionType)) {
|
||||
}
|
||||
|
||||
// 更新版本
|
||||
exec(`yarn version ${versionType} --immediate`)
|
||||
exec(`pnpm version ${versionType}`)
|
||||
|
||||
// 读取更新后的 package.json 获取新版本号
|
||||
const updatedPackageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'))
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
scanDir
|
||||
} from '@main/utils/file'
|
||||
import { documentExts, imageExts, KB, MB } from '@shared/config/constant'
|
||||
import { parseDataUrl } from '@shared/utils'
|
||||
import type { FileMetadata, NotesTreeNode } from '@types'
|
||||
import { FileTypes } from '@types'
|
||||
import chardet from 'chardet'
|
||||
@ -672,8 +673,8 @@ class FileStorage {
|
||||
throw new Error('Base64 data is required')
|
||||
}
|
||||
|
||||
// 移除 base64 头部信息(如果存在)
|
||||
const base64String = base64Data.replace(/^data:.*;base64,/, '')
|
||||
const parseResult = parseDataUrl(base64Data)
|
||||
const base64String = parseResult?.data ?? base64Data
|
||||
const buffer = Buffer.from(base64String, 'base64')
|
||||
const uuid = uuidv4()
|
||||
const ext = '.png'
|
||||
@ -1464,8 +1465,8 @@ class FileStorage {
|
||||
})
|
||||
|
||||
if (filePath) {
|
||||
const base64Data = data.replace(/^data:image\/png;base64,/, '')
|
||||
fs.writeFileSync(filePath, base64Data, 'base64')
|
||||
const parseResult = parseDataUrl(data)
|
||||
fs.writeFileSync(filePath, parseResult?.data ?? data, 'base64')
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[IPC - Error] An error occurred saving the image:', error as Error)
|
||||
|
||||
@ -39,22 +39,22 @@ const agent = await agentService.createAgent({
|
||||
|
||||
```bash
|
||||
# Apply schema changes
|
||||
yarn agents:generate
|
||||
pnpm agents:generate
|
||||
|
||||
# Quick development sync
|
||||
yarn agents:push
|
||||
pnpm agents:push
|
||||
|
||||
# Database tools
|
||||
yarn agents:studio # Open Drizzle Studio
|
||||
yarn agents:health # Health check
|
||||
yarn agents:drop # Reset database
|
||||
pnpm agents:studio # Open Drizzle Studio
|
||||
pnpm agents:health # Health check
|
||||
pnpm agents:drop # Reset database
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Edit schema** in `/database/schema/`
|
||||
2. **Generate migration** with `yarn agents:generate`
|
||||
3. **Test changes** with `yarn agents:health`
|
||||
2. **Generate migration** with `pnpm agents:generate`
|
||||
3. **Test changes** with `pnpm agents:health`
|
||||
4. **Deploy** - migrations apply automatically
|
||||
|
||||
## Services
|
||||
@ -69,13 +69,13 @@ yarn agents:drop # Reset database
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
yarn agents:health
|
||||
pnpm agents:health
|
||||
|
||||
# Apply migrations
|
||||
yarn agents:migrate
|
||||
pnpm agents:migrate
|
||||
|
||||
# Reset completely
|
||||
yarn agents:reset --yes
|
||||
pnpm agents:reset --yes
|
||||
```
|
||||
|
||||
The simplified migration system reduced complexity from 463 to ~30 lines while maintaining all functionality through Drizzle's native migration system.
|
||||
|
||||
@ -120,6 +120,21 @@ export class AiSdkToChunkAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果有累积的思考内容,发送 THINKING_COMPLETE chunk 并清空
|
||||
* @param final 包含 reasoningContent 的状态对象
|
||||
* @returns 是否发送了 THINKING_COMPLETE chunk
|
||||
*/
|
||||
private emitThinkingCompleteIfNeeded(final: { reasoningContent: string; [key: string]: any }) {
|
||||
if (final.reasoningContent) {
|
||||
this.onChunk({
|
||||
type: ChunkType.THINKING_COMPLETE,
|
||||
text: final.reasoningContent
|
||||
})
|
||||
final.reasoningContent = ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 AI SDK chunk 为 Cherry Studio chunk 并调用回调
|
||||
* @param chunk AI SDK 的 chunk 数据
|
||||
@ -145,6 +160,9 @@ export class AiSdkToChunkAdapter {
|
||||
}
|
||||
// === 文本相关事件 ===
|
||||
case 'text-start':
|
||||
// 如果有未完成的思考内容,先生成 THINKING_COMPLETE
|
||||
// 这处理了某些提供商不发送 reasoning-end 事件的情况
|
||||
this.emitThinkingCompleteIfNeeded(final)
|
||||
this.onChunk({
|
||||
type: ChunkType.TEXT_START
|
||||
})
|
||||
@ -215,11 +233,7 @@ export class AiSdkToChunkAdapter {
|
||||
})
|
||||
break
|
||||
case 'reasoning-end':
|
||||
this.onChunk({
|
||||
type: ChunkType.THINKING_COMPLETE,
|
||||
text: final.reasoningContent || ''
|
||||
})
|
||||
final.reasoningContent = ''
|
||||
this.emitThinkingCompleteIfNeeded(final)
|
||||
break
|
||||
|
||||
// === 工具调用相关事件(原始 AI SDK 事件,如果没有被中间件处理) ===
|
||||
|
||||
@ -8,13 +8,13 @@ import { loggerService } from '@logger'
|
||||
import { isImageEnhancementModel, isVisionModel } from '@renderer/config/models'
|
||||
import type { Message, Model } from '@renderer/types'
|
||||
import type { FileMessageBlock, ImageMessageBlock, ThinkingMessageBlock } from '@renderer/types/newMessage'
|
||||
import { parseDataUrlMediaType } from '@renderer/utils/image'
|
||||
import {
|
||||
findFileBlocks,
|
||||
findImageBlocks,
|
||||
findThinkingBlocks,
|
||||
getMainTextContent
|
||||
} from '@renderer/utils/messageUtils/find'
|
||||
import { parseDataUrl } from '@shared/utils'
|
||||
import type {
|
||||
AssistantModelMessage,
|
||||
FilePart,
|
||||
@ -69,18 +69,16 @@ async function convertImageBlockToImagePart(imageBlocks: ImageMessageBlock[]): P
|
||||
}
|
||||
} else if (imageBlock.url) {
|
||||
const url = imageBlock.url
|
||||
const isDataUrl = url.startsWith('data:')
|
||||
if (isDataUrl) {
|
||||
const { mediaType } = parseDataUrlMediaType(url)
|
||||
const commaIndex = url.indexOf(',')
|
||||
if (commaIndex === -1) {
|
||||
logger.error('Malformed data URL detected (missing comma separator), image will be excluded:', {
|
||||
urlPrefix: url.slice(0, 50) + '...'
|
||||
})
|
||||
continue
|
||||
}
|
||||
const base64Data = url.slice(commaIndex + 1)
|
||||
parts.push({ type: 'image', image: base64Data, ...(mediaType ? { mediaType } : {}) })
|
||||
const parseResult = parseDataUrl(url)
|
||||
if (parseResult?.isBase64) {
|
||||
const { mediaType, data } = parseResult
|
||||
parts.push({ type: 'image', image: data, ...(mediaType ? { mediaType } : {}) })
|
||||
} else if (url.startsWith('data:')) {
|
||||
// Malformed data URL or non-base64 data URL
|
||||
logger.error('Malformed or non-base64 data URL detected, image will be excluded:', {
|
||||
urlPrefix: url.slice(0, 50) + '...'
|
||||
})
|
||||
continue
|
||||
} else {
|
||||
// For remote URLs we keep payload minimal to match existing expectations.
|
||||
parts.push({ type: 'image', image: url })
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
import { loggerService } from '@logger'
|
||||
import { download } from '@renderer/utils/download'
|
||||
import { convertImageToPng } from '@renderer/utils/image'
|
||||
import { parseDataUrl } from '@shared/utils'
|
||||
import type { ImageProps as AntImageProps } from 'antd'
|
||||
import { Dropdown, Image as AntImage, Space } from 'antd'
|
||||
import { Base64 } from 'js-base64'
|
||||
@ -37,12 +38,13 @@ const ImageViewer: React.FC<ImageViewerProps> = ({ src, style, ...props }) => {
|
||||
let blob: Blob
|
||||
|
||||
if (src.startsWith('data:')) {
|
||||
// 处理 base64 格式的图片
|
||||
const match = src.match(/^data:(image\/\w+);base64,(.+)$/)
|
||||
if (!match) throw new Error('Invalid base64 image format')
|
||||
const mimeType = match[1]
|
||||
const byteArray = Base64.toUint8Array(match[2])
|
||||
blob = new Blob([byteArray], { type: mimeType })
|
||||
// 处理 base64 格式的图片 - 使用 parseDataUrl 避免正则匹配大字符串导致OOM
|
||||
const parseResult = parseDataUrl(src)
|
||||
if (!parseResult || !parseResult.mediaType || !parseResult.isBase64) {
|
||||
throw new Error('Invalid base64 image format')
|
||||
}
|
||||
const byteArray = Base64.toUint8Array(parseResult.data)
|
||||
blob = new Blob([byteArray], { type: parseResult.mediaType })
|
||||
} else if (src.startsWith('file://')) {
|
||||
// 处理本地文件路径
|
||||
const bytes = await window.api.fs.read(src)
|
||||
|
||||
@ -8,7 +8,7 @@ exports[`InputEmbeddingDimension > basic rendering > should match snapshot with
|
||||
<input
|
||||
data-testid="input-number"
|
||||
placeholder="请输入维度大小"
|
||||
style="flex: 1;"
|
||||
style="flex: 1 1 0%;"
|
||||
type="number"
|
||||
value="1536"
|
||||
/>
|
||||
@ -43,7 +43,7 @@ exports[`InputEmbeddingDimension > basic rendering > should match snapshot with
|
||||
<input
|
||||
data-testid="input-number"
|
||||
placeholder="请输入维度大小"
|
||||
style="flex: 1;"
|
||||
style="flex: 1 1 0%;"
|
||||
type="number"
|
||||
value=""
|
||||
/>
|
||||
|
||||
@ -24,6 +24,7 @@ exports[`Spinner > should match snapshot 1`] = `
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
style="color: unset;"
|
||||
viewBox="0 0 24 24"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@ -212,6 +212,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
||||
type: 'openai',
|
||||
apiKey: '',
|
||||
apiHost: 'https://api.302.ai',
|
||||
anthropicApiHost: 'https://api.302.ai',
|
||||
models: SYSTEM_MODELS['302ai'],
|
||||
isSystem: true,
|
||||
enabled: false
|
||||
|
||||
@ -1297,6 +1297,7 @@
|
||||
"backup": {
|
||||
"file_format": "Backup file format error"
|
||||
},
|
||||
"base64DataTruncated": "Base64 image data truncated, size",
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "Open debug panel",
|
||||
@ -1377,6 +1378,8 @@
|
||||
"text": "Text",
|
||||
"toolInput": "Tool Input",
|
||||
"toolName": "Tool Name",
|
||||
"truncated": "Data truncated, original size",
|
||||
"truncatedBadge": "Truncated",
|
||||
"unknown": "Unknown error",
|
||||
"usage": "Usage",
|
||||
"user_message_not_found": "Cannot find original user message to resend",
|
||||
@ -3165,6 +3168,7 @@
|
||||
"label": "App Data",
|
||||
"migration_title": "Data Migration",
|
||||
"new_path": "New Path",
|
||||
"open": "Open Directory",
|
||||
"original_path": "Original Path",
|
||||
"path_change_failed": "Failed to change data directory",
|
||||
"path_changed_without_copy": "Path changed successfully",
|
||||
|
||||
@ -1297,6 +1297,7 @@
|
||||
"backup": {
|
||||
"file_format": "备份文件格式错误"
|
||||
},
|
||||
"base64DataTruncated": "Base64 图片数据已截断,大小",
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "打开调试面板",
|
||||
@ -1377,6 +1378,8 @@
|
||||
"text": "文本",
|
||||
"toolInput": "工具输入",
|
||||
"toolName": "工具名",
|
||||
"truncated": "数据已截断,原始大小",
|
||||
"truncatedBadge": "已截断",
|
||||
"unknown": "未知错误",
|
||||
"usage": "用量",
|
||||
"user_message_not_found": "无法找到原始用户消息",
|
||||
@ -3165,6 +3168,7 @@
|
||||
"label": "应用数据",
|
||||
"migration_title": "数据迁移",
|
||||
"new_path": "新路径",
|
||||
"open": "打开目录",
|
||||
"original_path": "原始路径",
|
||||
"path_change_failed": "数据目录更改失败",
|
||||
"path_changed_without_copy": "路径已更改成功",
|
||||
|
||||
@ -1297,6 +1297,7 @@
|
||||
"backup": {
|
||||
"file_format": "備份檔案格式錯誤"
|
||||
},
|
||||
"base64DataTruncated": "Base64 圖片資料已截斷,大小",
|
||||
"boundary": {
|
||||
"default": {
|
||||
"devtools": "開啟除錯面板",
|
||||
@ -1377,6 +1378,8 @@
|
||||
"text": "文字",
|
||||
"toolInput": "工具輸入",
|
||||
"toolName": "工具名稱",
|
||||
"truncated": "資料已截斷,原始大小",
|
||||
"truncatedBadge": "已截斷",
|
||||
"unknown": "未知錯誤",
|
||||
"usage": "用量",
|
||||
"user_message_not_found": "無法找到原始使用者訊息",
|
||||
@ -3165,6 +3168,7 @@
|
||||
"label": "應用程式資料",
|
||||
"migration_title": "資料移轉",
|
||||
"new_path": "新路徑",
|
||||
"open": "開啟目錄",
|
||||
"original_path": "原始路徑",
|
||||
"path_change_failed": "資料目錄變更失敗",
|
||||
"path_changed_without_copy": "路徑已變更成功",
|
||||
|
||||
@ -3165,6 +3165,7 @@
|
||||
"label": "Anwendungsdaten",
|
||||
"migration_title": "Datenmigration",
|
||||
"new_path": "Neuer Pfad",
|
||||
"open": "Offenes Verzeichnis",
|
||||
"original_path": "Ursprünglicher Pfad",
|
||||
"path_change_failed": "Datenverzeichnisänderung fehlgeschlagen",
|
||||
"path_changed_without_copy": "Pfad erfolgreich geändert",
|
||||
|
||||
@ -3165,6 +3165,7 @@
|
||||
"label": "Δεδομένα εφαρμογής",
|
||||
"migration_title": "Μεταφορά δεδομένων",
|
||||
"new_path": "Νέα διαδρομή",
|
||||
"open": "Ανοιχτός Κατάλογος",
|
||||
"original_path": "Αρχική διαδρομή",
|
||||
"path_change_failed": "Η αλλαγή του καταλόγου δεδομένων απέτυχε",
|
||||
"path_changed_without_copy": "Η διαδρομή άλλαξε επιτυχώς",
|
||||
|
||||
@ -3165,6 +3165,7 @@
|
||||
"label": "Datos de la aplicación",
|
||||
"migration_title": "Migración de datos",
|
||||
"new_path": "Nueva ruta",
|
||||
"open": "Directorio abierto",
|
||||
"original_path": "Ruta original",
|
||||
"path_change_failed": "Error al cambiar el directorio de datos",
|
||||
"path_changed_without_copy": "La ruta se ha cambiado correctamente",
|
||||
|
||||
@ -3165,6 +3165,7 @@
|
||||
"label": "Données de l'application",
|
||||
"migration_title": "Migration des données",
|
||||
"new_path": "Nouveau chemin",
|
||||
"open": "Répertoire ouvert",
|
||||
"original_path": "Chemin d'origine",
|
||||
"path_change_failed": "Échec de la modification du répertoire de données",
|
||||
"path_changed_without_copy": "Le chemin a été modifié avec succès",
|
||||
|
||||
@ -3165,6 +3165,7 @@
|
||||
"label": "アプリデータ",
|
||||
"migration_title": "データ移行",
|
||||
"new_path": "新しいパス",
|
||||
"open": "オープンディレクトリ",
|
||||
"original_path": "元のパス",
|
||||
"path_change_failed": "データディレクトリの変更に失敗しました",
|
||||
"path_changed_without_copy": "パスが変更されました。",
|
||||
|
||||
@ -3165,6 +3165,7 @@
|
||||
"label": "Dados do aplicativo",
|
||||
"migration_title": "Migração de Dados",
|
||||
"new_path": "Novo Caminho",
|
||||
"open": "Diretório Aberto",
|
||||
"original_path": "Caminho Original",
|
||||
"path_change_failed": "Falha ao alterar o diretório de dados",
|
||||
"path_changed_without_copy": "O caminho foi alterado com sucesso",
|
||||
|
||||
@ -3165,6 +3165,7 @@
|
||||
"label": "Date aplicație",
|
||||
"migration_title": "Migrare date",
|
||||
"new_path": "Cale nouă",
|
||||
"open": "Director Deschis",
|
||||
"original_path": "Cale originală",
|
||||
"path_change_failed": "Schimbarea directorului de date a eșuat",
|
||||
"path_changed_without_copy": "Calea a fost schimbată cu succes",
|
||||
@ -4867,7 +4868,7 @@
|
||||
"custom": {
|
||||
"delete": {
|
||||
"description": "Ești sigur că vrei să ștergi?",
|
||||
"title": "Șterge limbă personalizată"
|
||||
"title": "Șterge limba personalizată"
|
||||
},
|
||||
"error": {
|
||||
"add": "Adăugarea a eșuat",
|
||||
|
||||
@ -3165,6 +3165,7 @@
|
||||
"label": "Данные приложения",
|
||||
"migration_title": "Миграция данных",
|
||||
"new_path": "Новый путь",
|
||||
"open": "Открыть каталог",
|
||||
"original_path": "Исходный путь",
|
||||
"path_change_failed": "Сбой изменения каталога данных",
|
||||
"path_changed_without_copy": "Путь изменен успешно",
|
||||
|
||||
@ -43,6 +43,7 @@ export const CLAUDE_SUPPORTED_PROVIDERS = [
|
||||
'dmxapi',
|
||||
'new-api',
|
||||
'cherryin',
|
||||
'302ai',
|
||||
...CLAUDE_OFFICIAL_SUPPORTED_PROVIDERS
|
||||
]
|
||||
export const OPENAI_CODEX_SUPPORTED_PROVIDERS = ['openai', 'openrouter', 'aihubmix', 'new-api', 'cherryin']
|
||||
@ -96,6 +97,11 @@ export const getCodeToolsApiBaseUrl = (model: Model, type: EndpointType) => {
|
||||
anthropic: {
|
||||
api_base_url: 'https://api.minimaxi.com/anthropic'
|
||||
}
|
||||
},
|
||||
'302ai': {
|
||||
anthropic: {
|
||||
api_base_url: 'https://api.302.ai'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -32,6 +32,8 @@ import {
|
||||
} from '@renderer/types/error'
|
||||
import type { ErrorMessageBlock, Message } from '@renderer/types/newMessage'
|
||||
import { formatAiSdkError, formatError, safeToString } from '@renderer/utils/error'
|
||||
import { formatFileSize } from '@renderer/utils/file'
|
||||
import { KB } from '@shared/config/constant'
|
||||
import { Button } from 'antd'
|
||||
import { Alert as AntdAlert, Modal } from 'antd'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
@ -41,6 +43,38 @@ import styled from 'styled-components'
|
||||
|
||||
const HTTP_ERROR_CODES = [400, 401, 403, 404, 429, 500, 502, 503, 504]
|
||||
|
||||
const MAX_DISPLAY_SIZE = 100 * KB
|
||||
|
||||
/**
|
||||
* Truncate large data to prevent OOM when displaying error details.
|
||||
* Uses simple string operations to avoid regex performance issues with large strings.
|
||||
*/
|
||||
const truncateLargeData = (
|
||||
data: string,
|
||||
t: (key: string) => string
|
||||
): { content: string; truncated: boolean; isLikelyBase64: boolean } => {
|
||||
if (!data || data.length <= MAX_DISPLAY_SIZE) {
|
||||
return { content: data, truncated: false, isLikelyBase64: false }
|
||||
}
|
||||
|
||||
const isLikelyBase64 = data.includes('data:image/') && data.includes(';base64,')
|
||||
const formattedSize = formatFileSize(data.length)
|
||||
|
||||
if (isLikelyBase64) {
|
||||
return {
|
||||
content: `[${t('error.base64DataTruncated')} ~${formattedSize}]`,
|
||||
truncated: true,
|
||||
isLikelyBase64: true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: data.slice(0, MAX_DISPLAY_SIZE) + `\n\n... [${t('error.truncated')} ${formattedSize}]`,
|
||||
truncated: true,
|
||||
isLikelyBase64: false
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
block: ErrorMessageBlock
|
||||
message: Message
|
||||
@ -275,6 +309,16 @@ const Alert = styled(AntdAlert)`
|
||||
}
|
||||
`
|
||||
|
||||
const TruncatedBadge = styled.span`
|
||||
margin-left: 8px;
|
||||
padding: 2px 6px;
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
color: var(--color-warning);
|
||||
background: var(--color-warning-bg, rgba(250, 173, 20, 0.1));
|
||||
border-radius: 4px;
|
||||
`
|
||||
|
||||
// 作为 base,渲染公共字段,应当在 ErrorDetailList 中渲染
|
||||
const BuiltinError = ({ error }: { error: SerializedError }) => {
|
||||
const { t } = useTranslation()
|
||||
@ -309,13 +353,32 @@ const AiSdkErrorBase = ({ error }: { error: SerializedAiSdkError }) => {
|
||||
const { t } = useTranslation()
|
||||
const { highlightCode } = useCodeStyle()
|
||||
const [highlightedString, setHighlightedString] = useState('')
|
||||
const [isTruncated, setIsTruncated] = useState(false)
|
||||
const cause = error.cause
|
||||
|
||||
useEffect(() => {
|
||||
const highlight = async () => {
|
||||
try {
|
||||
const result = await highlightCode(JSON.stringify(JSON.parse(cause || '{}'), null, 2), 'json')
|
||||
setHighlightedString(result)
|
||||
// Truncate large data before processing to prevent OOM
|
||||
const { content: truncatedCause, truncated, isLikelyBase64 } = truncateLargeData(cause || '', t)
|
||||
setIsTruncated(truncated)
|
||||
|
||||
// Skip JSON parsing and syntax highlighting for base64 data
|
||||
if (isLikelyBase64) {
|
||||
setHighlightedString(truncatedCause)
|
||||
return
|
||||
}
|
||||
|
||||
// Try to parse and format JSON
|
||||
try {
|
||||
const parsed = JSON.parse(truncatedCause || '{}')
|
||||
const formatted = JSON.stringify(parsed, null, 2)
|
||||
const result = await highlightCode(formatted, 'json')
|
||||
setHighlightedString(result)
|
||||
} catch {
|
||||
// If not valid JSON, use as-is
|
||||
setHighlightedString(truncatedCause || '')
|
||||
}
|
||||
} catch {
|
||||
setHighlightedString(cause || '')
|
||||
}
|
||||
@ -323,14 +386,16 @@ const AiSdkErrorBase = ({ error }: { error: SerializedAiSdkError }) => {
|
||||
const timer = setTimeout(highlight, 0)
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}, [highlightCode, cause])
|
||||
}, [highlightCode, cause, t])
|
||||
|
||||
return (
|
||||
<>
|
||||
<BuiltinError error={error} />
|
||||
{cause && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.cause')}:</ErrorDetailLabel>
|
||||
<ErrorDetailLabel>
|
||||
{t('error.cause')}:{isTruncated && <TruncatedBadge>{t('error.truncatedBadge')}</TruncatedBadge>}
|
||||
</ErrorDetailLabel>
|
||||
<ErrorDetailValue>
|
||||
<div
|
||||
className="markdown [&_pre]:!bg-transparent [&_pre_span]:whitespace-pre-wrap"
|
||||
@ -343,6 +408,29 @@ const AiSdkErrorBase = ({ error }: { error: SerializedAiSdkError }) => {
|
||||
)
|
||||
}
|
||||
|
||||
// Wrapper component to safely display potentially large data in CodeViewer
|
||||
const TruncatedCodeViewer: React.FC<{
|
||||
value: string
|
||||
label: string
|
||||
language?: string
|
||||
}> = ({ value, label, language = 'json' }) => {
|
||||
const { t } = useTranslation()
|
||||
const { content, truncated, isLikelyBase64 } = truncateLargeData(value, t)
|
||||
|
||||
return (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>
|
||||
{label}:{truncated && <TruncatedBadge>{t('error.truncatedBadge')}</TruncatedBadge>}
|
||||
</ErrorDetailLabel>
|
||||
{isLikelyBase64 ? (
|
||||
<ErrorDetailValue>{content}</ErrorDetailValue>
|
||||
) : (
|
||||
<CodeViewer value={content} className="source-view" language={language} expanded />
|
||||
)}
|
||||
</ErrorDetailItem>
|
||||
)
|
||||
}
|
||||
|
||||
const AiSdkError = ({ error }: { error: SerializedAiSdkErrorUnion }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -360,14 +448,7 @@ const AiSdkError = ({ error }: { error: SerializedAiSdkErrorUnion }) => {
|
||||
)}
|
||||
|
||||
{isSerializedAiSdkAPICallError(error) && (
|
||||
<>
|
||||
{error.responseBody && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.responseBody')}:</ErrorDetailLabel>
|
||||
<CodeViewer value={error.responseBody} className="source-view" language="json" expanded />
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
</>
|
||||
<>{error.responseBody && <TruncatedCodeViewer value={error.responseBody} label={t('error.responseBody')} />}</>
|
||||
)}
|
||||
|
||||
{(isSerializedAiSdkAPICallError(error) || isSerializedAiSdkDownloadError(error)) && (
|
||||
@ -396,23 +477,10 @@ const AiSdkError = ({ error }: { error: SerializedAiSdkErrorUnion }) => {
|
||||
)}
|
||||
|
||||
{error.requestBodyValues && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.requestBodyValues')}:</ErrorDetailLabel>
|
||||
<CodeViewer
|
||||
value={safeToString(error.requestBodyValues)}
|
||||
className="source-view"
|
||||
language="json"
|
||||
expanded
|
||||
/>
|
||||
</ErrorDetailItem>
|
||||
<TruncatedCodeViewer value={safeToString(error.requestBodyValues)} label={t('error.requestBodyValues')} />
|
||||
)}
|
||||
|
||||
{error.data && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.data')}:</ErrorDetailLabel>
|
||||
<CodeViewer value={safeToString(error.data)} className="source-view" language="json" expanded />
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
{error.data && <TruncatedCodeViewer value={safeToString(error.data)} label={t('error.data')} />}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@ -452,6 +452,7 @@ export const SidebarContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
`
|
||||
|
||||
export const NotesTreeContainer = styled.div`
|
||||
|
||||
@ -1,11 +1,4 @@
|
||||
import {
|
||||
CloudServerOutlined,
|
||||
CloudSyncOutlined,
|
||||
FileSearchOutlined,
|
||||
LoadingOutlined,
|
||||
WifiOutlined,
|
||||
YuqueOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { CloudServerOutlined, CloudSyncOutlined, LoadingOutlined, WifiOutlined, YuqueOutlined } from '@ant-design/icons'
|
||||
import DividerWithText from '@renderer/components/DividerWithText'
|
||||
import { NutstoreIcon } from '@renderer/components/Icons/NutstoreIcons'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
@ -23,8 +16,8 @@ import { setSkipBackupFile as _setSkipBackupFile } from '@renderer/store/setting
|
||||
import type { AppInfo } from '@renderer/types'
|
||||
import { formatFileSize } from '@renderer/utils'
|
||||
import { occupiedDirs } from '@shared/config/constant'
|
||||
import { Button, Progress, Switch, Typography } from 'antd'
|
||||
import { FileText, FolderCog, FolderInput, FolderOpen, SaveIcon } from 'lucide-react'
|
||||
import { Button, Progress, Switch, Tooltip, Typography } from 'antd'
|
||||
import { FileText, FolderCog, FolderInput, FolderOpen, FolderOutput, SaveIcon } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -646,9 +639,13 @@ const DataSettings: FC = () => {
|
||||
onClick={() => handleOpenPath(appInfo?.appDataPath)}>
|
||||
{appInfo?.appDataPath}
|
||||
</PathText>
|
||||
<StyledIcon onClick={() => handleOpenPath(appInfo?.appDataPath)} style={{ flexShrink: 0 }} />
|
||||
<Tooltip title={t('settings.data.app_data.select')}>
|
||||
<FolderOutput onClick={handleSelectAppDataPath} style={{ cursor: 'pointer' }} size={16} />
|
||||
</Tooltip>
|
||||
<HStack gap="5px" style={{ marginLeft: '8px' }}>
|
||||
<Button onClick={handleSelectAppDataPath}>{t('settings.data.app_data.select')}</Button>
|
||||
<Button onClick={() => handleOpenPath(appInfo?.appDataPath)}>
|
||||
{t('settings.data.app_data.open')}
|
||||
</Button>
|
||||
</HStack>
|
||||
</PathRow>
|
||||
</SettingRow>
|
||||
@ -659,7 +656,6 @@ const DataSettings: FC = () => {
|
||||
<PathText style={{ color: 'var(--color-text-3)' }} onClick={() => handleOpenPath(appInfo?.logsPath)}>
|
||||
{appInfo?.logsPath}
|
||||
</PathText>
|
||||
<StyledIcon onClick={() => handleOpenPath(appInfo?.logsPath)} style={{ flexShrink: 0 }} />
|
||||
<HStack gap="5px" style={{ marginLeft: '8px' }}>
|
||||
<Button onClick={() => handleOpenPath(appInfo?.logsPath)}>
|
||||
{t('settings.data.app_logs.button')}
|
||||
@ -717,16 +713,6 @@ const Container = styled(HStack)`
|
||||
flex: 1;
|
||||
`
|
||||
|
||||
const StyledIcon = styled(FileSearchOutlined)`
|
||||
color: var(--color-text-2);
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
`
|
||||
|
||||
const MenuList = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@ -8,7 +8,7 @@ import { updateOneBlock } from '@renderer/store/messageBlock'
|
||||
import { selectMessagesForTopic } from '@renderer/store/newMessage'
|
||||
import { newMessagesActions } from '@renderer/store/newMessage'
|
||||
import type { Assistant } from '@renderer/types'
|
||||
import type { PlaceholderMessageBlock, Response } from '@renderer/types/newMessage'
|
||||
import type { PlaceholderMessageBlock, Response, ThinkingMessageBlock } from '@renderer/types/newMessage'
|
||||
import { AssistantMessageStatus, MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage'
|
||||
import { uuid } from '@renderer/utils'
|
||||
import { isAbortError, serializeError } from '@renderer/utils/error'
|
||||
@ -29,10 +29,20 @@ interface BaseCallbacksDependencies {
|
||||
assistantMsgId: string
|
||||
saveUpdatesToDB: any
|
||||
assistant: Assistant
|
||||
getCurrentThinkingInfo?: () => { blockId: string | null; millsec: number }
|
||||
}
|
||||
|
||||
export const createBaseCallbacks = (deps: BaseCallbacksDependencies) => {
|
||||
const { blockManager, dispatch, getState, topicId, assistantMsgId, saveUpdatesToDB, assistant } = deps
|
||||
const {
|
||||
blockManager,
|
||||
dispatch,
|
||||
getState,
|
||||
topicId,
|
||||
assistantMsgId,
|
||||
saveUpdatesToDB,
|
||||
assistant,
|
||||
getCurrentThinkingInfo
|
||||
} = deps
|
||||
|
||||
const startTime = Date.now()
|
||||
const notificationService = NotificationService.getInstance()
|
||||
@ -98,10 +108,17 @@ export const createBaseCallbacks = (deps: BaseCallbacksDependencies) => {
|
||||
const possibleBlockId = findBlockIdForCompletion()
|
||||
|
||||
if (possibleBlockId) {
|
||||
// 更改上一个block的状态为ERROR
|
||||
const changes = {
|
||||
// 更改上一个block的状态为ERROR/PAUSED
|
||||
const changes: Partial<ThinkingMessageBlock> = {
|
||||
status: isErrorTypeAbort ? MessageBlockStatus.PAUSED : MessageBlockStatus.ERROR
|
||||
}
|
||||
// 如果是 thinking block,保留实际思考时间
|
||||
if (blockManager.lastBlockType === MessageBlockType.THINKING) {
|
||||
const thinkingInfo = getCurrentThinkingInfo?.()
|
||||
if (thinkingInfo?.blockId === possibleBlockId && thinkingInfo?.millsec && thinkingInfo.millsec > 0) {
|
||||
changes.thinking_millsec = thinkingInfo.millsec
|
||||
}
|
||||
}
|
||||
blockManager.smartBlockUpdate(possibleBlockId, changes, blockManager.lastBlockType!, true)
|
||||
}
|
||||
|
||||
@ -111,13 +128,28 @@ export const createBaseCallbacks = (deps: BaseCallbacksDependencies) => {
|
||||
if (currentMessage) {
|
||||
const allBlockRefs = findAllBlocks(currentMessage)
|
||||
const blockState = getState().messageBlocks
|
||||
// 获取当前思考信息(如果有),用于保留实际思考时间
|
||||
const thinkingInfo = getCurrentThinkingInfo?.()
|
||||
for (const blockRef of allBlockRefs) {
|
||||
const block = blockState.entities[blockRef.id]
|
||||
if (block && block.status === MessageBlockStatus.STREAMING && block.id !== possibleBlockId) {
|
||||
// 构建更新对象
|
||||
const changes: Partial<ThinkingMessageBlock> = {
|
||||
status: isErrorTypeAbort ? MessageBlockStatus.PAUSED : MessageBlockStatus.ERROR
|
||||
}
|
||||
// 如果是 thinking block 且有思考时间信息,保留实际思考时间
|
||||
if (
|
||||
block.type === MessageBlockType.THINKING &&
|
||||
thinkingInfo?.blockId === block.id &&
|
||||
thinkingInfo?.millsec &&
|
||||
thinkingInfo.millsec > 0
|
||||
) {
|
||||
changes.thinking_millsec = thinkingInfo.millsec
|
||||
}
|
||||
dispatch(
|
||||
updateOneBlock({
|
||||
id: block.id,
|
||||
changes: { status: isErrorTypeAbort ? MessageBlockStatus.PAUSED : MessageBlockStatus.ERROR }
|
||||
changes
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@ -23,6 +23,12 @@ interface CallbacksDependencies {
|
||||
export const createCallbacks = (deps: CallbacksDependencies) => {
|
||||
const { blockManager, dispatch, getState, topicId, assistantMsgId, saveUpdatesToDB, assistant } = deps
|
||||
|
||||
// 首先创建 thinkingCallbacks ,以便传递 getCurrentThinkingInfo 给 baseCallbacks
|
||||
const thinkingCallbacks = createThinkingCallbacks({
|
||||
blockManager,
|
||||
assistantMsgId
|
||||
})
|
||||
|
||||
// 创建基础回调
|
||||
const baseCallbacks = createBaseCallbacks({
|
||||
blockManager,
|
||||
@ -31,13 +37,8 @@ export const createCallbacks = (deps: CallbacksDependencies) => {
|
||||
topicId,
|
||||
assistantMsgId,
|
||||
saveUpdatesToDB,
|
||||
assistant
|
||||
})
|
||||
|
||||
// 创建各类回调
|
||||
const thinkingCallbacks = createThinkingCallbacks({
|
||||
blockManager,
|
||||
assistantMsgId
|
||||
assistant,
|
||||
getCurrentThinkingInfo: thinkingCallbacks.getCurrentThinkingInfo
|
||||
})
|
||||
|
||||
const toolCallbacks = createToolCallbacks({
|
||||
|
||||
@ -19,6 +19,12 @@ export const createThinkingCallbacks = (deps: ThinkingCallbacksDependencies) =>
|
||||
let thinking_millsec_now: number = 0
|
||||
|
||||
return {
|
||||
// 获取当前思考时间(用于停止回复时保留思考时间)
|
||||
getCurrentThinkingInfo: () => ({
|
||||
blockId: thinkingBlockId,
|
||||
millsec: thinking_millsec_now > 0 ? performance.now() - thinking_millsec_now : 0
|
||||
}),
|
||||
|
||||
onThinkingStart: async () => {
|
||||
if (blockManager.hasInitialPlaceholder) {
|
||||
const changes: Partial<MessageBlock> = {
|
||||
|
||||
@ -83,7 +83,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 191,
|
||||
version: 192,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@ -3149,6 +3149,21 @@ const migrateConfig = {
|
||||
logger.error('migrate 191 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'192': (state: RootState) => {
|
||||
try {
|
||||
state.llm.providers.forEach((provider) => {
|
||||
if (provider.id === '302ai') {
|
||||
provider.anthropicApiHost = 'https://api.302.ai'
|
||||
}
|
||||
})
|
||||
state.settings.readClipboardAtStartup = false
|
||||
logger.info('migrate 192 success')
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 192 error', error as Error)
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,8 +7,7 @@ import {
|
||||
captureScrollableAsDataURL,
|
||||
compressImage,
|
||||
convertToBase64,
|
||||
makeSvgSizeAdaptive,
|
||||
parseDataUrlMediaType
|
||||
makeSvgSizeAdaptive
|
||||
} from '../image'
|
||||
|
||||
// mock 依赖
|
||||
@ -202,36 +201,4 @@ describe('utils/image', () => {
|
||||
expect(result.outerHTML).toBe(originalOuterHTML)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseDataUrlMediaType', () => {
|
||||
it('extracts media type and base64 flag from standard data url', () => {
|
||||
const r = parseDataUrlMediaType('data:image/png;base64,AAA')
|
||||
expect(r.mediaType).toBe('image/png')
|
||||
expect(r.isBase64).toBe(true)
|
||||
})
|
||||
|
||||
it('handles additional parameters in header', () => {
|
||||
const r = parseDataUrlMediaType('data:image/jpeg;name=foo;base64,AAA')
|
||||
expect(r.mediaType).toBe('image/jpeg')
|
||||
expect(r.isBase64).toBe(true)
|
||||
})
|
||||
|
||||
it('returns undefined media type when missing and detects non-base64', () => {
|
||||
const r = parseDataUrlMediaType('data:text/plain,hello')
|
||||
expect(r.mediaType).toBe('text/plain')
|
||||
expect(r.isBase64).toBe(false)
|
||||
})
|
||||
|
||||
it('handles empty mediatype header', () => {
|
||||
const r = parseDataUrlMediaType('data:;base64,AAA')
|
||||
expect(r.mediaType).toBeUndefined()
|
||||
expect(r.isBase64).toBe(true)
|
||||
})
|
||||
|
||||
it('gracefully handles non data urls', () => {
|
||||
const r = parseDataUrlMediaType('https://example.com/x.png')
|
||||
expect(r.mediaType).toBeUndefined()
|
||||
expect(r.isBase64).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FileMetadata } from '@renderer/types'
|
||||
import { FileTypes } from '@renderer/types'
|
||||
import { audioExts, documentExts, imageExts, KB, MB, textExts, videoExts } from '@shared/config/constant'
|
||||
import { audioExts, documentExts, GB, imageExts, KB, MB, textExts, videoExts } from '@shared/config/constant'
|
||||
import mime from 'mime-types'
|
||||
|
||||
/**
|
||||
@ -46,6 +46,10 @@ export function removeFileExtension(filePath: string): string {
|
||||
* @returns {string} 格式化后的文件大小字符串
|
||||
*/
|
||||
export function formatFileSize(size: number): string {
|
||||
if (size >= GB) {
|
||||
return (size / GB).toFixed(1) + ' GB'
|
||||
}
|
||||
|
||||
if (size >= MB) {
|
||||
return (size / MB).toFixed(1) + ' MB'
|
||||
}
|
||||
|
||||
@ -617,23 +617,3 @@ export const convertImageToPng = async (blob: Blob): Promise<Blob> => {
|
||||
img.src = url
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse media type from a data URL without using heavy regular expressions.
|
||||
*
|
||||
* data:[<mediatype>][;base64],<data>
|
||||
* - mediatype may be empty (defaults to text/plain;charset=US-ASCII per spec)
|
||||
* - we only care about extracting media type and whether it's base64
|
||||
*/
|
||||
export function parseDataUrlMediaType(url: string): { mediaType?: string; isBase64: boolean } {
|
||||
if (!url.startsWith('data:')) return { isBase64: false }
|
||||
const comma = url.indexOf(',')
|
||||
if (comma === -1) return { isBase64: false }
|
||||
// strip leading 'data:' and take header portion only
|
||||
const header = url.slice(5, comma)
|
||||
const semi = header.indexOf(';')
|
||||
const mediaType = (semi === -1 ? header : header.slice(0, semi)).trim() || undefined
|
||||
// base64 flag may appear anywhere after mediatype in the header
|
||||
const isBase64 = header.indexOf(';base64') !== -1
|
||||
return { mediaType, isBase64 }
|
||||
}
|
||||
|
||||
@ -36,32 +36,32 @@ tests/e2e/
|
||||
|
||||
### 前置条件
|
||||
|
||||
1. 安装依赖:`yarn install`
|
||||
2. 构建应用:`yarn build`
|
||||
1. 安装依赖:`pnpm install`
|
||||
2. 构建应用:`pnpm build`
|
||||
|
||||
### 运行命令
|
||||
|
||||
```bash
|
||||
# 运行所有 e2e 测试
|
||||
yarn test:e2e
|
||||
pnpm test:e2e
|
||||
|
||||
# 带可视化窗口运行(可以看到测试过程)
|
||||
yarn test:e2e --headed
|
||||
pnpm test:e2e --headed
|
||||
|
||||
# 运行特定测试文件
|
||||
yarn playwright test tests/e2e/specs/app-launch.spec.ts
|
||||
pnpm playwright test tests/e2e/specs/app-launch.spec.ts
|
||||
|
||||
# 运行匹配名称的测试
|
||||
yarn playwright test -g "should launch"
|
||||
pnpm playwright test -g "should launch"
|
||||
|
||||
# 调试模式(会暂停并打开调试器)
|
||||
yarn playwright test --debug
|
||||
pnpm playwright test --debug
|
||||
|
||||
# 使用 Playwright UI 模式
|
||||
yarn playwright test --ui
|
||||
pnpm playwright test --ui
|
||||
|
||||
# 查看测试报告
|
||||
yarn playwright show-report
|
||||
pnpm playwright show-report
|
||||
```
|
||||
|
||||
### 常见问题
|
||||
@ -71,7 +71,7 @@ A: 默认是 headless 模式,使用 `--headed` 参数可看到窗口。
|
||||
|
||||
**Q: 测试失败,提示找不到元素?**
|
||||
A:
|
||||
1. 确保已运行 `yarn build` 构建最新代码
|
||||
1. 确保已运行 `pnpm build` 构建最新代码
|
||||
2. 检查选择器是否正确,UI 可能已更新
|
||||
|
||||
**Q: 测试超时?**
|
||||
|
||||
@ -56,6 +56,18 @@ export default defineConfig({
|
||||
'packages/aiCore/**/__tests__/**/*.{test,spec}.{ts,tsx}'
|
||||
]
|
||||
}
|
||||
},
|
||||
// shared 包单元测试配置
|
||||
{
|
||||
extends: true,
|
||||
test: {
|
||||
name: 'shared',
|
||||
environment: 'node',
|
||||
include: [
|
||||
'packages/shared/**/*.{test,spec}.{ts,tsx}',
|
||||
'packages/shared/**/__tests__/**/*.{test,spec}.{ts,tsx}'
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
// 全局共享配置
|
||||
|
||||
Loading…
Reference in New Issue
Block a user