mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-06 21:35:52 +08:00
Merge branch 'main' of github.com:CherryHQ/cherry-studio into v2
This commit is contained in:
commit
1b67b851b7
4
.github/workflows/auto-i18n.yml
vendored
4
.github/workflows/auto-i18n.yml
vendored
@ -27,9 +27,9 @@ jobs:
|
|||||||
ref: ${{ github.event.pull_request.head.ref }}
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
|
|
||||||
- name: 📦 Setting Node.js
|
- name: 📦 Setting Node.js
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
package-manager-cache: false
|
package-manager-cache: false
|
||||||
|
|
||||||
- name: 📦 Install dependencies in isolated directory
|
- name: 📦 Install dependencies in isolated directory
|
||||||
|
|||||||
10
.github/workflows/github-issue-tracker.yml
vendored
10
.github/workflows/github-issue-tracker.yml
vendored
@ -5,7 +5,7 @@ on:
|
|||||||
types: [opened]
|
types: [opened]
|
||||||
schedule:
|
schedule:
|
||||||
# Run every day at 8:30 Beijing Time (00:30 UTC)
|
# Run every day at 8:30 Beijing Time (00:30 UTC)
|
||||||
- cron: '30 0 * * *'
|
- cron: "30 0 * * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -54,9 +54,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
if: steps.check_time.outputs.should_delay == 'false'
|
if: steps.check_time.outputs.should_delay == 'false'
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: 22
|
||||||
|
|
||||||
- name: Process issue with Claude
|
- name: Process issue with Claude
|
||||||
if: steps.check_time.outputs.should_delay == 'false'
|
if: steps.check_time.outputs.should_delay == 'false'
|
||||||
@ -121,9 +121,9 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: 22
|
||||||
|
|
||||||
- name: Process pending issues with Claude
|
- name: Process pending issues with Claude
|
||||||
uses: anthropics/claude-code-action@main
|
uses: anthropics/claude-code-action@main
|
||||||
|
|||||||
4
.github/workflows/issue-management.yml
vendored
4
.github/workflows/issue-management.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
contents: none
|
contents: none
|
||||||
steps:
|
steps:
|
||||||
- name: Close needs-more-info issues
|
- name: Close needs-more-info issues
|
||||||
uses: actions/stale@v9
|
uses: actions/stale@v10
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
only-labels: 'needs-more-info'
|
only-labels: 'needs-more-info'
|
||||||
@ -42,7 +42,7 @@ jobs:
|
|||||||
days-before-pr-close: -1
|
days-before-pr-close: -1
|
||||||
|
|
||||||
- name: Close inactive issues
|
- name: Close inactive issues
|
||||||
uses: actions/stale@v9
|
uses: actions/stale@v10
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-stale: ${{ env.daysBeforeStale }}
|
days-before-stale: ${{ env.daysBeforeStale }}
|
||||||
|
|||||||
10
.github/workflows/nightly-build.yml
vendored
10
.github/workflows/nightly-build.yml
vendored
@ -3,7 +3,7 @@ name: Nightly Build
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 17 * * *' # 1:00 BJ Time
|
- cron: "0 17 * * *" # 1:00 BJ Time
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@ -56,9 +56,9 @@ jobs:
|
|||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
|
|
||||||
- name: macos-latest dependencies fix
|
- name: macos-latest dependencies fix
|
||||||
if: matrix.os == 'macos-latest'
|
if: matrix.os == 'macos-latest'
|
||||||
@ -66,7 +66,7 @@ jobs:
|
|||||||
brew install python-setuptools
|
brew install python-setuptools
|
||||||
|
|
||||||
- name: Install corepack
|
- name: Install corepack
|
||||||
run: corepack enable && corepack prepare yarn@4.6.0 --activate
|
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
- name: Get yarn cache directory path
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
@ -208,7 +208,7 @@ jobs:
|
|||||||
echo "总计: $(find renamed-artifacts -type f | wc -l) 个文件"
|
echo "总计: $(find renamed-artifacts -type f | wc -l) 个文件"
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: cherry-studio-nightly-${{ steps.date.outputs.date }}-${{ matrix.os }}
|
name: cherry-studio-nightly-${{ steps.date.outputs.date }}-${{ matrix.os }}
|
||||||
path: renamed-artifacts/*
|
path: renamed-artifacts/*
|
||||||
|
|||||||
6
.github/workflows/pr-ci.yml
vendored
6
.github/workflows/pr-ci.yml
vendored
@ -24,12 +24,12 @@ jobs:
|
|||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
|
|
||||||
- name: Install corepack
|
- name: Install corepack
|
||||||
run: corepack enable && corepack prepare yarn@4.6.0 --activate
|
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
- name: Get yarn cache directory path
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
|
|||||||
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@ -4,9 +4,9 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
description: 'Release tag (e.g. v1.0.0)'
|
description: "Release tag (e.g. v1.0.0)"
|
||||||
required: true
|
required: true
|
||||||
default: 'v1.0.0'
|
default: "v1.0.0"
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- v*.*.*
|
- v*.*.*
|
||||||
@ -47,9 +47,9 @@ jobs:
|
|||||||
npm version "$VERSION" --no-git-tag-version --allow-same-version
|
npm version "$VERSION" --no-git-tag-version --allow-same-version
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 22
|
||||||
|
|
||||||
- name: macos-latest dependencies fix
|
- name: macos-latest dependencies fix
|
||||||
if: matrix.os == 'macos-latest'
|
if: matrix.os == 'macos-latest'
|
||||||
@ -57,7 +57,7 @@ jobs:
|
|||||||
brew install python-setuptools
|
brew install python-setuptools
|
||||||
|
|
||||||
- name: Install corepack
|
- name: Install corepack
|
||||||
run: corepack enable && corepack prepare yarn@4.6.0 --activate
|
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
- name: Get yarn cache directory path
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
@ -127,5 +127,5 @@ jobs:
|
|||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
makeLatest: false
|
makeLatest: false
|
||||||
tag: ${{ steps.get-tag.outputs.tag }}
|
tag: ${{ steps.get-tag.outputs.tag }}
|
||||||
artifacts: 'dist/*.exe,dist/*.zip,dist/*.dmg,dist/*.AppImage,dist/*.snap,dist/*.deb,dist/*.rpm,dist/*.tar.gz,dist/latest*.yml,dist/rc*.yml,dist/beta*.yml,dist/*.blockmap'
|
artifacts: "dist/*.exe,dist/*.zip,dist/*.dmg,dist/*.AppImage,dist/*.snap,dist/*.deb,dist/*.rpm,dist/*.tar.gz,dist/latest*.yml,dist/rc*.yml,dist/beta*.yml,dist/*.blockmap"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@ -18,13 +18,13 @@ yarn
|
|||||||
|
|
||||||
### Setup Node.js
|
### Setup Node.js
|
||||||
|
|
||||||
Download and install [Node.js v20.x.x](https://nodejs.org/en/download)
|
Download and install [Node.js v22.x.x](https://nodejs.org/en/download)
|
||||||
|
|
||||||
### Setup Yarn
|
### Setup Yarn
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
corepack enable
|
corepack enable
|
||||||
corepack prepare yarn@4.6.0 --activate
|
corepack prepare yarn@4.9.1 --activate
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install Dependencies
|
### Install Dependencies
|
||||||
|
|||||||
@ -21,6 +21,8 @@ files:
|
|||||||
- "**/*"
|
- "**/*"
|
||||||
- "!**/{.vscode,.yarn,.yarn-lock,.github,.cursorrules,.prettierrc}"
|
- "!**/{.vscode,.yarn,.yarn-lock,.github,.cursorrules,.prettierrc}"
|
||||||
- "!electron.vite.config.{js,ts,mjs,cjs}}"
|
- "!electron.vite.config.{js,ts,mjs,cjs}}"
|
||||||
|
- "!.*"
|
||||||
|
- "!components.json"
|
||||||
- "!**/{.eslintignore,.eslintrc.js,.eslintrc.json,.eslintcache,root.eslint.config.js,eslint.config.js,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,eslint.config.mjs,dev-app-update.yml,CHANGELOG.md,README.md,biome.jsonc}"
|
- "!**/{.eslintignore,.eslintrc.js,.eslintrc.json,.eslintcache,root.eslint.config.js,eslint.config.js,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,eslint.config.mjs,dev-app-update.yml,CHANGELOG.md,README.md,biome.jsonc}"
|
||||||
- "!**/{.env,.env.*,.npmrc,pnpm-lock.yaml}"
|
- "!**/{.env,.env.*,.npmrc,pnpm-lock.yaml}"
|
||||||
- "!**/{tsconfig.json,tsconfig.tsbuildinfo,tsconfig.node.json,tsconfig.web.json}"
|
- "!**/{tsconfig.json,tsconfig.tsbuildinfo,tsconfig.node.json,tsconfig.web.json}"
|
||||||
@ -162,6 +164,9 @@ releaseInfo:
|
|||||||
- MCP Confirmation: Added confirmation modal when activating protocol-installed MCP servers
|
- MCP Confirmation: Added confirmation modal when activating protocol-installed MCP servers
|
||||||
- Translation: Enhanced translation script with concurrency and validation
|
- Translation: Enhanced translation script with concurrency and validation
|
||||||
- Electron & Vite: Updated to Electron 38 and Vite 4.0.1
|
- Electron & Vite: Updated to Electron 38 and Vite 4.0.1
|
||||||
|
- QR Code Generation: Optimized performance for phone LAN export
|
||||||
|
- Enterprise Settings: Added enterprise section in About settings
|
||||||
|
- Assistant/Agent Popup: Enhanced UI for adding assistants and agents
|
||||||
|
|
||||||
Claude Code Tool Improvements:
|
Claude Code Tool Improvements:
|
||||||
- GlobTool: Now counts lines instead of files in output for better clarity
|
- GlobTool: Now counts lines instead of files in output for better clarity
|
||||||
@ -189,6 +194,9 @@ releaseInfo:
|
|||||||
- Fixed reranker API error response capture
|
- Fixed reranker API error response capture
|
||||||
- Fixed right-click paste file content into inputbar
|
- Fixed right-click paste file content into inputbar
|
||||||
- Fixed minimax-m2 support in aiCore
|
- Fixed minimax-m2 support in aiCore
|
||||||
|
- Fixed Azure embedding issues
|
||||||
|
- Fixed agent edit modal loading race condition
|
||||||
|
- Fixed debounced save cancellation on file path update
|
||||||
|
|
||||||
<!--LANG:zh-CN-->
|
<!--LANG:zh-CN-->
|
||||||
v1.7.0-beta.3 新特性
|
v1.7.0-beta.3 新特性
|
||||||
@ -219,6 +227,9 @@ releaseInfo:
|
|||||||
- MCP 确认:添加激活协议安装的 MCP 服务器时的确认模态框
|
- MCP 确认:添加激活协议安装的 MCP 服务器时的确认模态框
|
||||||
- 翻译:增强翻译脚本的并发和验证功能
|
- 翻译:增强翻译脚本的并发和验证功能
|
||||||
- Electron & Vite:更新至 Electron 38 和 Vite 4.0.1
|
- Electron & Vite:更新至 Electron 38 和 Vite 4.0.1
|
||||||
|
- 二维码生成:优化手机局域网导出性能
|
||||||
|
- 企业设置:在关于设置中添加企业部分
|
||||||
|
- 助手/Agent 弹窗:增强添加助手和 Agent 的界面
|
||||||
|
|
||||||
Claude Code 工具改进:
|
Claude Code 工具改进:
|
||||||
- GlobTool:现在计算行数而不是文件数,提供更清晰的输出
|
- GlobTool:现在计算行数而不是文件数,提供更清晰的输出
|
||||||
@ -246,4 +257,7 @@ releaseInfo:
|
|||||||
- 修复 reranker API 错误响应捕获
|
- 修复 reranker API 错误响应捕获
|
||||||
- 修复右键粘贴文件内容到输入栏
|
- 修复右键粘贴文件内容到输入栏
|
||||||
- 修复 aiCore 中的 minimax-m2 支持
|
- 修复 aiCore 中的 minimax-m2 支持
|
||||||
|
- 修复 Azure embedding 问题
|
||||||
|
- 修复 Agent 编辑模态框加载竞态条件
|
||||||
|
- 修复文件路径更新时防抖保存取消问题
|
||||||
<!--LANG:END-->
|
<!--LANG:END-->
|
||||||
|
|||||||
@ -85,6 +85,7 @@
|
|||||||
"@libsql/client": "0.14.0",
|
"@libsql/client": "0.14.0",
|
||||||
"@libsql/win32-x64-msvc": "^0.4.7",
|
"@libsql/win32-x64-msvc": "^0.4.7",
|
||||||
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
|
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
|
||||||
|
"@paymoapp/electron-shutdown-handler": "^1.1.2",
|
||||||
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"font-list": "^2.0.0",
|
"font-list": "^2.0.0",
|
||||||
@ -116,9 +117,9 @@
|
|||||||
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||||
"@anthropic-ai/sdk": "^0.41.0",
|
"@anthropic-ai/sdk": "^0.41.0",
|
||||||
"@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch",
|
"@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch",
|
||||||
"@aws-sdk/client-bedrock": "^3.840.0",
|
"@aws-sdk/client-bedrock": "^3.910.0",
|
||||||
"@aws-sdk/client-bedrock-runtime": "^3.840.0",
|
"@aws-sdk/client-bedrock-runtime": "^3.910.0",
|
||||||
"@aws-sdk/client-s3": "^3.840.0",
|
"@aws-sdk/client-s3": "^3.910.0",
|
||||||
"@biomejs/biome": "2.2.4",
|
"@biomejs/biome": "2.2.4",
|
||||||
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18",
|
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18",
|
||||||
"@cherrystudio/embedjs": "^0.1.31",
|
"@cherrystudio/embedjs": "^0.1.31",
|
||||||
@ -376,6 +377,7 @@
|
|||||||
"zod": "^4.1.5"
|
"zod": "^4.1.5"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
"@smithy/types": "4.7.1",
|
||||||
"@codemirror/language": "6.11.3",
|
"@codemirror/language": "6.11.3",
|
||||||
"@codemirror/lint": "6.8.5",
|
"@codemirror/lint": "6.8.5",
|
||||||
"@codemirror/view": "6.38.1",
|
"@codemirror/view": "6.38.1",
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import { apiServerService } from './services/ApiServerService'
|
|||||||
import { appMenuService } from './services/AppMenuService'
|
import { appMenuService } from './services/AppMenuService'
|
||||||
import mcpService from './services/MCPService'
|
import mcpService from './services/MCPService'
|
||||||
import { nodeTraceService } from './services/NodeTraceService'
|
import { nodeTraceService } from './services/NodeTraceService'
|
||||||
|
import powerMonitorService from './services/PowerMonitorService'
|
||||||
import {
|
import {
|
||||||
CHERRY_STUDIO_PROTOCOL,
|
CHERRY_STUDIO_PROTOCOL,
|
||||||
handleProtocolUrl,
|
handleProtocolUrl,
|
||||||
@ -31,6 +32,7 @@ import {
|
|||||||
import selectionService, { initSelectionService } from './services/SelectionService'
|
import selectionService, { initSelectionService } from './services/SelectionService'
|
||||||
import { registerShortcuts } from './services/ShortcutService'
|
import { registerShortcuts } from './services/ShortcutService'
|
||||||
import { TrayService } from './services/TrayService'
|
import { TrayService } from './services/TrayService'
|
||||||
|
import { versionService } from './services/VersionService'
|
||||||
import { windowService } from './services/WindowService'
|
import { windowService } from './services/WindowService'
|
||||||
import { dataRefactorMigrateService } from './data/migrate/dataRefactor/DataRefactorMigrateService'
|
import { dataRefactorMigrateService } from './data/migrate/dataRefactor/DataRefactorMigrateService'
|
||||||
import { dataApiService } from '@data/DataApiService'
|
import { dataApiService } from '@data/DataApiService'
|
||||||
@ -188,6 +190,10 @@ if (!app.requestSingleInstanceLock()) {
|
|||||||
|
|
||||||
/************FOR TESTING ONLY END****************/
|
/************FOR TESTING ONLY END****************/
|
||||||
|
|
||||||
|
// Record current version for tracking
|
||||||
|
// A preparation for v2 data refactoring
|
||||||
|
versionService.recordCurrentVersion()
|
||||||
|
|
||||||
initWebviewHotkeys()
|
initWebviewHotkeys()
|
||||||
// Set app user model id for windows
|
// Set app user model id for windows
|
||||||
electronApp.setAppUserModelId(import.meta.env.VITE_MAIN_BUNDLE_ID || 'com.kangfenmao.CherryStudio')
|
electronApp.setAppUserModelId(import.meta.env.VITE_MAIN_BUNDLE_ID || 'com.kangfenmao.CherryStudio')
|
||||||
@ -206,6 +212,7 @@ if (!app.requestSingleInstanceLock()) {
|
|||||||
// Setup macOS application menu
|
// Setup macOS application menu
|
||||||
appMenuService?.setupApplicationMenu()
|
appMenuService?.setupApplicationMenu()
|
||||||
nodeTraceService.init()
|
nodeTraceService.init()
|
||||||
|
powerMonitorService.init()
|
||||||
|
|
||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
const mainWindow = windowService.getMainWindow()
|
const mainWindow = windowService.getMainWindow()
|
||||||
|
|||||||
@ -51,6 +51,7 @@ import * as NutstoreService from './services/NutstoreService'
|
|||||||
import ObsidianVaultService from './services/ObsidianVaultService'
|
import ObsidianVaultService from './services/ObsidianVaultService'
|
||||||
import { ocrService } from './services/ocr/OcrService'
|
import { ocrService } from './services/ocr/OcrService'
|
||||||
import OvmsManager from './services/OvmsManager'
|
import OvmsManager from './services/OvmsManager'
|
||||||
|
import powerMonitorService from './services/PowerMonitorService'
|
||||||
import { proxyManager } from './services/ProxyManager'
|
import { proxyManager } from './services/ProxyManager'
|
||||||
import { pythonService } from './services/PythonService'
|
import { pythonService } from './services/PythonService'
|
||||||
import { FileServiceManager } from './services/remotefile/FileServiceManager'
|
import { FileServiceManager } from './services/remotefile/FileServiceManager'
|
||||||
@ -115,8 +116,17 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
const appUpdater = new AppUpdater()
|
const appUpdater = new AppUpdater()
|
||||||
const notificationService = new NotificationService()
|
const notificationService = new NotificationService()
|
||||||
|
|
||||||
// Initialize Python service with main window
|
// Register shutdown handlers
|
||||||
pythonService.setMainWindow(mainWindow)
|
powerMonitorService.registerShutdownHandler(() => {
|
||||||
|
appUpdater.setAutoUpdate(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
powerMonitorService.registerShutdownHandler(() => {
|
||||||
|
const mw = windowService.getMainWindow()
|
||||||
|
if (mw && !mw.isDestroyed()) {
|
||||||
|
mw.webContents.send(IpcChannel.App_SaveData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const checkMainWindow = () => {
|
const checkMainWindow = () => {
|
||||||
if (!mainWindow || mainWindow.isDestroyed()) {
|
if (!mainWindow || mainWindow.isDestroyed()) {
|
||||||
|
|||||||
112
src/main/services/PowerMonitorService.ts
Normal file
112
src/main/services/PowerMonitorService.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { isLinux, isMac, isWin } from '@main/constant'
|
||||||
|
import ElectronShutdownHandler from '@paymoapp/electron-shutdown-handler'
|
||||||
|
import { BrowserWindow } from 'electron'
|
||||||
|
import { powerMonitor } from 'electron'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('PowerMonitorService')
|
||||||
|
|
||||||
|
type ShutdownHandler = () => void | Promise<void>
|
||||||
|
|
||||||
|
export class PowerMonitorService {
|
||||||
|
private static instance: PowerMonitorService
|
||||||
|
private initialized = false
|
||||||
|
private shutdownHandlers: ShutdownHandler[] = []
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
// Private constructor to prevent direct instantiation
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): PowerMonitorService {
|
||||||
|
if (!PowerMonitorService.instance) {
|
||||||
|
PowerMonitorService.instance = new PowerMonitorService()
|
||||||
|
}
|
||||||
|
return PowerMonitorService.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a shutdown handler to be called when system shutdown is detected
|
||||||
|
* @param handler - The handler function to be called on shutdown
|
||||||
|
*/
|
||||||
|
public registerShutdownHandler(handler: ShutdownHandler): void {
|
||||||
|
this.shutdownHandlers.push(handler)
|
||||||
|
logger.info('Shutdown handler registered', { totalHandlers: this.shutdownHandlers.length })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize power monitor to listen for shutdown events
|
||||||
|
*/
|
||||||
|
public init(): void {
|
||||||
|
if (this.initialized) {
|
||||||
|
logger.warn('PowerMonitorService already initialized')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWin) {
|
||||||
|
this.initWindowsShutdownHandler()
|
||||||
|
} else if (isMac || isLinux) {
|
||||||
|
this.initElectronPowerMonitor()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initialized = true
|
||||||
|
logger.info('PowerMonitorService initialized', { platform: process.platform })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute all registered shutdown handlers
|
||||||
|
*/
|
||||||
|
private async executeShutdownHandlers(): Promise<void> {
|
||||||
|
logger.info('Executing shutdown handlers', { count: this.shutdownHandlers.length })
|
||||||
|
for (const handler of this.shutdownHandlers) {
|
||||||
|
try {
|
||||||
|
await handler()
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error executing shutdown handler', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize shutdown handler for Windows using @paymoapp/electron-shutdown-handler
|
||||||
|
*/
|
||||||
|
private initWindowsShutdownHandler(): void {
|
||||||
|
try {
|
||||||
|
const zeroMemoryWindow = new BrowserWindow({ show: false })
|
||||||
|
// Set the window handle for the shutdown handler
|
||||||
|
ElectronShutdownHandler.setWindowHandle(zeroMemoryWindow.getNativeWindowHandle())
|
||||||
|
|
||||||
|
// Listen for shutdown event
|
||||||
|
ElectronShutdownHandler.on('shutdown', async () => {
|
||||||
|
logger.info('System shutdown event detected (Windows)')
|
||||||
|
// Execute all registered shutdown handlers
|
||||||
|
await this.executeShutdownHandlers()
|
||||||
|
// Release the shutdown block to allow the system to shut down
|
||||||
|
ElectronShutdownHandler.releaseShutdown()
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info('Windows shutdown handler registered')
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to initialize Windows shutdown handler', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize power monitor for macOS and Linux using Electron's powerMonitor
|
||||||
|
*/
|
||||||
|
private initElectronPowerMonitor(): void {
|
||||||
|
try {
|
||||||
|
powerMonitor.on('shutdown', async () => {
|
||||||
|
logger.info('System shutdown event detected', { platform: process.platform })
|
||||||
|
// Execute all registered shutdown handlers
|
||||||
|
await this.executeShutdownHandlers()
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info('Electron powerMonitor shutdown listener registered')
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to initialize Electron powerMonitor', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default export as singleton instance
|
||||||
|
export default PowerMonitorService.getInstance()
|
||||||
@ -1,8 +1,9 @@
|
|||||||
import { randomUUID } from 'node:crypto'
|
import { randomUUID } from 'node:crypto'
|
||||||
|
|
||||||
import type { BrowserWindow } from 'electron'
|
|
||||||
import { ipcMain } from 'electron'
|
import { ipcMain } from 'electron'
|
||||||
|
|
||||||
|
import { windowService } from './WindowService'
|
||||||
|
|
||||||
interface PythonExecutionRequest {
|
interface PythonExecutionRequest {
|
||||||
id: string
|
id: string
|
||||||
script: string
|
script: string
|
||||||
@ -21,7 +22,6 @@ interface PythonExecutionResponse {
|
|||||||
*/
|
*/
|
||||||
export class PythonService {
|
export class PythonService {
|
||||||
private static instance: PythonService | null = null
|
private static instance: PythonService | null = null
|
||||||
private mainWindow: BrowserWindow | null = null
|
|
||||||
private pendingRequests = new Map<string, { resolve: (value: string) => void; reject: (error: Error) => void }>()
|
private pendingRequests = new Map<string, { resolve: (value: string) => void; reject: (error: Error) => void }>()
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
@ -51,10 +51,6 @@ export class PythonService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public setMainWindow(mainWindow: BrowserWindow) {
|
|
||||||
this.mainWindow = mainWindow
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute Python code by sending request to renderer PyodideService
|
* Execute Python code by sending request to renderer PyodideService
|
||||||
*/
|
*/
|
||||||
@ -63,8 +59,8 @@ export class PythonService {
|
|||||||
context: Record<string, any> = {},
|
context: Record<string, any> = {},
|
||||||
timeout: number = 60000
|
timeout: number = 60000
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (!this.mainWindow) {
|
if (!windowService.getMainWindow()) {
|
||||||
throw new Error('Main window not set in PythonService')
|
throw new Error('Main window not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -95,7 +91,7 @@ export class PythonService {
|
|||||||
|
|
||||||
// Send request to renderer
|
// Send request to renderer
|
||||||
const request: PythonExecutionRequest = { id: requestId, script, context, timeout }
|
const request: PythonExecutionRequest = { id: requestId, script, context, timeout }
|
||||||
this.mainWindow?.webContents.send('python-execution-request', request)
|
windowService.getMainWindow()?.webContents.send('python-execution-request', request)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
285
src/main/services/VersionService.ts
Normal file
285
src/main/services/VersionService.ts
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { app } from 'electron'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('VersionService')
|
||||||
|
|
||||||
|
type OS = 'win' | 'mac' | 'linux' | 'unknown'
|
||||||
|
type Environment = 'prod' | 'dev'
|
||||||
|
type Packaged = 'packaged' | 'unpackaged'
|
||||||
|
type Mode = 'install' | 'portable'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version record stored in version.log
|
||||||
|
*/
|
||||||
|
interface VersionRecord {
|
||||||
|
version: string
|
||||||
|
os: OS
|
||||||
|
environment: Environment
|
||||||
|
packaged: Packaged
|
||||||
|
mode: Mode
|
||||||
|
timestamp: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for tracking application version history
|
||||||
|
* Stores version information in userData/version.log for data migration and diagnostics
|
||||||
|
*/
|
||||||
|
class VersionService {
|
||||||
|
private readonly VERSION_LOG_FILE = 'version.log'
|
||||||
|
private versionLogPath: string | null = null
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Lazy initialization of path since app.getPath may not be available during construction
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the full path to version.log file
|
||||||
|
* @returns {string} Full path to version log file
|
||||||
|
*/
|
||||||
|
private getVersionLogPath(): string {
|
||||||
|
if (!this.versionLogPath) {
|
||||||
|
this.versionLogPath = path.join(app.getPath('userData'), this.VERSION_LOG_FILE)
|
||||||
|
}
|
||||||
|
return this.versionLogPath
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets current operating system identifier
|
||||||
|
* @returns {OS} OS identifier
|
||||||
|
*/
|
||||||
|
private getCurrentOS(): OS {
|
||||||
|
switch (process.platform) {
|
||||||
|
case 'win32':
|
||||||
|
return 'win'
|
||||||
|
case 'darwin':
|
||||||
|
return 'mac'
|
||||||
|
case 'linux':
|
||||||
|
return 'linux'
|
||||||
|
default:
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets current environment (production or development)
|
||||||
|
* @returns {Environment} Environment identifier
|
||||||
|
*/
|
||||||
|
private getCurrentEnvironment(): Environment {
|
||||||
|
return import.meta.env.MODE === 'production' ? 'prod' : 'dev'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets packaging status
|
||||||
|
* @returns {Packaged} Packaging status
|
||||||
|
*/
|
||||||
|
private getPackagedStatus(): Packaged {
|
||||||
|
return app.isPackaged ? 'packaged' : 'unpackaged'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets installation mode (install or portable)
|
||||||
|
* @returns {Mode} Installation mode
|
||||||
|
*/
|
||||||
|
private getInstallMode(): Mode {
|
||||||
|
return process.env.PORTABLE_EXECUTABLE_DIR !== undefined ? 'portable' : 'install'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates version log line for current application state
|
||||||
|
* @returns {string} Pipe-separated version record line
|
||||||
|
*/
|
||||||
|
private generateCurrentVersionLine(): string {
|
||||||
|
const version = app.getVersion()
|
||||||
|
const os = this.getCurrentOS()
|
||||||
|
const environment = this.getCurrentEnvironment()
|
||||||
|
const packaged = this.getPackagedStatus()
|
||||||
|
const mode = this.getInstallMode()
|
||||||
|
const timestamp = new Date().toISOString()
|
||||||
|
|
||||||
|
return `${version}|${os}|${environment}|${packaged}|${mode}|${timestamp}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a version log line into a VersionRecord object
|
||||||
|
* @param {string} line - Pipe-separated version record line
|
||||||
|
* @returns {VersionRecord | null} Parsed version record or null if invalid
|
||||||
|
*/
|
||||||
|
private parseVersionLine(line: string): VersionRecord | null {
|
||||||
|
try {
|
||||||
|
const parts = line.trim().split('|')
|
||||||
|
if (parts.length !== 6) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const [version, os, environment, packaged, mode, timestamp] = parts
|
||||||
|
|
||||||
|
// Validate data
|
||||||
|
if (
|
||||||
|
!version ||
|
||||||
|
!['win', 'mac', 'linux', 'unknown'].includes(os) ||
|
||||||
|
!['prod', 'dev'].includes(environment) ||
|
||||||
|
!['packaged', 'unpackaged'].includes(packaged) ||
|
||||||
|
!['install', 'portable'].includes(mode) ||
|
||||||
|
!timestamp
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
os: os as OS,
|
||||||
|
environment: environment as Environment,
|
||||||
|
packaged: packaged as Packaged,
|
||||||
|
mode: mode as Mode,
|
||||||
|
timestamp
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`Failed to parse version line: ${line}`, error as Error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the last 1KB from version.log and returns all lines
|
||||||
|
* Uses reverse reading from file end to avoid reading the entire file
|
||||||
|
* @returns {string[]} Array of version lines from the last 1KB
|
||||||
|
*/
|
||||||
|
private readLastVersionLines(): string[] {
|
||||||
|
const logPath = this.getVersionLogPath()
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(logPath)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = fs.statSync(logPath)
|
||||||
|
const fileSize = stats.size
|
||||||
|
|
||||||
|
if (fileSize === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read from the end of the file, 1KB is enough to find previous version
|
||||||
|
// Typical line: "1.7.0-beta.3|win|prod|packaged|install|2025-01-15T08:30:00.000Z\n" (~70 bytes)
|
||||||
|
// 1KB can store ~14 lines, which is more than enough
|
||||||
|
const bufferSize = Math.min(1024, fileSize)
|
||||||
|
const buffer = Buffer.alloc(bufferSize)
|
||||||
|
|
||||||
|
const fd = fs.openSync(logPath, 'r')
|
||||||
|
try {
|
||||||
|
const startPosition = Math.max(0, fileSize - bufferSize)
|
||||||
|
fs.readSync(fd, buffer, 0, bufferSize, startPosition)
|
||||||
|
|
||||||
|
const content = buffer.toString('utf-8')
|
||||||
|
const lines = content
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.filter((line) => line.trim())
|
||||||
|
|
||||||
|
return lines
|
||||||
|
} finally {
|
||||||
|
fs.closeSync(fd)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to read version log:', error as Error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a version record line to version.log
|
||||||
|
* @param {string} line - Version record line to append
|
||||||
|
*/
|
||||||
|
private appendVersionLine(line: string): void {
|
||||||
|
const logPath = this.getVersionLogPath()
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.appendFileSync(logPath, line + '\n', 'utf-8')
|
||||||
|
logger.debug(`Version recorded: ${line}`)
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to append version log:', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the current version on application startup
|
||||||
|
* Only adds a new record if the version has changed since the last run
|
||||||
|
*/
|
||||||
|
recordCurrentVersion(): void {
|
||||||
|
try {
|
||||||
|
const currentLine = this.generateCurrentVersionLine()
|
||||||
|
const lines = this.readLastVersionLines()
|
||||||
|
|
||||||
|
// Add new record if this is the first run or version has changed
|
||||||
|
if (lines.length === 0) {
|
||||||
|
logger.info('First run detected, creating version log')
|
||||||
|
this.appendVersionLine(currentLine)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastLine = lines[lines.length - 1]
|
||||||
|
const lastRecord = this.parseVersionLine(lastLine)
|
||||||
|
const currentVersion = app.getVersion()
|
||||||
|
|
||||||
|
// Check if any meaningful field has changed (version, os, environment, packaged, mode)
|
||||||
|
const currentOS = this.getCurrentOS()
|
||||||
|
const currentEnvironment = this.getCurrentEnvironment()
|
||||||
|
const currentPackaged = this.getPackagedStatus()
|
||||||
|
const currentMode = this.getInstallMode()
|
||||||
|
|
||||||
|
const hasMeaningfulChange =
|
||||||
|
!lastRecord ||
|
||||||
|
lastRecord.version !== currentVersion ||
|
||||||
|
lastRecord.os !== currentOS ||
|
||||||
|
lastRecord.environment !== currentEnvironment ||
|
||||||
|
lastRecord.packaged !== currentPackaged ||
|
||||||
|
lastRecord.mode !== currentMode
|
||||||
|
|
||||||
|
if (hasMeaningfulChange) {
|
||||||
|
logger.info(`Version information changed, recording new entry`)
|
||||||
|
this.appendVersionLine(currentLine)
|
||||||
|
} else {
|
||||||
|
logger.debug(`Version information not changed, skip recording`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to record current version:', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the previous version record (last record with different version than current)
|
||||||
|
* Reads from the last 1KB of version.log to find the most recent different version
|
||||||
|
* Useful for detecting version upgrades and running migrations
|
||||||
|
* @returns {VersionRecord | null} Previous version record or null if not available
|
||||||
|
*/
|
||||||
|
getPreviousVersion(): VersionRecord | null {
|
||||||
|
try {
|
||||||
|
const lines = this.readLastVersionLines()
|
||||||
|
if (lines.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentVersion = app.getVersion()
|
||||||
|
|
||||||
|
// Read from the end backwards to find the first different version
|
||||||
|
for (let i = lines.length - 1; i >= 0; i--) {
|
||||||
|
const record = this.parseVersionLine(lines[i])
|
||||||
|
if (record && record.version !== currentVersion) {
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Failed to get previous version:', error as Error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton instance of VersionService
|
||||||
|
*/
|
||||||
|
export const versionService = new VersionService()
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { BedrockClient, ListFoundationModelsCommand, ListInferenceProfilesCommand } from '@aws-sdk/client-bedrock'
|
import { BedrockClient, ListFoundationModelsCommand, ListInferenceProfilesCommand } from '@aws-sdk/client-bedrock'
|
||||||
import {
|
import {
|
||||||
BedrockRuntimeClient,
|
BedrockRuntimeClient,
|
||||||
|
type BedrockRuntimeClientConfig,
|
||||||
ConverseCommand,
|
ConverseCommand,
|
||||||
InvokeModelCommand,
|
InvokeModelCommand,
|
||||||
InvokeModelWithResponseStreamCommand
|
InvokeModelWithResponseStreamCommand
|
||||||
@ -11,6 +12,8 @@ import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant'
|
|||||||
import { findTokenLimit, isReasoningModel } from '@renderer/config/models'
|
import { findTokenLimit, isReasoningModel } from '@renderer/config/models'
|
||||||
import {
|
import {
|
||||||
getAwsBedrockAccessKeyId,
|
getAwsBedrockAccessKeyId,
|
||||||
|
getAwsBedrockApiKey,
|
||||||
|
getAwsBedrockAuthType,
|
||||||
getAwsBedrockRegion,
|
getAwsBedrockRegion,
|
||||||
getAwsBedrockSecretAccessKey
|
getAwsBedrockSecretAccessKey
|
||||||
} from '@renderer/hooks/useAwsBedrock'
|
} from '@renderer/hooks/useAwsBedrock'
|
||||||
@ -75,32 +78,48 @@ export class AwsBedrockAPIClient extends BaseApiClient<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const region = getAwsBedrockRegion()
|
const region = getAwsBedrockRegion()
|
||||||
const accessKeyId = getAwsBedrockAccessKeyId()
|
const authType = getAwsBedrockAuthType()
|
||||||
const secretAccessKey = getAwsBedrockSecretAccessKey()
|
|
||||||
|
|
||||||
if (!region) {
|
if (!region) {
|
||||||
throw new Error('AWS region is required. Please configure AWS-Region in extra headers.')
|
throw new Error('AWS region is required. Please configure AWS region in settings.')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!accessKeyId || !secretAccessKey) {
|
// Build client configuration based on auth type
|
||||||
throw new Error('AWS credentials are required. Please configure AWS-Access-Key-ID and AWS-Secret-Access-Key.')
|
let clientConfig: BedrockRuntimeClientConfig
|
||||||
|
|
||||||
|
if (authType === 'iam') {
|
||||||
|
// IAM credentials authentication
|
||||||
|
const accessKeyId = getAwsBedrockAccessKeyId()
|
||||||
|
const secretAccessKey = getAwsBedrockSecretAccessKey()
|
||||||
|
|
||||||
|
if (!accessKeyId || !secretAccessKey) {
|
||||||
|
throw new Error('AWS credentials are required. Please configure Access Key ID and Secret Access Key.')
|
||||||
|
}
|
||||||
|
|
||||||
|
clientConfig = {
|
||||||
|
region,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId,
|
||||||
|
secretAccessKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// API Key authentication
|
||||||
|
const awsBedrockApiKey = getAwsBedrockApiKey()
|
||||||
|
|
||||||
|
if (!awsBedrockApiKey) {
|
||||||
|
throw new Error('AWS Bedrock API Key is required. Please configure API Key in settings.')
|
||||||
|
}
|
||||||
|
|
||||||
|
clientConfig = {
|
||||||
|
region,
|
||||||
|
token: { token: awsBedrockApiKey },
|
||||||
|
authSchemePreference: ['httpBearerAuth']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = new BedrockRuntimeClient({
|
const client = new BedrockRuntimeClient(clientConfig)
|
||||||
region,
|
const bedrockClient = new BedrockClient(clientConfig)
|
||||||
credentials: {
|
|
||||||
accessKeyId,
|
|
||||||
secretAccessKey
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const bedrockClient = new BedrockClient({
|
|
||||||
region,
|
|
||||||
credentials: {
|
|
||||||
accessKeyId,
|
|
||||||
secretAccessKey
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.sdkInstance = { client, bedrockClient, region }
|
this.sdkInstance = { client, bedrockClient, region }
|
||||||
return this.sdkInstance
|
return this.sdkInstance
|
||||||
|
|||||||
@ -192,7 +192,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
|
|||||||
extra_body: {
|
extra_body: {
|
||||||
google: {
|
google: {
|
||||||
thinking_config: {
|
thinking_config: {
|
||||||
thinkingBudget: 0
|
thinking_budget: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -327,8 +327,8 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
|
|||||||
extra_body: {
|
extra_body: {
|
||||||
google: {
|
google: {
|
||||||
thinking_config: {
|
thinking_config: {
|
||||||
thinkingBudget: -1,
|
thinking_budget: -1,
|
||||||
includeThoughts: true
|
include_thoughts: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -338,8 +338,8 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
|
|||||||
extra_body: {
|
extra_body: {
|
||||||
google: {
|
google: {
|
||||||
thinking_config: {
|
thinking_config: {
|
||||||
thinkingBudget: budgetTokens,
|
thinking_budget: budgetTokens,
|
||||||
includeThoughts: true
|
include_thoughts: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -670,7 +670,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient<
|
|||||||
} else if (isClaudeReasoningModel(model) && reasoningEffort.thinking?.budget_tokens) {
|
} else if (isClaudeReasoningModel(model) && reasoningEffort.thinking?.budget_tokens) {
|
||||||
suffix = ` --thinking_budget ${reasoningEffort.thinking.budget_tokens}`
|
suffix = ` --thinking_budget ${reasoningEffort.thinking.budget_tokens}`
|
||||||
} else if (isGeminiReasoningModel(model) && reasoningEffort.extra_body?.google?.thinking_config) {
|
} else if (isGeminiReasoningModel(model) && reasoningEffort.extra_body?.google?.thinking_config) {
|
||||||
suffix = ` --thinking_budget ${reasoningEffort.extra_body.google.thinking_config.thinkingBudget}`
|
suffix = ` --thinking_budget ${reasoningEffort.extra_body.google.thinking_config.thinking_budget}`
|
||||||
}
|
}
|
||||||
// FIXME: poe 不支持多个text part,上传文本文件的时候用的不是file part而是text part,因此会出问题
|
// FIXME: poe 不支持多个text part,上传文本文件的时候用的不是file part而是text part,因此会出问题
|
||||||
// 临时解决方案是强制poe用string content,但是其实poe部分支持array
|
// 临时解决方案是强制poe用string content,但是其实poe部分支持array
|
||||||
|
|||||||
@ -85,6 +85,19 @@ export function supportsLargeFileUpload(model: Model): boolean {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查模型是否支持TopP
|
||||||
|
*/
|
||||||
|
export function supportsTopP(model: Model): boolean {
|
||||||
|
const provider = getProviderByModel(model)
|
||||||
|
|
||||||
|
if (provider?.type === 'anthropic' || model?.endpoint_type === 'anthropic') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取提供商特定的文件大小限制
|
* 获取提供商特定的文件大小限制
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import { setupToolsConfig } from '../utils/mcp'
|
|||||||
import { buildProviderOptions } from '../utils/options'
|
import { buildProviderOptions } from '../utils/options'
|
||||||
import { getAnthropicThinkingBudget } from '../utils/reasoning'
|
import { getAnthropicThinkingBudget } from '../utils/reasoning'
|
||||||
import { buildProviderBuiltinWebSearchConfig } from '../utils/websearch'
|
import { buildProviderBuiltinWebSearchConfig } from '../utils/websearch'
|
||||||
|
import { supportsTopP } from './modelCapabilities'
|
||||||
import { getTemperature, getTopP } from './modelParameters'
|
import { getTemperature, getTopP } from './modelParameters'
|
||||||
|
|
||||||
const logger = loggerService.withContext('parameterBuilder')
|
const logger = loggerService.withContext('parameterBuilder')
|
||||||
@ -176,20 +177,27 @@ export async function buildStreamTextParams(
|
|||||||
messages: sdkMessages,
|
messages: sdkMessages,
|
||||||
maxOutputTokens: maxTokens,
|
maxOutputTokens: maxTokens,
|
||||||
temperature: getTemperature(assistant, model),
|
temperature: getTemperature(assistant, model),
|
||||||
topP: getTopP(assistant, model),
|
|
||||||
abortSignal: options.requestOptions?.signal,
|
abortSignal: options.requestOptions?.signal,
|
||||||
headers: options.requestOptions?.headers,
|
headers: options.requestOptions?.headers,
|
||||||
providerOptions,
|
providerOptions,
|
||||||
stopWhen: stepCountIs(20),
|
stopWhen: stepCountIs(20),
|
||||||
maxRetries: 0
|
maxRetries: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (supportsTopP(model)) {
|
||||||
|
params.topP = getTopP(assistant, model)
|
||||||
|
}
|
||||||
|
|
||||||
if (tools) {
|
if (tools) {
|
||||||
params.tools = tools
|
params.tools = tools
|
||||||
}
|
}
|
||||||
|
|
||||||
if (assistant.prompt) {
|
if (assistant.prompt) {
|
||||||
params.system = await replacePromptVariables(assistant.prompt, model.name)
|
params.system = await replacePromptVariables(assistant.prompt, model.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug('params', params)
|
logger.debug('params', params)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
params,
|
params,
|
||||||
modelId: model.id,
|
modelId: model.id,
|
||||||
|
|||||||
@ -21,10 +21,44 @@ vi.mock('@renderer/store', () => ({
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@renderer/utils/api', () => ({
|
||||||
|
formatApiHost: vi.fn((host, isSupportedAPIVersion = true) => {
|
||||||
|
if (isSupportedAPIVersion === false) {
|
||||||
|
return host // Return host as-is when isSupportedAPIVersion is false
|
||||||
|
}
|
||||||
|
return `${host}/v1` // Default behavior when isSupportedAPIVersion is true
|
||||||
|
}),
|
||||||
|
routeToEndpoint: vi.fn((host) => ({
|
||||||
|
baseURL: host,
|
||||||
|
endpoint: '/chat/completions'
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@renderer/config/providers', async (importOriginal) => {
|
||||||
|
const actual = (await importOriginal()) as any
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
isCherryAIProvider: vi.fn(),
|
||||||
|
isAnthropicProvider: vi.fn(() => false),
|
||||||
|
isAzureOpenAIProvider: vi.fn(() => false),
|
||||||
|
isGeminiProvider: vi.fn(() => false),
|
||||||
|
isNewApiProvider: vi.fn(() => false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
vi.mock('@renderer/hooks/useVertexAI', () => ({
|
||||||
|
isVertexProvider: vi.fn(() => false),
|
||||||
|
isVertexAIConfigured: vi.fn(() => false),
|
||||||
|
createVertexProvider: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
import { isCherryAIProvider } from '@renderer/config/providers'
|
||||||
|
import { getProviderByModel } from '@renderer/services/AssistantService'
|
||||||
import type { Model, Provider } from '@renderer/types'
|
import type { Model, Provider } from '@renderer/types'
|
||||||
|
import { formatApiHost } from '@renderer/utils/api'
|
||||||
|
|
||||||
import { COPILOT_DEFAULT_HEADERS, COPILOT_EDITOR_VERSION, isCopilotResponsesModel } from '../constants'
|
import { COPILOT_DEFAULT_HEADERS, COPILOT_EDITOR_VERSION, isCopilotResponsesModel } from '../constants'
|
||||||
import { providerToAiSdkConfig } from '../providerConfig'
|
import { getActualProvider, providerToAiSdkConfig } from '../providerConfig'
|
||||||
|
|
||||||
const createWindowKeyv = () => {
|
const createWindowKeyv = () => {
|
||||||
const store = new Map<string, string>()
|
const store = new Map<string, string>()
|
||||||
@ -46,11 +80,21 @@ const createCopilotProvider = (): Provider => ({
|
|||||||
isSystem: true
|
isSystem: true
|
||||||
})
|
})
|
||||||
|
|
||||||
const createModel = (id: string, name = id): Model => ({
|
const createModel = (id: string, name = id, provider = 'copilot'): Model => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
provider: 'copilot',
|
provider,
|
||||||
group: 'copilot'
|
group: provider
|
||||||
|
})
|
||||||
|
|
||||||
|
const createCherryAIProvider = (): Provider => ({
|
||||||
|
id: 'cherryai',
|
||||||
|
type: 'openai',
|
||||||
|
name: 'CherryAI',
|
||||||
|
apiKey: 'test-key',
|
||||||
|
apiHost: 'https://api.cherryai.com',
|
||||||
|
models: [],
|
||||||
|
isSystem: false
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Copilot responses routing', () => {
|
describe('Copilot responses routing', () => {
|
||||||
@ -87,3 +131,67 @@ describe('Copilot responses routing', () => {
|
|||||||
expect(config.options.headers?.['Copilot-Integration-Id']).toBe(COPILOT_DEFAULT_HEADERS['Copilot-Integration-Id'])
|
expect(config.options.headers?.['Copilot-Integration-Id']).toBe(COPILOT_DEFAULT_HEADERS['Copilot-Integration-Id'])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('CherryAI provider configuration', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
;(globalThis as any).window = {
|
||||||
|
...(globalThis as any).window,
|
||||||
|
keyv: createWindowKeyv()
|
||||||
|
}
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('formats CherryAI provider apiHost with false parameter', () => {
|
||||||
|
const provider = createCherryAIProvider()
|
||||||
|
const model = createModel('gpt-4', 'GPT-4', 'cherryai')
|
||||||
|
|
||||||
|
// Mock the functions to simulate CherryAI provider detection
|
||||||
|
vi.mocked(isCherryAIProvider).mockReturnValue(true)
|
||||||
|
vi.mocked(getProviderByModel).mockReturnValue(provider)
|
||||||
|
|
||||||
|
// Call getActualProvider which should trigger formatProviderApiHost
|
||||||
|
const actualProvider = getActualProvider(model)
|
||||||
|
|
||||||
|
// Verify that formatApiHost was called with false as the second parameter
|
||||||
|
expect(formatApiHost).toHaveBeenCalledWith('https://api.cherryai.com', false)
|
||||||
|
expect(actualProvider.apiHost).toBe('https://api.cherryai.com')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not format non-CherryAI provider with false parameter', () => {
|
||||||
|
const provider = {
|
||||||
|
id: 'openai',
|
||||||
|
type: 'openai',
|
||||||
|
name: 'OpenAI',
|
||||||
|
apiKey: 'test-key',
|
||||||
|
apiHost: 'https://api.openai.com',
|
||||||
|
models: [],
|
||||||
|
isSystem: false
|
||||||
|
} as Provider
|
||||||
|
const model = createModel('gpt-4', 'GPT-4', 'openai')
|
||||||
|
|
||||||
|
// Mock the functions to simulate non-CherryAI provider
|
||||||
|
vi.mocked(isCherryAIProvider).mockReturnValue(false)
|
||||||
|
vi.mocked(getProviderByModel).mockReturnValue(provider)
|
||||||
|
|
||||||
|
// Call getActualProvider
|
||||||
|
const actualProvider = getActualProvider(model)
|
||||||
|
|
||||||
|
// Verify that formatApiHost was called with default parameters (true)
|
||||||
|
expect(formatApiHost).toHaveBeenCalledWith('https://api.openai.com')
|
||||||
|
expect(actualProvider.apiHost).toBe('https://api.openai.com/v1')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles CherryAI provider with empty apiHost', () => {
|
||||||
|
const provider = createCherryAIProvider()
|
||||||
|
provider.apiHost = ''
|
||||||
|
const model = createModel('gpt-4', 'GPT-4', 'cherryai')
|
||||||
|
|
||||||
|
vi.mocked(isCherryAIProvider).mockReturnValue(true)
|
||||||
|
vi.mocked(getProviderByModel).mockReturnValue(provider)
|
||||||
|
|
||||||
|
const actualProvider = getActualProvider(model)
|
||||||
|
|
||||||
|
expect(formatApiHost).toHaveBeenCalledWith('', false)
|
||||||
|
expect(actualProvider.apiHost).toBe('')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@ -52,7 +52,7 @@ const AIHUBMIX_RULES: RuleSet = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
fallbackRule: (provider: Provider) => provider
|
fallbackRule: (provider: Provider) => extraProviderConfig(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const aihubmixProviderCreator = provider2Provider.bind(null, AIHUBMIX_RULES)
|
export const aihubmixProviderCreator = provider2Provider.bind(null, AIHUBMIX_RULES)
|
||||||
|
|||||||
@ -10,11 +10,14 @@ import { isOpenAIChatCompletionOnlyModel } from '@renderer/config/models'
|
|||||||
import {
|
import {
|
||||||
isAnthropicProvider,
|
isAnthropicProvider,
|
||||||
isAzureOpenAIProvider,
|
isAzureOpenAIProvider,
|
||||||
|
isCherryAIProvider,
|
||||||
isGeminiProvider,
|
isGeminiProvider,
|
||||||
isNewApiProvider
|
isNewApiProvider
|
||||||
} from '@renderer/config/providers'
|
} from '@renderer/config/providers'
|
||||||
import {
|
import {
|
||||||
getAwsBedrockAccessKeyId,
|
getAwsBedrockAccessKeyId,
|
||||||
|
getAwsBedrockApiKey,
|
||||||
|
getAwsBedrockAuthType,
|
||||||
getAwsBedrockRegion,
|
getAwsBedrockRegion,
|
||||||
getAwsBedrockSecretAccessKey
|
getAwsBedrockSecretAccessKey
|
||||||
} from '@renderer/hooks/useAwsBedrock'
|
} from '@renderer/hooks/useAwsBedrock'
|
||||||
@ -99,6 +102,8 @@ function formatProviderApiHost(provider: Provider): Provider {
|
|||||||
formatted.apiHost = formatAzureOpenAIApiHost(formatted.apiHost)
|
formatted.apiHost = formatAzureOpenAIApiHost(formatted.apiHost)
|
||||||
} else if (isVertexProvider(formatted)) {
|
} else if (isVertexProvider(formatted)) {
|
||||||
formatted.apiHost = formatVertexApiHost(formatted)
|
formatted.apiHost = formatVertexApiHost(formatted)
|
||||||
|
} else if (isCherryAIProvider(formatted)) {
|
||||||
|
formatted.apiHost = formatApiHost(formatted.apiHost, false)
|
||||||
} else {
|
} else {
|
||||||
formatted.apiHost = formatApiHost(formatted.apiHost)
|
formatted.apiHost = formatApiHost(formatted.apiHost)
|
||||||
}
|
}
|
||||||
@ -193,9 +198,15 @@ export function providerToAiSdkConfig(
|
|||||||
|
|
||||||
// bedrock
|
// bedrock
|
||||||
if (aiSdkProviderId === 'bedrock') {
|
if (aiSdkProviderId === 'bedrock') {
|
||||||
|
const authType = getAwsBedrockAuthType()
|
||||||
extraOptions.region = getAwsBedrockRegion()
|
extraOptions.region = getAwsBedrockRegion()
|
||||||
extraOptions.accessKeyId = getAwsBedrockAccessKeyId()
|
|
||||||
extraOptions.secretAccessKey = getAwsBedrockSecretAccessKey()
|
if (authType === 'apiKey') {
|
||||||
|
extraOptions.apiKey = getAwsBedrockApiKey()
|
||||||
|
} else {
|
||||||
|
extraOptions.accessKeyId = getAwsBedrockAccessKeyId()
|
||||||
|
extraOptions.secretAccessKey = getAwsBedrockSecretAccessKey()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// google-vertex
|
// google-vertex
|
||||||
if (aiSdkProviderId === 'google-vertex' || aiSdkProviderId === 'google-vertex-anthropic') {
|
if (aiSdkProviderId === 'google-vertex' || aiSdkProviderId === 'google-vertex-anthropic') {
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { getAiSdkProviderId } from '../provider/factory'
|
|||||||
import { buildGeminiGenerateImageParams } from './image'
|
import { buildGeminiGenerateImageParams } from './image'
|
||||||
import {
|
import {
|
||||||
getAnthropicReasoningParams,
|
getAnthropicReasoningParams,
|
||||||
|
getBedrockReasoningParams,
|
||||||
getCustomParameters,
|
getCustomParameters,
|
||||||
getGeminiReasoningParams,
|
getGeminiReasoningParams,
|
||||||
getOpenAIReasoningParams,
|
getOpenAIReasoningParams,
|
||||||
@ -127,6 +128,9 @@ export function buildProviderOptions(
|
|||||||
case 'google-vertex-anthropic':
|
case 'google-vertex-anthropic':
|
||||||
providerSpecificOptions = buildAnthropicProviderOptions(assistant, model, capabilities)
|
providerSpecificOptions = buildAnthropicProviderOptions(assistant, model, capabilities)
|
||||||
break
|
break
|
||||||
|
case 'bedrock':
|
||||||
|
providerSpecificOptions = buildBedrockProviderOptions(assistant, model, capabilities)
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
// 对于其他 provider,使用通用的构建逻辑
|
// 对于其他 provider,使用通用的构建逻辑
|
||||||
providerSpecificOptions = {
|
providerSpecificOptions = {
|
||||||
@ -266,6 +270,32 @@ function buildXAIProviderOptions(
|
|||||||
return providerOptions
|
return providerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build Bedrock providerOptions
|
||||||
|
*/
|
||||||
|
function buildBedrockProviderOptions(
|
||||||
|
assistant: Assistant,
|
||||||
|
model: Model,
|
||||||
|
capabilities: {
|
||||||
|
enableReasoning: boolean
|
||||||
|
enableWebSearch: boolean
|
||||||
|
enableGenerateImage: boolean
|
||||||
|
}
|
||||||
|
): Record<string, any> {
|
||||||
|
const { enableReasoning } = capabilities
|
||||||
|
let providerOptions: Record<string, any> = {}
|
||||||
|
|
||||||
|
if (enableReasoning) {
|
||||||
|
const reasoningParams = getBedrockReasoningParams(assistant, model)
|
||||||
|
providerOptions = {
|
||||||
|
...providerOptions,
|
||||||
|
...reasoningParams
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return providerOptions
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建通用的 providerOptions(用于其他 provider)
|
* 构建通用的 providerOptions(用于其他 provider)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -98,7 +98,7 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
|
|||||||
extra_body: {
|
extra_body: {
|
||||||
google: {
|
google: {
|
||||||
thinking_config: {
|
thinking_config: {
|
||||||
thinkingBudget: 0
|
thinking_budget: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -259,8 +259,8 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
|
|||||||
extra_body: {
|
extra_body: {
|
||||||
google: {
|
google: {
|
||||||
thinking_config: {
|
thinking_config: {
|
||||||
thinkingBudget: -1,
|
thinking_budget: -1,
|
||||||
includeThoughts: true
|
include_thoughts: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,8 +270,8 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin
|
|||||||
extra_body: {
|
extra_body: {
|
||||||
google: {
|
google: {
|
||||||
thinking_config: {
|
thinking_config: {
|
||||||
thinkingBudget: budgetTokens,
|
thinking_budget: budgetTokens ?? -1,
|
||||||
includeThoughts: true
|
include_thoughts: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -431,8 +431,8 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re
|
|||||||
if (reasoningEffort === undefined) {
|
if (reasoningEffort === undefined) {
|
||||||
return {
|
return {
|
||||||
thinkingConfig: {
|
thinkingConfig: {
|
||||||
includeThoughts: false,
|
include_thoughts: false,
|
||||||
...(GEMINI_FLASH_MODEL_REGEX.test(model.id) ? { thinkingBudget: 0 } : {})
|
...(GEMINI_FLASH_MODEL_REGEX.test(model.id) ? { thinking_budget: 0 } : {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -442,7 +442,7 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re
|
|||||||
if (effortRatio > 1) {
|
if (effortRatio > 1) {
|
||||||
return {
|
return {
|
||||||
thinkingConfig: {
|
thinkingConfig: {
|
||||||
includeThoughts: true
|
include_thoughts: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -452,8 +452,8 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
thinkingConfig: {
|
thinkingConfig: {
|
||||||
...(budget > 0 ? { thinkingBudget: budget } : {}),
|
...(budget > 0 ? { thinking_budget: budget } : {}),
|
||||||
includeThoughts: true
|
include_thoughts: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -485,6 +485,34 @@ export function getXAIReasoningParams(assistant: Assistant, model: Model): Recor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Bedrock reasoning parameters
|
||||||
|
*/
|
||||||
|
export function getBedrockReasoningParams(assistant: Assistant, model: Model): Record<string, any> {
|
||||||
|
if (!isReasoningModel(model)) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reasoningEffort = assistant?.settings?.reasoning_effort
|
||||||
|
|
||||||
|
if (reasoningEffort === undefined) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only apply thinking budget for Claude reasoning models
|
||||||
|
if (!isSupportedThinkingTokenClaudeModel(model)) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const budgetTokens = getAnthropicThinkingBudget(assistant, model)
|
||||||
|
return {
|
||||||
|
reasoningConfig: {
|
||||||
|
type: 'enabled',
|
||||||
|
budgetTokens: budgetTokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取自定义参数
|
* 获取自定义参数
|
||||||
* 从 assistant 设置中提取自定义参数
|
* 从 assistant 设置中提取自定义参数
|
||||||
|
|||||||
13
src/renderer/src/assets/images/providers/sophnet.svg
Normal file
13
src/renderer/src/assets/images/providers/sophnet.svg
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="33" height="38" viewBox="0 0 33 38" fill="none">
|
||||||
|
<g clip-path="url(#clip0_4321_9943)">
|
||||||
|
<path d="M1.51221 6.59813C1.51221 4.09263 3.54331 2.06152 6.04881 2.06152H27.9757C30.4812 2.06152 32.5123 4.09263 32.5123 6.59813C32.5123 9.10362 30.4812 11.1347 27.9757 11.1347H6.04881C3.54331 11.1347 1.51221 9.10362 1.51221 6.59813Z" fill="#6200EE"/>
|
||||||
|
<path d="M3.38905 3.56467C5.26076 1.89906 8.12831 2.06615 9.79391 3.93785L22.1493 17.8221C23.8149 19.6938 23.6478 22.5614 21.7761 24.227C19.9044 25.8926 17.0369 25.7255 15.3713 23.8538L3.01586 9.96953C1.35026 8.09782 1.51734 5.23027 3.38905 3.56467Z" fill="#6200EE"/>
|
||||||
|
<path d="M1.51221 20.9643C1.51221 18.4588 3.54331 16.4277 6.04881 16.4277H18.9025C21.408 16.4277 23.4391 18.4588 23.4391 20.9643C23.4391 23.4698 21.408 25.5009 18.9025 25.5009H6.04881C3.54331 25.5009 1.51221 23.4698 1.51221 20.9643Z" fill="#6200EE"/>
|
||||||
|
<path d="M10.5854 32.3052C10.5854 34.8107 8.55431 36.8418 6.04881 36.8418C3.54331 36.8418 1.51221 34.8107 1.51221 32.3052C1.51221 29.7997 3.54331 27.7686 6.04881 27.7686C8.55431 27.7686 10.5854 29.7997 10.5854 32.3052Z" fill="#BF7AFF"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_4321_9943">
|
||||||
|
<rect width="32.5124" height="36.9029" fill="white" transform="translate(0 0.548828)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -64,6 +64,7 @@ export type QuickPanelListItem = {
|
|||||||
isSelected?: boolean
|
isSelected?: boolean
|
||||||
isMenu?: boolean
|
isMenu?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
hidden?: boolean
|
||||||
/**
|
/**
|
||||||
* 固定显示项:不参与过滤,始终出现在列表顶部。
|
* 固定显示项:不参与过滤,始终出现在列表顶部。
|
||||||
* 例如“清除”按钮可设置为 alwaysVisible,从而在有匹配项时始终可见;
|
* 例如“清除”按钮可设置为 alwaysVisible,从而在有匹配项时始终可见;
|
||||||
|
|||||||
@ -143,7 +143,8 @@ export const QuickPanelView: React.FC<Props> = ({ setInputText }) => {
|
|||||||
prevSymbolRef.current = ctx.symbol
|
prevSymbolRef.current = ctx.symbol
|
||||||
|
|
||||||
// 固定项置顶 + 过滤后的普通项
|
// 固定项置顶 + 过滤后的普通项
|
||||||
return [...pinnedItems, ...filteredNormalItems]
|
const pinnedFiltered = [...pinnedItems, ...filteredNormalItems]
|
||||||
|
return pinnedFiltered.filter((item) => !item.hidden)
|
||||||
}, [ctx.isVisible, ctx.symbol, ctx.list, searchText])
|
}, [ctx.isVisible, ctx.symbol, ctx.list, searchText])
|
||||||
|
|
||||||
const canForwardAndBackward = useMemo(() => {
|
const canForwardAndBackward = useMemo(() => {
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
|||||||
],
|
],
|
||||||
cherryin: [],
|
cherryin: [],
|
||||||
vertexai: [],
|
vertexai: [],
|
||||||
|
sophnet: [],
|
||||||
'302ai': [
|
'302ai': [
|
||||||
{
|
{
|
||||||
id: 'deepseek-chat',
|
id: 'deepseek-chat',
|
||||||
|
|||||||
@ -46,6 +46,7 @@ import Ph8ProviderLogo from '@renderer/assets/images/providers/ph8.png'
|
|||||||
import PPIOProviderLogo from '@renderer/assets/images/providers/ppio.png'
|
import PPIOProviderLogo from '@renderer/assets/images/providers/ppio.png'
|
||||||
import QiniuProviderLogo from '@renderer/assets/images/providers/qiniu.webp'
|
import QiniuProviderLogo from '@renderer/assets/images/providers/qiniu.webp'
|
||||||
import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png'
|
import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png'
|
||||||
|
import SophnetProviderLogo from '@renderer/assets/images/providers/sophnet.svg'
|
||||||
import StepProviderLogo from '@renderer/assets/images/providers/step.png'
|
import StepProviderLogo from '@renderer/assets/images/providers/step.png'
|
||||||
import TencentCloudProviderLogo from '@renderer/assets/images/providers/tencent-cloud-ti.png'
|
import TencentCloudProviderLogo from '@renderer/assets/images/providers/tencent-cloud-ti.png'
|
||||||
import TogetherProviderLogo from '@renderer/assets/images/providers/together.png'
|
import TogetherProviderLogo from '@renderer/assets/images/providers/together.png'
|
||||||
@ -246,6 +247,16 @@ export const SYSTEM_PROVIDERS_CONFIG: Record<SystemProviderId, SystemProvider> =
|
|||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
|
sophnet: {
|
||||||
|
id: 'sophnet',
|
||||||
|
name: 'SophNet',
|
||||||
|
type: 'openai',
|
||||||
|
apiKey: '',
|
||||||
|
apiHost: 'https://www.sophnet.com/api/open-apis/v1',
|
||||||
|
models: [],
|
||||||
|
isSystem: true,
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
ppio: {
|
ppio: {
|
||||||
id: 'ppio',
|
id: 'ppio',
|
||||||
name: 'PPIO',
|
name: 'PPIO',
|
||||||
@ -729,7 +740,8 @@ export const PROVIDER_LOGO_MAP: AtLeast<SystemProviderId, string> = {
|
|||||||
poe: 'poe', // use svg icon component
|
poe: 'poe', // use svg icon component
|
||||||
aionly: AiOnlyProviderLogo,
|
aionly: AiOnlyProviderLogo,
|
||||||
longcat: LongCatProviderLogo,
|
longcat: LongCatProviderLogo,
|
||||||
huggingface: HuggingfaceProviderLogo
|
huggingface: HuggingfaceProviderLogo,
|
||||||
|
sophnet: SophnetProviderLogo
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export function getProviderLogo(providerId: string) {
|
export function getProviderLogo(providerId: string) {
|
||||||
@ -808,6 +820,17 @@ export const PROVIDER_URLS: Record<SystemProviderId, ProviderUrls> = {
|
|||||||
models: 'https://ai.burncloud.com/pricing'
|
models: 'https://ai.burncloud.com/pricing'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
sophnet: {
|
||||||
|
api: {
|
||||||
|
url: 'https://www.sophnet.com/api/open-apis/v1'
|
||||||
|
},
|
||||||
|
websites: {
|
||||||
|
official: 'https://sophnet.com',
|
||||||
|
apiKey: 'https://sophnet.com/#/project/key',
|
||||||
|
docs: 'https://sophnet.com/docs/component/introduce.html',
|
||||||
|
models: 'https://sophnet.com/#/model/list'
|
||||||
|
}
|
||||||
|
},
|
||||||
ppio: {
|
ppio: {
|
||||||
api: {
|
api: {
|
||||||
url: 'https://api.ppinfra.com/v3/openai'
|
url: 'https://api.ppinfra.com/v3/openai'
|
||||||
@ -1463,6 +1486,10 @@ export const isNewApiProvider = (provider: Provider) => {
|
|||||||
return ['new-api', 'cherryin'].includes(provider.id) || provider.type === 'new-api'
|
return ['new-api', 'cherryin'].includes(provider.id) || provider.type === 'new-api'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isCherryAIProvider(provider: Provider): boolean {
|
||||||
|
return provider.id === 'cherryai'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断是否为 OpenAI 兼容的提供商
|
* 判断是否为 OpenAI 兼容的提供商
|
||||||
* @param {Provider} provider 提供商对象
|
* @param {Provider} provider 提供商对象
|
||||||
|
|||||||
@ -1,5 +1,12 @@
|
|||||||
import store, { useAppSelector } from '@renderer/store'
|
import store, { useAppSelector } from '@renderer/store'
|
||||||
import { setAwsBedrockAccessKeyId, setAwsBedrockRegion, setAwsBedrockSecretAccessKey } from '@renderer/store/llm'
|
import {
|
||||||
|
setAwsBedrockAccessKeyId,
|
||||||
|
setAwsBedrockApiKey,
|
||||||
|
setAwsBedrockAuthType,
|
||||||
|
setAwsBedrockRegion,
|
||||||
|
setAwsBedrockSecretAccessKey
|
||||||
|
} from '@renderer/store/llm'
|
||||||
|
import type { AwsBedrockAuthType } from '@renderer/types'
|
||||||
import { useDispatch } from 'react-redux'
|
import { useDispatch } from 'react-redux'
|
||||||
|
|
||||||
export function useAwsBedrockSettings() {
|
export function useAwsBedrockSettings() {
|
||||||
@ -8,8 +15,10 @@ export function useAwsBedrockSettings() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...settings,
|
...settings,
|
||||||
|
setAuthType: (authType: AwsBedrockAuthType) => dispatch(setAwsBedrockAuthType(authType)),
|
||||||
setAccessKeyId: (accessKeyId: string) => dispatch(setAwsBedrockAccessKeyId(accessKeyId)),
|
setAccessKeyId: (accessKeyId: string) => dispatch(setAwsBedrockAccessKeyId(accessKeyId)),
|
||||||
setSecretAccessKey: (secretAccessKey: string) => dispatch(setAwsBedrockSecretAccessKey(secretAccessKey)),
|
setSecretAccessKey: (secretAccessKey: string) => dispatch(setAwsBedrockSecretAccessKey(secretAccessKey)),
|
||||||
|
setApiKey: (apiKey: string) => dispatch(setAwsBedrockApiKey(apiKey)),
|
||||||
setRegion: (region: string) => dispatch(setAwsBedrockRegion(region))
|
setRegion: (region: string) => dispatch(setAwsBedrockRegion(region))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -18,6 +27,10 @@ export function getAwsBedrockSettings() {
|
|||||||
return store.getState().llm.settings.awsBedrock
|
return store.getState().llm.settings.awsBedrock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAwsBedrockAuthType() {
|
||||||
|
return store.getState().llm.settings.awsBedrock.authType
|
||||||
|
}
|
||||||
|
|
||||||
export function getAwsBedrockAccessKeyId() {
|
export function getAwsBedrockAccessKeyId() {
|
||||||
return store.getState().llm.settings.awsBedrock.accessKeyId
|
return store.getState().llm.settings.awsBedrock.accessKeyId
|
||||||
}
|
}
|
||||||
@ -26,6 +39,10 @@ export function getAwsBedrockSecretAccessKey() {
|
|||||||
return store.getState().llm.settings.awsBedrock.secretAccessKey
|
return store.getState().llm.settings.awsBedrock.secretAccessKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAwsBedrockApiKey() {
|
||||||
|
return store.getState().llm.settings.awsBedrock.apiKey
|
||||||
|
}
|
||||||
|
|
||||||
export function getAwsBedrockRegion() {
|
export function getAwsBedrockRegion() {
|
||||||
return store.getState().llm.settings.awsBedrock.region
|
return store.getState().llm.settings.awsBedrock.region
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,7 +85,8 @@ const providerKeyMap = {
|
|||||||
poe: 'provider.poe',
|
poe: 'provider.poe',
|
||||||
aionly: 'provider.aionly',
|
aionly: 'provider.aionly',
|
||||||
longcat: 'provider.longcat',
|
longcat: 'provider.longcat',
|
||||||
huggingface: 'provider.huggingface'
|
huggingface: 'provider.huggingface',
|
||||||
|
sophnet: 'provider.sophnet'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -238,7 +239,7 @@ const paintingsImageSizeOptionsKeyMap = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const getPaintingsImageSizeOptionsLabel = (key: string): string => {
|
export const getPaintingsImageSizeOptionsLabel = (key: string): string => {
|
||||||
return getLabel(paintingsImageSizeOptionsKeyMap, key)
|
return paintingsImageSizeOptionsKeyMap[key] ? getLabel(paintingsImageSizeOptionsKeyMap, key) : key
|
||||||
}
|
}
|
||||||
|
|
||||||
const paintingsQualityOptionsKeyMap = {
|
const paintingsQualityOptionsKeyMap = {
|
||||||
|
|||||||
@ -2482,6 +2482,7 @@
|
|||||||
"qiniu": "Qiniu AI",
|
"qiniu": "Qiniu AI",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "SiliconFlow",
|
"silicon": "SiliconFlow",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "StepFun",
|
"stepfun": "StepFun",
|
||||||
"tencent-cloud-ti": "Tencent Cloud TI",
|
"tencent-cloud-ti": "Tencent Cloud TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@ -4259,6 +4260,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "AWS Access Key ID",
|
"access_key_id": "AWS Access Key ID",
|
||||||
"access_key_id_help": "Your AWS Access Key ID for accessing AWS Bedrock services",
|
"access_key_id_help": "Your AWS Access Key ID for accessing AWS Bedrock services",
|
||||||
|
"api_key": "Bedrock API Key",
|
||||||
|
"api_key_help": "Your AWS Bedrock API Key for authentication",
|
||||||
|
"auth_type": "Authentication Type",
|
||||||
|
"auth_type_api_key": "Bedrock API Key",
|
||||||
|
"auth_type_help": "Choose between IAM credentials or Bedrock API Key authentication",
|
||||||
|
"auth_type_iam": "IAM Credentials",
|
||||||
"description": "AWS Bedrock is Amazon's fully managed foundation model service that supports various advanced large language models",
|
"description": "AWS Bedrock is Amazon's fully managed foundation model service that supports various advanced large language models",
|
||||||
"region": "AWS Region",
|
"region": "AWS Region",
|
||||||
"region_help": "Your AWS service region, e.g., us-east-1",
|
"region_help": "Your AWS service region, e.g., us-east-1",
|
||||||
|
|||||||
@ -2482,6 +2482,7 @@
|
|||||||
"qiniu": "七牛云 AI 推理",
|
"qiniu": "七牛云 AI 推理",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "硅基流动",
|
"silicon": "硅基流动",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "阶跃星辰",
|
"stepfun": "阶跃星辰",
|
||||||
"tencent-cloud-ti": "腾讯云 TI",
|
"tencent-cloud-ti": "腾讯云 TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@ -4259,6 +4260,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "AWS 访问密钥 ID",
|
"access_key_id": "AWS 访问密钥 ID",
|
||||||
"access_key_id_help": "您的 AWS 访问密钥 ID,用于访问 AWS Bedrock 服务",
|
"access_key_id_help": "您的 AWS 访问密钥 ID,用于访问 AWS Bedrock 服务",
|
||||||
|
"api_key": "Bedrock API 密钥",
|
||||||
|
"api_key_help": "您的 AWS Bedrock API 密钥,用于身份验证",
|
||||||
|
"auth_type": "认证方式",
|
||||||
|
"auth_type_api_key": "Bedrock API 密钥",
|
||||||
|
"auth_type_help": "选择使用 IAM 凭证或 Bedrock API 密钥进行身份验证",
|
||||||
|
"auth_type_iam": "IAM 凭证",
|
||||||
"description": "AWS Bedrock 是亚马逊提供的全托管基础模型服务,支持多种先进的大语言模型",
|
"description": "AWS Bedrock 是亚马逊提供的全托管基础模型服务,支持多种先进的大语言模型",
|
||||||
"region": "AWS 区域",
|
"region": "AWS 区域",
|
||||||
"region_help": "您的 AWS 服务区域,例如 us-east-1",
|
"region_help": "您的 AWS 服务区域,例如 us-east-1",
|
||||||
|
|||||||
@ -2482,6 +2482,7 @@
|
|||||||
"qiniu": "七牛雲 AI 推理",
|
"qiniu": "七牛雲 AI 推理",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "SiliconFlow",
|
"silicon": "SiliconFlow",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "StepFun",
|
"stepfun": "StepFun",
|
||||||
"tencent-cloud-ti": "騰訊雲 TI",
|
"tencent-cloud-ti": "騰訊雲 TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@ -4259,6 +4260,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "AWS 存取密鑰 ID",
|
"access_key_id": "AWS 存取密鑰 ID",
|
||||||
"access_key_id_help": "您的 AWS 存取密鑰 ID,用於存取 AWS Bedrock 服務",
|
"access_key_id_help": "您的 AWS 存取密鑰 ID,用於存取 AWS Bedrock 服務",
|
||||||
|
"api_key": "Bedrock API 金鑰",
|
||||||
|
"api_key_help": "您的 AWS Bedrock API 金鑰,用於身份驗證",
|
||||||
|
"auth_type": "認證方式",
|
||||||
|
"auth_type_api_key": "Bedrock API 金鑰",
|
||||||
|
"auth_type_help": "選擇使用 IAM 憑證或 Bedrock API 金鑰進行身份驗證",
|
||||||
|
"auth_type_iam": "IAM 憑證",
|
||||||
"description": "AWS Bedrock 是亞馬遜提供的全托管基础模型服務,支持多種先進的大語言模型",
|
"description": "AWS Bedrock 是亞馬遜提供的全托管基础模型服務,支持多種先進的大語言模型",
|
||||||
"region": "AWS 區域",
|
"region": "AWS 區域",
|
||||||
"region_help": "您的 AWS 服務區域,例如 us-east-1",
|
"region_help": "您的 AWS 服務區域,例如 us-east-1",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
"description": "[to be translated]:Handle complex tasks with various tools",
|
"description": "Bewältigen Sie komplexe Aufgaben mit verschiedenen Werkzeugen",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "Agent hinzufügen fehlgeschlagen",
|
"failed": "Agent hinzufügen fehlgeschlagen",
|
||||||
"invalid_agent": "Ungültiger Agent"
|
"invalid_agent": "Ungültiger Agent"
|
||||||
@ -548,11 +548,11 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
"description": "[to be translated]:Daily conversations and quick Q&A",
|
"description": "Tägliche Gespräche und schnelle Fragen & Antworten",
|
||||||
"title": "Assistent hinzufügen"
|
"title": "Assistent hinzufügen"
|
||||||
},
|
},
|
||||||
"option": {
|
"option": {
|
||||||
"title": "[to be translated]:Select Type"
|
"title": "Typ auswählen"
|
||||||
},
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "Neues Thema erstellen"
|
"title": "Neues Thema erstellen"
|
||||||
@ -2476,12 +2476,13 @@
|
|||||||
"openrouter": "OpenRouter",
|
"openrouter": "OpenRouter",
|
||||||
"ovms": "Intel OVMS",
|
"ovms": "Intel OVMS",
|
||||||
"perplexity": "Perplexity",
|
"perplexity": "Perplexity",
|
||||||
"ph8": "PH8 Großmodell-Plattform",
|
"ph8": "PH8",
|
||||||
"poe": "Poe",
|
"poe": "Poe",
|
||||||
"ppio": "PPIO Cloud",
|
"ppio": "PPIO Cloud",
|
||||||
"qiniu": "Qiniu Cloud KI-Inferenz",
|
"qiniu": "Qiniu Cloud KI-Inferenz",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "SiliconFlow",
|
"silicon": "SiliconFlow",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "StepFun",
|
"stepfun": "StepFun",
|
||||||
"tencent-cloud-ti": "Tencent Cloud TI",
|
"tencent-cloud-ti": "Tencent Cloud TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@ -4259,6 +4260,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "AWS-Zugriffsschlüssel-ID",
|
"access_key_id": "AWS-Zugriffsschlüssel-ID",
|
||||||
"access_key_id_help": "Ihre AWS-Zugriffsschlüssel-ID, um auf AWS Bedrock-Dienste zuzugreifen",
|
"access_key_id_help": "Ihre AWS-Zugriffsschlüssel-ID, um auf AWS Bedrock-Dienste zuzugreifen",
|
||||||
|
"api_key": "Bedrock-API-Schlüssel",
|
||||||
|
"api_key_help": "Ihr AWS Bedrock-API-Schlüssel für die Authentifizierung",
|
||||||
|
"auth_type": "Authentifizierungstyp",
|
||||||
|
"auth_type_api_key": "Bedrock-API-Schlüssel",
|
||||||
|
"auth_type_help": "Wählen Sie zwischen IAM-Anmeldeinformationen oder Bedrock-API-Schlüssel-Authentifizierung",
|
||||||
|
"auth_type_iam": "IAM-Anmeldeinformationen",
|
||||||
"description": "AWS Bedrock ist ein vollständig verwalteter Basismodell-Dienst von Amazon, der eine Vielzahl moderner großer Sprachmodelle unterstützt",
|
"description": "AWS Bedrock ist ein vollständig verwalteter Basismodell-Dienst von Amazon, der eine Vielzahl moderner großer Sprachmodelle unterstützt",
|
||||||
"region": "AWS-Region",
|
"region": "AWS-Region",
|
||||||
"region_help": "Ihre AWS-Serviceregion, z.B. us-east-1",
|
"region_help": "Ihre AWS-Serviceregion, z.B. us-east-1",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
"description": "[to be translated]:Handle complex tasks with various tools",
|
"description": "Χειριστείτε πολύπλοκες εργασίες με διάφορα εργαλεία",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "Αποτυχία προσθήκης πράκτορα",
|
"failed": "Αποτυχία προσθήκης πράκτορα",
|
||||||
"invalid_agent": "Μη έγκυρος Agent"
|
"invalid_agent": "Μη έγκυρος Agent"
|
||||||
@ -548,11 +548,11 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
"description": "[to be translated]:Daily conversations and quick Q&A",
|
"description": "Καθημερινές συνομιλίες και γρήγορες ερωταπαντήσεις",
|
||||||
"title": "Προσθήκη βοηθού"
|
"title": "Προσθήκη βοηθού"
|
||||||
},
|
},
|
||||||
"option": {
|
"option": {
|
||||||
"title": "[to be translated]:Select Type"
|
"title": "Επιλέξτε Τύπο"
|
||||||
},
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "Δημιουργία νέου θέματος"
|
"title": "Δημιουργία νέου θέματος"
|
||||||
@ -2476,12 +2476,13 @@
|
|||||||
"openrouter": "OpenRouter",
|
"openrouter": "OpenRouter",
|
||||||
"ovms": "Intel OVMS",
|
"ovms": "Intel OVMS",
|
||||||
"perplexity": "Perplexity",
|
"perplexity": "Perplexity",
|
||||||
"ph8": "Πλατφόρμα Ανοιχτής Μεγάλης Μοντέλου PH8",
|
"ph8": "PH8",
|
||||||
"poe": "Poe",
|
"poe": "Poe",
|
||||||
"ppio": "PPIO Piao Yun",
|
"ppio": "PPIO Piao Yun",
|
||||||
"qiniu": "Qiniu AI",
|
"qiniu": "Qiniu AI",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "Σιδηρική Παρουσία",
|
"silicon": "Σιδηρική Παρουσία",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "Βήμα Ουράς",
|
"stepfun": "Βήμα Ουράς",
|
||||||
"tencent-cloud-ti": "Tencent Cloud TI",
|
"tencent-cloud-ti": "Tencent Cloud TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@ -4259,6 +4260,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "Αναγνωριστικό κλειδιού πρόσβασης AWS",
|
"access_key_id": "Αναγνωριστικό κλειδιού πρόσβασης AWS",
|
||||||
"access_key_id_help": "Το ID του κλειδιού πρόσβασης AWS που χρησιμοποιείται για την πρόσβαση στην υπηρεσία AWS Bedrock",
|
"access_key_id_help": "Το ID του κλειδιού πρόσβασης AWS που χρησιμοποιείται για την πρόσβαση στην υπηρεσία AWS Bedrock",
|
||||||
|
"api_key": "Κλειδί API Bedrock",
|
||||||
|
"api_key_help": "Το κλειδί API του AWS Bedrock για έλεγχο ταυτότητας",
|
||||||
|
"auth_type": "Τύπος Πιστοποίησης",
|
||||||
|
"auth_type_api_key": "Κλειδί API Bedrock",
|
||||||
|
"auth_type_help": "Επιλέξτε μεταξύ πιστοποιητικών IAM ή πιστοποίησης με κλειδί API Bedrock",
|
||||||
|
"auth_type_iam": "Διαπιστευτήρια IAM",
|
||||||
"description": "Η AWS Bedrock είναι μια πλήρως διαχειριζόμενη υπηρεσία βασικών μοντέλων που παρέχεται από την Amazon και υποστηρίζει διάφορα προηγμένα μεγάλα γλωσσικά μοντέλα.",
|
"description": "Η AWS Bedrock είναι μια πλήρως διαχειριζόμενη υπηρεσία βασικών μοντέλων που παρέχεται από την Amazon και υποστηρίζει διάφορα προηγμένα μεγάλα γλωσσικά μοντέλα.",
|
||||||
"region": "Περιοχές AWS",
|
"region": "Περιοχές AWS",
|
||||||
"region_help": "Η περιοχή υπηρεσίας AWS σας, για παράδειγμα us-east-1",
|
"region_help": "Η περιοχή υπηρεσίας AWS σας, για παράδειγμα us-east-1",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
"description": "[to be translated]:Handle complex tasks with various tools",
|
"description": "Maneja tareas complejas con varias herramientas",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "Error al añadir agente",
|
"failed": "Error al añadir agente",
|
||||||
"invalid_agent": "Agent inválido"
|
"invalid_agent": "Agent inválido"
|
||||||
@ -548,11 +548,11 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
"description": "[to be translated]:Daily conversations and quick Q&A",
|
"description": "Conversaciones diarias y preguntas y respuestas rápidas",
|
||||||
"title": "Agregar asistente"
|
"title": "Agregar asistente"
|
||||||
},
|
},
|
||||||
"option": {
|
"option": {
|
||||||
"title": "[to be translated]:Select Type"
|
"title": "Seleccionar Tipo"
|
||||||
},
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "Crear nuevo tema"
|
"title": "Crear nuevo tema"
|
||||||
@ -2476,12 +2476,13 @@
|
|||||||
"openrouter": "OpenRouter",
|
"openrouter": "OpenRouter",
|
||||||
"ovms": "Intel OVMS",
|
"ovms": "Intel OVMS",
|
||||||
"perplexity": "Perplejidad",
|
"perplexity": "Perplejidad",
|
||||||
"ph8": "Plataforma Abierta de Grandes Modelos PH8",
|
"ph8": "PH8",
|
||||||
"poe": "Poe",
|
"poe": "Poe",
|
||||||
"ppio": "PPIO Cloud Piao",
|
"ppio": "PPIO Cloud Piao",
|
||||||
"qiniu": "Qiniu AI",
|
"qiniu": "Qiniu AI",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "Silicio Fluido",
|
"silicon": "Silicio Fluido",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "Función Salto",
|
"stepfun": "Función Salto",
|
||||||
"tencent-cloud-ti": "Tencent Nube TI",
|
"tencent-cloud-ti": "Tencent Nube TI",
|
||||||
"together": "Juntos",
|
"together": "Juntos",
|
||||||
@ -4259,6 +4260,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "ID de clave de acceso de AWS",
|
"access_key_id": "ID de clave de acceso de AWS",
|
||||||
"access_key_id_help": "Su ID de clave de acceso de AWS, utilizado para acceder al servicio AWS Bedrock",
|
"access_key_id_help": "Su ID de clave de acceso de AWS, utilizado para acceder al servicio AWS Bedrock",
|
||||||
|
"api_key": "Clave de API de Bedrock",
|
||||||
|
"api_key_help": "Tu clave de API de AWS Bedrock para autenticación",
|
||||||
|
"auth_type": "Tipo de autenticación",
|
||||||
|
"auth_type_api_key": "Clave de API de Bedrock",
|
||||||
|
"auth_type_help": "Elige entre credenciales IAM o autenticación con clave API de Bedrock",
|
||||||
|
"auth_type_iam": "Credenciales de IAM",
|
||||||
"description": "AWS Bedrock es un servicio de modelos fundamentales completamente gestionado proporcionado por Amazon, que admite diversos modelos avanzados de lenguaje de gran tamaño.",
|
"description": "AWS Bedrock es un servicio de modelos fundamentales completamente gestionado proporcionado por Amazon, que admite diversos modelos avanzados de lenguaje de gran tamaño.",
|
||||||
"region": "Región de AWS",
|
"region": "Región de AWS",
|
||||||
"region_help": "Su región de servicio AWS, por ejemplo us-east-1",
|
"region_help": "Su región de servicio AWS, por ejemplo us-east-1",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
"description": "[to be translated]:Handle complex tasks with various tools",
|
"description": "Gérez des tâches complexes avec divers outils",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "Échec de l'ajout de l'agent",
|
"failed": "Échec de l'ajout de l'agent",
|
||||||
"invalid_agent": "Agent invalide"
|
"invalid_agent": "Agent invalide"
|
||||||
@ -548,11 +548,11 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
"description": "[to be translated]:Daily conversations and quick Q&A",
|
"description": "Conversations quotidiennes et Q&R rapides",
|
||||||
"title": "Ajouter un assistant"
|
"title": "Ajouter un assistant"
|
||||||
},
|
},
|
||||||
"option": {
|
"option": {
|
||||||
"title": "[to be translated]:Select Type"
|
"title": "Sélectionner le type"
|
||||||
},
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "Nouveau sujet"
|
"title": "Nouveau sujet"
|
||||||
@ -2476,12 +2476,13 @@
|
|||||||
"openrouter": "OpenRouter",
|
"openrouter": "OpenRouter",
|
||||||
"ovms": "Intel OVMS",
|
"ovms": "Intel OVMS",
|
||||||
"perplexity": "Perplexité",
|
"perplexity": "Perplexité",
|
||||||
"ph8": "Plateforme ouverte de grands modèles PH8",
|
"ph8": "PH8",
|
||||||
"poe": "Poe",
|
"poe": "Poe",
|
||||||
"ppio": "PPIO Cloud Piou",
|
"ppio": "PPIO Cloud Piou",
|
||||||
"qiniu": "Qiniu AI",
|
"qiniu": "Qiniu AI",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "Silicium Fluide",
|
"silicon": "Silicium Fluide",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "Échelon Étoile",
|
"stepfun": "Échelon Étoile",
|
||||||
"tencent-cloud-ti": "Tencent Cloud TI",
|
"tencent-cloud-ti": "Tencent Cloud TI",
|
||||||
"together": "Ensemble",
|
"together": "Ensemble",
|
||||||
@ -4259,6 +4260,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "Identifiant de clé d'accès AWS",
|
"access_key_id": "Identifiant de clé d'accès AWS",
|
||||||
"access_key_id_help": "Votre identifiant de clé d'accès AWS, utilisé pour accéder au service AWS Bedrock",
|
"access_key_id_help": "Votre identifiant de clé d'accès AWS, utilisé pour accéder au service AWS Bedrock",
|
||||||
|
"api_key": "Clé API Bedrock",
|
||||||
|
"api_key_help": "Votre clé API AWS Bedrock pour l'authentification",
|
||||||
|
"auth_type": "Type d'authentification",
|
||||||
|
"auth_type_api_key": "Clé API Bedrock",
|
||||||
|
"auth_type_help": "Choisissez entre l'authentification par identifiants IAM ou par clé API Bedrock",
|
||||||
|
"auth_type_iam": "Identifiants IAM",
|
||||||
"description": "AWS Bedrock est un service de modèles de base entièrement géré proposé par Amazon, prenant en charge divers grands modèles linguistiques avancés.",
|
"description": "AWS Bedrock est un service de modèles de base entièrement géré proposé par Amazon, prenant en charge divers grands modèles linguistiques avancés.",
|
||||||
"region": "Région AWS",
|
"region": "Région AWS",
|
||||||
"region_help": "Votre région de service AWS, par exemple us-east-1",
|
"region_help": "Votre région de service AWS, par exemple us-east-1",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
"description": "[to be translated]:Handle complex tasks with various tools",
|
"description": "さまざまなツールを使って複雑なタスクを処理する",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "エージェントの追加に失敗しました",
|
"failed": "エージェントの追加に失敗しました",
|
||||||
"invalid_agent": "無効なエージェント"
|
"invalid_agent": "無効なエージェント"
|
||||||
@ -548,11 +548,11 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
"description": "[to be translated]:Daily conversations and quick Q&A",
|
"description": "日常会話と簡単なQ&A",
|
||||||
"title": "アシスタントを追加"
|
"title": "アシスタントを追加"
|
||||||
},
|
},
|
||||||
"option": {
|
"option": {
|
||||||
"title": "[to be translated]:Select Type"
|
"title": "種類を選択"
|
||||||
},
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "新しいトピック"
|
"title": "新しいトピック"
|
||||||
@ -2482,6 +2482,7 @@
|
|||||||
"qiniu": "七牛云 AI 推理",
|
"qiniu": "七牛云 AI 推理",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "SiliconFlow",
|
"silicon": "SiliconFlow",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "StepFun",
|
"stepfun": "StepFun",
|
||||||
"tencent-cloud-ti": "Tencent Cloud TI",
|
"tencent-cloud-ti": "Tencent Cloud TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@ -4259,6 +4260,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "AWS アクセスキー ID",
|
"access_key_id": "AWS アクセスキー ID",
|
||||||
"access_key_id_help": "あなたの AWS アクセスキー ID は、AWS Bedrock サービスへのアクセスに使用されます",
|
"access_key_id_help": "あなたの AWS アクセスキー ID は、AWS Bedrock サービスへのアクセスに使用されます",
|
||||||
|
"api_key": "Bedrock APIキー",
|
||||||
|
"api_key_help": "認証用のAWS Bedrock APIキー",
|
||||||
|
"auth_type": "認証タイプ",
|
||||||
|
"auth_type_api_key": "Bedrock APIキー",
|
||||||
|
"auth_type_help": "IAM認証情報とBedrock APIキー認証のどちらかを選択してください",
|
||||||
|
"auth_type_iam": "IAM認証情報",
|
||||||
"description": "AWS Bedrock は、Amazon が提供する完全に管理されたベースモデルサービスで、さまざまな最先端の大言語モデルをサポートしています",
|
"description": "AWS Bedrock は、Amazon が提供する完全に管理されたベースモデルサービスで、さまざまな最先端の大言語モデルをサポートしています",
|
||||||
"region": "AWS リージョン",
|
"region": "AWS リージョン",
|
||||||
"region_help": "あなたの AWS サービスリージョン、例:us-east-1",
|
"region_help": "あなたの AWS サービスリージョン、例:us-east-1",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
"description": "[to be translated]:Handle complex tasks with various tools",
|
"description": "Lide com tarefas complexas usando várias ferramentas",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "Falha ao adicionar agente",
|
"failed": "Falha ao adicionar agente",
|
||||||
"invalid_agent": "Agent inválido"
|
"invalid_agent": "Agent inválido"
|
||||||
@ -548,11 +548,11 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
"description": "[to be translated]:Daily conversations and quick Q&A",
|
"description": "Conversas diárias e perguntas e respostas rápidas",
|
||||||
"title": "Adicionar assistente"
|
"title": "Adicionar assistente"
|
||||||
},
|
},
|
||||||
"option": {
|
"option": {
|
||||||
"title": "[to be translated]:Select Type"
|
"title": "Selecionar Tipo"
|
||||||
},
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "Novo Tópico"
|
"title": "Novo Tópico"
|
||||||
@ -2476,12 +2476,13 @@
|
|||||||
"openrouter": "OpenRouter",
|
"openrouter": "OpenRouter",
|
||||||
"ovms": "Intel OVMS",
|
"ovms": "Intel OVMS",
|
||||||
"perplexity": "Perplexidade",
|
"perplexity": "Perplexidade",
|
||||||
"ph8": "Plataforma Aberta de Grandes Modelos PH8",
|
"ph8": "PH8",
|
||||||
"poe": "Poe",
|
"poe": "Poe",
|
||||||
"ppio": "PPIO Nuvem Piao",
|
"ppio": "PPIO Nuvem Piao",
|
||||||
"qiniu": "Qiniu AI",
|
"qiniu": "Qiniu AI",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "Silício em Fluxo",
|
"silicon": "Silício em Fluxo",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "Função de Passo Estelar",
|
"stepfun": "Função de Passo Estelar",
|
||||||
"tencent-cloud-ti": "Nuvem TI da Tencent",
|
"tencent-cloud-ti": "Nuvem TI da Tencent",
|
||||||
"together": "Juntos",
|
"together": "Juntos",
|
||||||
@ -4259,6 +4260,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "ID da chave de acesso da AWS",
|
"access_key_id": "ID da chave de acesso da AWS",
|
||||||
"access_key_id_help": "O seu ID da chave de acesso AWS, utilizado para aceder ao serviço AWS Bedrock",
|
"access_key_id_help": "O seu ID da chave de acesso AWS, utilizado para aceder ao serviço AWS Bedrock",
|
||||||
|
"api_key": "Chave de API do Bedrock",
|
||||||
|
"api_key_help": "Sua Chave de API AWS Bedrock para autenticação",
|
||||||
|
"auth_type": "Tipo de Autenticação",
|
||||||
|
"auth_type_api_key": "Chave de API do Bedrock",
|
||||||
|
"auth_type_help": "Escolha entre credenciais IAM ou autenticação por chave de API do Bedrock",
|
||||||
|
"auth_type_iam": "Credenciais IAM",
|
||||||
"description": "A AWS Bedrock é um serviço de modelos fundamentais totalmente gerido fornecido pela Amazon, que suporta diversos modelos avançados de linguagem.",
|
"description": "A AWS Bedrock é um serviço de modelos fundamentais totalmente gerido fornecido pela Amazon, que suporta diversos modelos avançados de linguagem.",
|
||||||
"region": "Região da AWS",
|
"region": "Região da AWS",
|
||||||
"region_help": "A sua região de serviço da AWS, por exemplo, us-east-1",
|
"region_help": "A sua região de serviço da AWS, por exemplo, us-east-1",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"agent": {
|
"agent": {
|
||||||
"add": {
|
"add": {
|
||||||
"description": "[to be translated]:Handle complex tasks with various tools",
|
"description": "Справляйтесь со сложными задачами с помощью различных инструментов",
|
||||||
"error": {
|
"error": {
|
||||||
"failed": "Не удалось добавить агента",
|
"failed": "Не удалось добавить агента",
|
||||||
"invalid_agent": "Недействительный агент"
|
"invalid_agent": "Недействительный агент"
|
||||||
@ -548,11 +548,11 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"add": {
|
"add": {
|
||||||
"assistant": {
|
"assistant": {
|
||||||
"description": "[to be translated]:Daily conversations and quick Q&A",
|
"description": "Ежедневные разговоры и быстрые вопросы и ответы",
|
||||||
"title": "Добавить ассистента"
|
"title": "Добавить ассистента"
|
||||||
},
|
},
|
||||||
"option": {
|
"option": {
|
||||||
"title": "[to be translated]:Select Type"
|
"title": "Выберите тип"
|
||||||
},
|
},
|
||||||
"topic": {
|
"topic": {
|
||||||
"title": "Новый топик"
|
"title": "Новый топик"
|
||||||
@ -2482,6 +2482,7 @@
|
|||||||
"qiniu": "Qiniu AI",
|
"qiniu": "Qiniu AI",
|
||||||
"qwenlm": "QwenLM",
|
"qwenlm": "QwenLM",
|
||||||
"silicon": "SiliconFlow",
|
"silicon": "SiliconFlow",
|
||||||
|
"sophnet": "SophNet",
|
||||||
"stepfun": "StepFun",
|
"stepfun": "StepFun",
|
||||||
"tencent-cloud-ti": "Tencent Cloud TI",
|
"tencent-cloud-ti": "Tencent Cloud TI",
|
||||||
"together": "Together",
|
"together": "Together",
|
||||||
@ -4259,6 +4260,12 @@
|
|||||||
"aws-bedrock": {
|
"aws-bedrock": {
|
||||||
"access_key_id": "AWS Ключ доступа ID",
|
"access_key_id": "AWS Ключ доступа ID",
|
||||||
"access_key_id_help": "Ваш AWS Ключ доступа ID для доступа к AWS Bedrock",
|
"access_key_id_help": "Ваш AWS Ключ доступа ID для доступа к AWS Bedrock",
|
||||||
|
"api_key": "Ключ API Bedrock",
|
||||||
|
"api_key_help": "Ваш ключ API AWS Bedrock для аутентификации",
|
||||||
|
"auth_type": "Тип аутентификации",
|
||||||
|
"auth_type_api_key": "Ключ API Bedrock",
|
||||||
|
"auth_type_help": "Выберите между аутентификацией с помощью учетных данных IAM или ключа API Bedrock",
|
||||||
|
"auth_type_iam": "Учетные данные IAM",
|
||||||
"description": "AWS Bedrock — это полное управляемое сервисное предложение для моделей, поддерживающее различные современные модели языка",
|
"description": "AWS Bedrock — это полное управляемое сервисное предложение для моделей, поддерживающее различные современные модели языка",
|
||||||
"region": "AWS регион",
|
"region": "AWS регион",
|
||||||
"region_help": "Ваш регион AWS, например us-east-1",
|
"region_help": "Ваш регион AWS, например us-east-1",
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import type { DropResult } from '@hello-pangea/dnd'
|
|||||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
|
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
|
||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { ActionIconButton } from '@renderer/components/Buttons'
|
import { ActionIconButton } from '@renderer/components/Buttons'
|
||||||
|
import { MdiLightbulbOn } from '@renderer/components/Icons'
|
||||||
import type { QuickPanelListItem } from '@renderer/components/QuickPanel'
|
import type { QuickPanelListItem } from '@renderer/components/QuickPanel'
|
||||||
import {
|
import {
|
||||||
isAnthropicModel,
|
isAnthropicModel,
|
||||||
@ -231,6 +232,15 @@ const InputbarTools = ({
|
|||||||
quickPhrasesButtonRef.current?.openQuickPanel()
|
quickPhrasesButtonRef.current?.openQuickPanel()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t('assistants.settings.reasoning_effort.label'),
|
||||||
|
description: '',
|
||||||
|
icon: <MdiLightbulbOn />,
|
||||||
|
isMenu: true,
|
||||||
|
action: () => {
|
||||||
|
thinkingButtonRef.current?.openQuickPanel()
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t('assistants.presets.edit.model.select.title'),
|
label: t('assistants.presets.edit.model.select.title'),
|
||||||
description: '',
|
description: '',
|
||||||
@ -246,6 +256,7 @@ const InputbarTools = ({
|
|||||||
icon: <FileSearch />,
|
icon: <FileSearch />,
|
||||||
isMenu: true,
|
isMenu: true,
|
||||||
disabled: files.length > 0,
|
disabled: files.length > 0,
|
||||||
|
hidden: !showKnowledgeBaseButton,
|
||||||
action: () => {
|
action: () => {
|
||||||
knowledgeBaseButtonRef.current?.openQuickPanel()
|
knowledgeBaseButtonRef.current?.openQuickPanel()
|
||||||
}
|
}
|
||||||
@ -313,7 +324,7 @@ const InputbarTools = ({
|
|||||||
translate()
|
translate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
] satisfies QuickPanelListItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDragEnd = (result: DropResult) => {
|
const handleDragEnd = (result: DropResult) => {
|
||||||
|
|||||||
@ -1,16 +1,11 @@
|
|||||||
import { Button, cn } from '@cherrystudio/ui'
|
import { Button, cn } from '@cherrystudio/ui'
|
||||||
import { PlusIcon } from 'lucide-react'
|
import { PlusIcon } from 'lucide-react'
|
||||||
import type { FC } from 'react'
|
|
||||||
|
|
||||||
interface Props extends React.ComponentProps<typeof Button> {
|
const AddButton = ({ children, className, ...props }) => {
|
||||||
children: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
const AddButton: FC<Props> = ({ children, className, ...props }) => {
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
{...props}
|
{...props}
|
||||||
variant="ghost"
|
onClick={props.onClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-9 w-[calc(var(--assistants-width)-20px)] justify-start rounded-lg bg-transparent px-3 text-[13px] text-[var(--color-text-2)] hover:bg-[var(--color-list-item)]',
|
'h-9 w-[calc(var(--assistants-width)-20px)] justify-start rounded-lg bg-transparent px-3 text-[13px] text-[var(--color-text-2)] hover:bg-[var(--color-list-item)]',
|
||||||
className
|
className
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Tooltip } from '@cherrystudio/ui'
|
import { Tooltip } from '@cherrystudio/ui'
|
||||||
import { usePreference } from '@data/hooks/usePreference'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
|
import { cn } from '@heroui/react'
|
||||||
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
||||||
@ -20,12 +21,12 @@ import {
|
|||||||
ContextMenuSubTrigger,
|
ContextMenuSubTrigger,
|
||||||
ContextMenuTrigger
|
ContextMenuTrigger
|
||||||
} from '@renderer/ui/context-menu'
|
} from '@renderer/ui/context-menu'
|
||||||
import { classNames } from '@renderer/utils'
|
|
||||||
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
|
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
|
||||||
import { MenuIcon, XIcon } from 'lucide-react'
|
import { MenuIcon, XIcon } from 'lucide-react'
|
||||||
import React, { type FC, memo, startTransition, useEffect, useMemo, useState } from 'react'
|
import React, { type FC, memo, startTransition, useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
|
||||||
|
import { ListItem, ListItemEditInput, ListItemName, ListItemNameContainer, MenuButton, StatusIndicator } from './shared'
|
||||||
// const logger = loggerService.withContext('AgentItem')
|
// const logger = loggerService.withContext('AgentItem')
|
||||||
|
|
||||||
interface SessionItemProps {
|
interface SessionItemProps {
|
||||||
@ -65,7 +66,6 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, onDelete, onPress
|
|||||||
</div>
|
</div>
|
||||||
}>
|
}>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
className="menu"
|
|
||||||
onClick={(e: React.MouseEvent) => {
|
onClick={(e: React.MouseEvent) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
if (isConfirmingDeletion || e.ctrlKey || e.metaKey) {
|
if (isConfirmingDeletion || e.ctrlKey || e.metaKey) {
|
||||||
@ -113,20 +113,21 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, onDelete, onPress
|
|||||||
<>
|
<>
|
||||||
<ContextMenu modal={false}>
|
<ContextMenu modal={false}>
|
||||||
<ContextMenuTrigger>
|
<ContextMenuTrigger>
|
||||||
<SessionListItem
|
<ListItem
|
||||||
className={classNames(isActive ? 'active' : '', singlealone ? 'singlealone' : '')}
|
className={cn(
|
||||||
|
isActive ? 'active' : undefined,
|
||||||
|
singlealone ? 'singlealone' : undefined,
|
||||||
|
isEditing ? 'cursor-default' : 'cursor-pointer',
|
||||||
|
'rounded-[var(--list-item-border-radius)]'
|
||||||
|
)}
|
||||||
onClick={isEditing ? undefined : onPress}
|
onClick={isEditing ? undefined : onPress}
|
||||||
onDoubleClick={() => startEdit(session.name ?? '')}
|
onDoubleClick={() => startEdit(session.name ?? '')}
|
||||||
title={session.name ?? session.id}
|
title={session.name ?? session.id}>
|
||||||
style={{
|
{isPending && !isActive && <StatusIndicator variant="pending" />}
|
||||||
borderRadius: 'var(--list-item-border-radius)',
|
{isFulfilled && !isActive && <StatusIndicator variant="fulfilled" />}
|
||||||
cursor: isEditing ? 'default' : 'pointer'
|
<ListItemNameContainer>
|
||||||
}}>
|
|
||||||
{isPending && !isActive && <PendingIndicator />}
|
|
||||||
{isFulfilled && !isActive && <FulfilledIndicator />}
|
|
||||||
<SessionNameContainer>
|
|
||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
<SessionEditInput
|
<ListItemEditInput
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
value={editValue}
|
value={editValue}
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleValueChange(e.target.value)}
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleValueChange(e.target.value)}
|
||||||
@ -136,14 +137,14 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, onDelete, onPress
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<SessionName>
|
<ListItemName>
|
||||||
<SessionLabel session={session} />
|
<SessionLabel session={session} />
|
||||||
</SessionName>
|
</ListItemName>
|
||||||
<DeleteButton />
|
<DeleteButton />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</SessionNameContainer>
|
</ListItemNameContainer>
|
||||||
</SessionListItem>
|
</ListItem>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
@ -186,121 +187,4 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, onDelete, onPress
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const SessionListItem = styled.div`
|
|
||||||
padding: 7px 12px;
|
|
||||||
border-radius: var(--list-item-border-radius);
|
|
||||||
font-size: 13px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
cursor: pointer;
|
|
||||||
width: calc(var(--assistants-width) - 20px);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
|
|
||||||
.menu {
|
|
||||||
opacity: 0;
|
|
||||||
color: var(--color-text-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--color-list-item-hover);
|
|
||||||
transition: background-color 0.1s;
|
|
||||||
|
|
||||||
.menu {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: var(--color-list-item);
|
|
||||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
||||||
.menu {
|
|
||||||
opacity: 1;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--color-text-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.singlealone {
|
|
||||||
border-radius: 0 !important;
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--color-background-soft);
|
|
||||||
}
|
|
||||||
&.active {
|
|
||||||
border-left: 2px solid var(--color-primary);
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const SessionNameContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
height: 20px;
|
|
||||||
justify-content: space-between;
|
|
||||||
`
|
|
||||||
|
|
||||||
const SessionName = styled.div`
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 1;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: 13px;
|
|
||||||
position: relative;
|
|
||||||
`
|
|
||||||
|
|
||||||
const SessionEditInput = styled.input`
|
|
||||||
background: var(--color-background);
|
|
||||||
border: none;
|
|
||||||
color: var(--color-text-1);
|
|
||||||
font-size: 13px;
|
|
||||||
font-family: inherit;
|
|
||||||
padding: 2px 6px;
|
|
||||||
width: 100%;
|
|
||||||
outline: none;
|
|
||||||
padding: 0;
|
|
||||||
`
|
|
||||||
|
|
||||||
const MenuButton = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-width: 20px;
|
|
||||||
min-height: 20px;
|
|
||||||
.anticon {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const PendingIndicator = styled.div.attrs({
|
|
||||||
className: 'animation-pulse'
|
|
||||||
})`
|
|
||||||
--pulse-size: 5px;
|
|
||||||
width: 5px;
|
|
||||||
height: 5px;
|
|
||||||
position: absolute;
|
|
||||||
left: 3px;
|
|
||||||
top: 15px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--color-status-warning);
|
|
||||||
`
|
|
||||||
|
|
||||||
const FulfilledIndicator = styled.div.attrs({
|
|
||||||
className: 'animation-pulse'
|
|
||||||
})`
|
|
||||||
--pulse-size: 5px;
|
|
||||||
width: 5px;
|
|
||||||
height: 5px;
|
|
||||||
position: absolute;
|
|
||||||
left: 3px;
|
|
||||||
top: 15px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--color-status-success);
|
|
||||||
`
|
|
||||||
|
|
||||||
export default memo(SessionItem)
|
export default memo(SessionItem)
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
|
|
||||||
import AddButton from './AddButton'
|
import AddButton from './AddButton'
|
||||||
import SessionItem from './SessionItem'
|
import SessionItem from './SessionItem'
|
||||||
|
import { ListContainer } from './shared'
|
||||||
|
|
||||||
// const logger = loggerService.withContext('SessionsTab')
|
// const logger = loggerService.withContext('SessionsTab')
|
||||||
|
|
||||||
@ -95,7 +96,7 @@ const Sessions: React.FC<SessionsProps> = ({ agentId }) => {
|
|||||||
if (error) return <Alert color="danger" content={t('agent.session.get.error.failed')} />
|
if (error) return <Alert color="danger" content={t('agent.session.get.error.failed')} />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sessions-tab flex h-full w-full flex-col p-2">
|
<ListContainer className="sessions-tab">
|
||||||
<AddButton onClick={createDefaultSession} className="mb-2" disabled={creatingSession}>
|
<AddButton onClick={createDefaultSession} className="mb-2" disabled={creatingSession}>
|
||||||
{t('agent.session.add.title')}
|
{t('agent.session.add.title')}
|
||||||
</AddButton>
|
</AddButton>
|
||||||
@ -118,7 +119,7 @@ const Sessions: React.FC<SessionsProps> = ({ agentId }) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</DynamicVirtualList>
|
</DynamicVirtualList>
|
||||||
</div>
|
</ListContainer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { Tooltip } from '@cherrystudio/ui'
|
import { Tooltip } from '@cherrystudio/ui'
|
||||||
import { useCache } from '@data/hooks/useCache'
|
import { useCache } from '@data/hooks/useCache'
|
||||||
import { usePreference } from '@data/hooks/usePreference'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
|
import { cn } from '@heroui/react'
|
||||||
import { DraggableVirtualList } from '@renderer/components/DraggableList'
|
import { DraggableVirtualList } from '@renderer/components/DraggableList'
|
||||||
import { CopyIcon, DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
import { CopyIcon, DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
||||||
import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup'
|
import ObsidianExportPopup from '@renderer/components/Popups/ObsidianExportPopup'
|
||||||
@ -17,7 +18,7 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
|||||||
import type { RootState } from '@renderer/store'
|
import type { RootState } from '@renderer/store'
|
||||||
import { newMessagesActions } from '@renderer/store/newMessage'
|
import { newMessagesActions } from '@renderer/store/newMessage'
|
||||||
import type { Assistant, Topic } from '@renderer/types'
|
import type { Assistant, Topic } from '@renderer/types'
|
||||||
import { classNames, removeSpecialCharactersForFileName } from '@renderer/utils'
|
import { removeSpecialCharactersForFileName } from '@renderer/utils'
|
||||||
import { copyTopicAsMarkdown, copyTopicAsPlainText } from '@renderer/utils/copy'
|
import { copyTopicAsMarkdown, copyTopicAsPlainText } from '@renderer/utils/copy'
|
||||||
import {
|
import {
|
||||||
exportMarkdownToJoplin,
|
exportMarkdownToJoplin,
|
||||||
@ -53,6 +54,15 @@ import { useDispatch, useSelector } from 'react-redux'
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import AddButton from './AddButton'
|
import AddButton from './AddButton'
|
||||||
|
import {
|
||||||
|
ListContainer,
|
||||||
|
ListItem,
|
||||||
|
ListItemEditInput,
|
||||||
|
ListItemName,
|
||||||
|
ListItemNameContainer,
|
||||||
|
MenuButton,
|
||||||
|
StatusIndicator
|
||||||
|
} from './shared'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
assistant: Assistant
|
assistant: Assistant
|
||||||
@ -78,8 +88,6 @@ export const Topics: React.FC<Props> = ({ assistant: _assistant, activeTopic, se
|
|||||||
const topicFulfilledQuery = useSelector((state: RootState) => state.messages.fulfilledByTopic)
|
const topicFulfilledQuery = useSelector((state: RootState) => state.messages.fulfilledByTopic)
|
||||||
const newlyRenamedTopics = useSelector((state: RootState) => state.runtime.chat.newlyRenamedTopics)
|
const newlyRenamedTopics = useSelector((state: RootState) => state.runtime.chat.newlyRenamedTopics)
|
||||||
|
|
||||||
const borderRadius = showTopicTime ? 12 : 'var(--list-item-border-radius)'
|
|
||||||
|
|
||||||
const [deletingTopicId, setDeletingTopicId] = useState<string | null>(null)
|
const [deletingTopicId, setDeletingTopicId] = useState<string | null>(null)
|
||||||
const deleteTimerRef = useRef<NodeJS.Timeout>(null)
|
const deleteTimerRef = useRef<NodeJS.Timeout>(null)
|
||||||
const [editingTopicId, setEditingTopicId] = useState<string | null>(null)
|
const [editingTopicId, setEditingTopicId] = useState<string | null>(null)
|
||||||
@ -497,252 +505,107 @@ export const Topics: React.FC<Props> = ({ assistant: _assistant, activeTopic, se
|
|||||||
const singlealone = topicPosition === 'right' && position === 'right'
|
const singlealone = topicPosition === 'right' && position === 'right'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DraggableVirtualList
|
<ListContainer className="topics-tab">
|
||||||
className="topics-tab"
|
<AddButton onClick={() => EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)} className="mb-2">
|
||||||
list={sortedTopics}
|
{t('chat.add.topic.title')}
|
||||||
onUpdate={updateTopics}
|
</AddButton>
|
||||||
style={{ height: '100%', padding: '11px 0 10px 10px' }}
|
<DraggableVirtualList list={sortedTopics} onUpdate={updateTopics} className="overflow-y-auto overflow-x-hidden">
|
||||||
itemContainerStyle={{ paddingBottom: '8px' }}
|
{(topic) => {
|
||||||
header={
|
const isActive = topic.id === activeTopic?.id
|
||||||
<AddButton onClick={() => EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)} className="mb-2">
|
const topicName = topic.name.replace('`', '')
|
||||||
{t('chat.add.topic.title')}
|
const topicPrompt = topic.prompt
|
||||||
</AddButton>
|
const fullTopicPrompt = t('common.prompt') + ': ' + topicPrompt
|
||||||
}>
|
|
||||||
{(topic) => {
|
|
||||||
const isActive = topic.id === activeTopic?.id
|
|
||||||
const topicName = topic.name.replace('`', '')
|
|
||||||
const topicPrompt = topic.prompt
|
|
||||||
const fullTopicPrompt = t('common.prompt') + ': ' + topicPrompt
|
|
||||||
|
|
||||||
const getTopicNameClassName = () => {
|
const getTopicNameClassName = () => {
|
||||||
if (isRenaming(topic.id)) return 'shimmer'
|
if (isRenaming(topic.id)) return 'shimmer'
|
||||||
if (isNewlyRenamed(topic.id)) return 'typing'
|
if (isNewlyRenamed(topic.id)) return 'typing'
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown menu={{ items: getTopicMenuItems }} trigger={['contextMenu']}>
|
<Dropdown menu={{ items: getTopicMenuItems }} trigger={['contextMenu']}>
|
||||||
<TopicListItem
|
<ListItem
|
||||||
onContextMenu={() => setTargetTopic(topic)}
|
onContextMenu={() => setTargetTopic(topic)}
|
||||||
className={classNames(isActive ? 'active' : '', singlealone ? 'singlealone' : '')}
|
className={cn(
|
||||||
onClick={editingTopicId === topic.id && topicEdit.isEditing ? undefined : () => onSwitchTopic(topic)}
|
isActive ? 'active' : undefined,
|
||||||
style={{
|
singlealone ? 'singlealone' : undefined,
|
||||||
borderRadius,
|
editingTopicId === topic.id && topicEdit.isEditing ? 'cursor-default' : 'cursor-pointer',
|
||||||
cursor: editingTopicId === topic.id && topicEdit.isEditing ? 'default' : 'pointer'
|
showTopicTime ? 'rounded-2xl' : 'rounded-[var(--list-item-border-radius)]'
|
||||||
}}>
|
|
||||||
{isPending(topic.id) && !isActive && <PendingIndicator />}
|
|
||||||
{isFulfilled(topic.id) && !isActive && <FulfilledIndicator />}
|
|
||||||
<TopicNameContainer>
|
|
||||||
{editingTopicId === topic.id && topicEdit.isEditing ? (
|
|
||||||
<TopicEditInput
|
|
||||||
ref={topicEdit.inputRef}
|
|
||||||
value={topicEdit.editValue}
|
|
||||||
onChange={topicEdit.handleInputChange}
|
|
||||||
onKeyDown={topicEdit.handleKeyDown}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<TopicName
|
|
||||||
className={getTopicNameClassName()}
|
|
||||||
title={topicName}
|
|
||||||
onDoubleClick={() => {
|
|
||||||
setEditingTopicId(topic.id)
|
|
||||||
topicEdit.startEdit(topic.name)
|
|
||||||
}}>
|
|
||||||
{topicName}
|
|
||||||
</TopicName>
|
|
||||||
)}
|
)}
|
||||||
{!topic.pinned && (
|
onClick={editingTopicId === topic.id && topicEdit.isEditing ? undefined : () => onSwitchTopic(topic)}>
|
||||||
<Tooltip
|
{isPending(topic.id) && !isActive && <StatusIndicator variant="pending" />}
|
||||||
placement="bottom"
|
{isFulfilled(topic.id) && !isActive && <StatusIndicator variant="fulfilled" />}
|
||||||
delay={700}
|
<ListItemNameContainer>
|
||||||
closeDelay={0}
|
{editingTopicId === topic.id && topicEdit.isEditing ? (
|
||||||
content={
|
<ListItemEditInput
|
||||||
<div style={{ fontSize: '12px', opacity: 0.8, fontStyle: 'italic' }}>
|
ref={topicEdit.inputRef}
|
||||||
{t('chat.topics.delete.shortcut', { key: isMac ? '⌘' : 'Ctrl' })}
|
value={topicEdit.editValue}
|
||||||
</div>
|
onChange={topicEdit.handleInputChange}
|
||||||
}>
|
onKeyDown={topicEdit.handleKeyDown}
|
||||||
<MenuButton
|
onClick={(e) => e.stopPropagation()}
|
||||||
className="menu"
|
/>
|
||||||
onClick={(e) => {
|
) : (
|
||||||
if (e.ctrlKey || e.metaKey) {
|
<ListItemName
|
||||||
handleConfirmDelete(topic, e)
|
className={getTopicNameClassName()}
|
||||||
} else if (deletingTopicId === topic.id) {
|
title={topicName}
|
||||||
handleConfirmDelete(topic, e)
|
onDoubleClick={() => {
|
||||||
} else {
|
setEditingTopicId(topic.id)
|
||||||
handleDeleteClick(topic.id, e)
|
topicEdit.startEdit(topic.name)
|
||||||
}
|
|
||||||
}}>
|
}}>
|
||||||
{deletingTopicId === topic.id ? (
|
{topicName}
|
||||||
<DeleteIcon size={14} color="var(--color-error)" style={{ pointerEvents: 'none' }} />
|
</ListItemName>
|
||||||
) : (
|
)}
|
||||||
<XIcon size={14} color="var(--color-text-3)" style={{ pointerEvents: 'none' }} />
|
{!topic.pinned && (
|
||||||
)}
|
<Tooltip
|
||||||
|
placement="bottom"
|
||||||
|
delay={700}
|
||||||
|
closeDelay={0}
|
||||||
|
content={
|
||||||
|
<div style={{ fontSize: '12px', opacity: 0.8, fontStyle: 'italic' }}>
|
||||||
|
{t('chat.topics.delete.shortcut', { key: isMac ? '⌘' : 'Ctrl' })}
|
||||||
|
</div>
|
||||||
|
}>
|
||||||
|
<MenuButton
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.ctrlKey || e.metaKey) {
|
||||||
|
handleConfirmDelete(topic, e)
|
||||||
|
} else if (deletingTopicId === topic.id) {
|
||||||
|
handleConfirmDelete(topic, e)
|
||||||
|
} else {
|
||||||
|
handleDeleteClick(topic.id, e)
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{deletingTopicId === topic.id ? (
|
||||||
|
<DeleteIcon size={14} color="var(--color-error)" style={{ pointerEvents: 'none' }} />
|
||||||
|
) : (
|
||||||
|
<XIcon size={14} color="var(--color-text-3)" style={{ pointerEvents: 'none' }} />
|
||||||
|
)}
|
||||||
|
</MenuButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{topic.pinned && (
|
||||||
|
<MenuButton className="pin">
|
||||||
|
<PinIcon size={14} color="var(--color-text-3)" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
</Tooltip>
|
)}
|
||||||
|
</ListItemNameContainer>
|
||||||
|
{topicPrompt && (
|
||||||
|
<TopicPromptText className="prompt" title={fullTopicPrompt}>
|
||||||
|
{fullTopicPrompt}
|
||||||
|
</TopicPromptText>
|
||||||
)}
|
)}
|
||||||
{topic.pinned && (
|
{showTopicTime && (
|
||||||
<MenuButton className="pin">
|
<TopicTime className="time">{dayjs(topic.createdAt).format('MM/DD HH:mm')}</TopicTime>
|
||||||
<PinIcon size={14} color="var(--color-text-3)" />
|
|
||||||
</MenuButton>
|
|
||||||
)}
|
)}
|
||||||
</TopicNameContainer>
|
</ListItem>
|
||||||
{topicPrompt && (
|
</Dropdown>
|
||||||
<TopicPromptText className="prompt" title={fullTopicPrompt}>
|
)
|
||||||
{fullTopicPrompt}
|
}}
|
||||||
</TopicPromptText>
|
</DraggableVirtualList>
|
||||||
)}
|
</ListContainer>
|
||||||
{showTopicTime && <TopicTime className="time">{dayjs(topic.createdAt).format('MM/DD HH:mm')}</TopicTime>}
|
|
||||||
</TopicListItem>
|
|
||||||
</Dropdown>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</DraggableVirtualList>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const TopicListItem = styled.div`
|
|
||||||
padding: 7px 12px;
|
|
||||||
border-radius: var(--list-item-border-radius);
|
|
||||||
font-size: 13px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
cursor: pointer;
|
|
||||||
width: calc(var(--assistants-width) - 20px);
|
|
||||||
|
|
||||||
.menu {
|
|
||||||
opacity: 0;
|
|
||||||
color: var(--color-text-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--color-list-item-hover);
|
|
||||||
transition: background-color 0.1s;
|
|
||||||
|
|
||||||
.menu {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: var(--color-list-item);
|
|
||||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
||||||
.menu {
|
|
||||||
opacity: 1;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--color-text-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.singlealone {
|
|
||||||
border-radius: 0 !important;
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--color-background-soft);
|
|
||||||
}
|
|
||||||
&.active {
|
|
||||||
border-left: 2px solid var(--color-primary);
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const TopicNameContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
height: 20px;
|
|
||||||
justify-content: space-between;
|
|
||||||
`
|
|
||||||
|
|
||||||
const TopicName = styled.div`
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 1;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: 13px;
|
|
||||||
position: relative;
|
|
||||||
will-change: background-position, width;
|
|
||||||
|
|
||||||
--color-shimmer-mid: var(--color-text-1);
|
|
||||||
--color-shimmer-end: color-mix(in srgb, var(--color-text-1) 25%, transparent);
|
|
||||||
|
|
||||||
&.shimmer {
|
|
||||||
background: linear-gradient(to left, var(--color-shimmer-end), var(--color-shimmer-mid), var(--color-shimmer-end));
|
|
||||||
background-size: 200% 100%;
|
|
||||||
background-clip: text;
|
|
||||||
color: transparent;
|
|
||||||
animation: shimmer 3s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.typing {
|
|
||||||
display: block;
|
|
||||||
-webkit-line-clamp: unset;
|
|
||||||
-webkit-box-orient: unset;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
animation: typewriter 0.5s steps(40, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes shimmer {
|
|
||||||
0% {
|
|
||||||
background-position: 200% 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: -200% 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes typewriter {
|
|
||||||
from {
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const TopicEditInput = styled.input`
|
|
||||||
background: var(--color-background);
|
|
||||||
border: none;
|
|
||||||
color: var(--color-text-1);
|
|
||||||
font-size: 13px;
|
|
||||||
font-family: inherit;
|
|
||||||
padding: 2px 6px;
|
|
||||||
width: 100%;
|
|
||||||
outline: none;
|
|
||||||
padding: 0;
|
|
||||||
`
|
|
||||||
|
|
||||||
const PendingIndicator = styled.div.attrs({
|
|
||||||
className: 'animation-pulse'
|
|
||||||
})`
|
|
||||||
--pulse-size: 5px;
|
|
||||||
width: 5px;
|
|
||||||
height: 5px;
|
|
||||||
position: absolute;
|
|
||||||
left: 3px;
|
|
||||||
top: 15px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--color-status-warning);
|
|
||||||
`
|
|
||||||
|
|
||||||
const FulfilledIndicator = styled.div.attrs({
|
|
||||||
className: 'animation-pulse'
|
|
||||||
})`
|
|
||||||
--pulse-size: 5px;
|
|
||||||
width: 5px;
|
|
||||||
height: 5px;
|
|
||||||
position: absolute;
|
|
||||||
left: 3px;
|
|
||||||
top: 15px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--color-status-success);
|
|
||||||
`
|
|
||||||
|
|
||||||
const TopicPromptText = styled.div`
|
const TopicPromptText = styled.div`
|
||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -759,15 +622,3 @@ const TopicTime = styled.div`
|
|||||||
color: var(--color-text-3);
|
color: var(--color-text-3);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const MenuButton = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-width: 20px;
|
|
||||||
min-height: 20px;
|
|
||||||
.anticon {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|||||||
131
src/renderer/src/pages/home/Tabs/components/shared.tsx
Normal file
131
src/renderer/src/pages/home/Tabs/components/shared.tsx
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import { cn } from '@heroui/react'
|
||||||
|
import type { ComponentPropsWithoutRef, ComponentPropsWithRef } from 'react'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
export const ListItem = ({ children, className, ...props }: ComponentPropsWithoutRef<'div'>) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'mb-2 flex w-[calc(var(--assistants-width)-20px)] cursor-pointer flex-col justify-between rounded-lg px-3 py-2 text-sm',
|
||||||
|
'transition-colors duration-100',
|
||||||
|
'hover:bg-[var(--color-list-item-hover)]',
|
||||||
|
'[.active]:bg-[var(--color-list-item)] [.active]:shadow-[0_1px_2px_0_rgba(0,0,0,0.05)]',
|
||||||
|
'[&_.menu]:text-[var(--color-text-3)] [&_.menu]:opacity-0',
|
||||||
|
'hover:[&_.menu]:opacity-100',
|
||||||
|
'[.active]:[&_.menu]:opacity-100 [.active]:[&_.menu]:hover:text-[var(--color-text-2)]',
|
||||||
|
'[.singlealone.active]:border-[var(--color-primary)] [.singlealone.active]:shadow-none [.singlealone]:rounded-none [.singlealone]:border-transparent [.singlealone]:border-l-2 [.singlealone]:hover:bg-[var(--color-background-soft)]',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export const ListItemNameContainer = ({ children, className, ...props }: ComponentPropsWithoutRef<'div'>) => {
|
||||||
|
return (
|
||||||
|
<div className={cn('flex h-5 flex-row items-center justify-between gap-1', className)} {...props}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This component involves complex animations and will not be migrated for now.
|
||||||
|
export const ListItemName = styled.div`
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 14px;
|
||||||
|
position: relative;
|
||||||
|
will-change: background-position, width;
|
||||||
|
|
||||||
|
--color-shimmer-mid: var(--color-text-1);
|
||||||
|
--color-shimmer-end: color-mix(in srgb, var(--color-text-1) 25%, transparent);
|
||||||
|
|
||||||
|
&.shimmer {
|
||||||
|
background: linear-gradient(to left, var(--color-shimmer-end), var(--color-shimmer-mid), var(--color-shimmer-end));
|
||||||
|
background-size: 200% 100%;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
animation: shimmer 3s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.typing {
|
||||||
|
display: block;
|
||||||
|
-webkit-line-clamp: unset;
|
||||||
|
-webkit-box-orient: unset;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
animation: typewriter 0.5s steps(40, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: 200% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -200% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes typewriter {
|
||||||
|
from {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const ListItemEditInput = ({ className, ...props }: ComponentPropsWithRef<'input'>) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className={cn(
|
||||||
|
'w-full border-none bg-[var(--color-background)] p-0 font-inherit text-[var(--color-text-1)] text-sm outline-none',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ListContainer = ({ children, className, ...props }: ComponentPropsWithoutRef<'div'>) => {
|
||||||
|
return (
|
||||||
|
<div className={cn('flex h-full w-full flex-col p-2', className)} {...props}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MenuButton = ({ children, className, ...props }: ComponentPropsWithoutRef<'div'>) => {
|
||||||
|
return (
|
||||||
|
<div className={cn('menu', 'flex min-h-5 min-w-5 flex-row items-center justify-center', className)} {...props}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatusIndicator = ({ variant }: { variant: 'pending' | 'fulfilled' }) => {
|
||||||
|
const colors = useMemo(() => {
|
||||||
|
switch (variant) {
|
||||||
|
case 'pending':
|
||||||
|
return {
|
||||||
|
wave: 'bg-warning-400',
|
||||||
|
back: 'bg-warning-500'
|
||||||
|
}
|
||||||
|
case 'fulfilled':
|
||||||
|
return {
|
||||||
|
wave: 'bg-success-400',
|
||||||
|
back: 'bg-success-500'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [variant])
|
||||||
|
return (
|
||||||
|
<div className="absolute top-4 left-1 flex size-1">
|
||||||
|
<span className={cn('absolute inline-flex h-full w-full animate-ping rounded-full opacity-75', colors.wave)} />
|
||||||
|
<span className={cn('relative inline-flex size-1 rounded-full bg-warning-500', colors.back)} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -95,11 +95,14 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
return editImageFiles
|
return editImageFiles
|
||||||
}, [editImageFiles])
|
}, [editImageFiles])
|
||||||
|
|
||||||
const updatePaintingState = (updates: Partial<PaintingAction>) => {
|
const updatePaintingState = useCallback(
|
||||||
const updatedPainting = { ...painting, providerId: newApiProvider.id, ...updates }
|
(updates: Partial<PaintingAction>) => {
|
||||||
setPainting(updatedPainting)
|
const updatedPainting = { ...painting, providerId: newApiProvider.id, ...updates }
|
||||||
updatePainting(mode, updatedPainting)
|
setPainting(updatedPainting)
|
||||||
}
|
updatePainting(mode, updatedPainting)
|
||||||
|
},
|
||||||
|
[painting, newApiProvider.id, mode, updatePainting]
|
||||||
|
)
|
||||||
|
|
||||||
// ---------------- Model Related Configurations ----------------
|
// ---------------- Model Related Configurations ----------------
|
||||||
// const modelOptions = MODELS.map((m) => ({ label: m.name, value: m.name }))
|
// const modelOptions = MODELS.map((m) => ({ label: m.name, value: m.name }))
|
||||||
@ -481,6 +484,13 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// if painting.model is not set, set it to the first model in modelOptions
|
||||||
|
useEffect(() => {
|
||||||
|
if (!painting.model && modelOptions.length > 0) {
|
||||||
|
updatePaintingState({ model: modelOptions[0].value })
|
||||||
|
}
|
||||||
|
}, [modelOptions, painting.model, updatePaintingState])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Navbar>
|
<Navbar>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { isNewApiProvider } from '@renderer/config/providers'
|
|||||||
import { useAllProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders } from '@renderer/hooks/useProvider'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setDefaultPaintingProvider } from '@renderer/store/settings'
|
import { setDefaultPaintingProvider } from '@renderer/store/settings'
|
||||||
|
import { updateTab } from '@renderer/store/tabs'
|
||||||
import type { PaintingProvider, SystemProviderId } from '@renderer/types'
|
import type { PaintingProvider, SystemProviderId } from '@renderer/types'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
@ -25,11 +26,11 @@ const PaintingsRoutePage: FC = () => {
|
|||||||
const provider = params['*']
|
const provider = params['*']
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
const providers = useAllProviders()
|
const providers = useAllProviders()
|
||||||
const Options = useMemo(() => {
|
|
||||||
return [...BASE_OPTIONS, ...providers.filter((p) => isNewApiProvider(p)).map((p) => p.id)]
|
|
||||||
}, [providers])
|
|
||||||
const [ovmsStatus, setOvmsStatus] = useState<'not-installed' | 'not-running' | 'running'>('not-running')
|
const [ovmsStatus, setOvmsStatus] = useState<'not-installed' | 'not-running' | 'running'>('not-running')
|
||||||
|
|
||||||
|
const Options = useMemo(() => [...BASE_OPTIONS, ...providers.filter(isNewApiProvider).map((p) => p.id)], [providers])
|
||||||
|
const newApiProviders = useMemo(() => providers.filter(isNewApiProvider), [providers])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkStatus = async () => {
|
const checkStatus = async () => {
|
||||||
const status = await window.api.ovms.getStatus()
|
const status = await window.api.ovms.getStatus()
|
||||||
@ -44,25 +45,24 @@ const PaintingsRoutePage: FC = () => {
|
|||||||
logger.debug(`defaultPaintingProvider: ${provider}`)
|
logger.debug(`defaultPaintingProvider: ${provider}`)
|
||||||
if (provider && validOptions.includes(provider)) {
|
if (provider && validOptions.includes(provider)) {
|
||||||
dispatch(setDefaultPaintingProvider(provider as PaintingProvider))
|
dispatch(setDefaultPaintingProvider(provider as PaintingProvider))
|
||||||
|
dispatch(updateTab({ id: 'paintings', updates: { path: `/paintings/${provider}` } }))
|
||||||
}
|
}
|
||||||
}, [provider, dispatch, validOptions])
|
}, [provider, dispatch, validOptions])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="*" element={<ZhipuPage Options={validOptions} />} />
|
<Route path="*" element={<NewApiPage Options={validOptions} />} />
|
||||||
<Route path="/zhipu" element={<ZhipuPage Options={validOptions} />} />
|
<Route path="/zhipu" element={<ZhipuPage Options={validOptions} />} />
|
||||||
<Route path="/aihubmix" element={<AihubmixPage Options={validOptions} />} />
|
<Route path="/aihubmix" element={<AihubmixPage Options={validOptions} />} />
|
||||||
<Route path="/silicon" element={<SiliconPage Options={validOptions} />} />
|
<Route path="/silicon" element={<SiliconPage Options={validOptions} />} />
|
||||||
<Route path="/dmxapi" element={<DmxapiPage Options={validOptions} />} />
|
<Route path="/dmxapi" element={<DmxapiPage Options={validOptions} />} />
|
||||||
<Route path="/tokenflux" element={<TokenFluxPage Options={validOptions} />} />
|
<Route path="/tokenflux" element={<TokenFluxPage Options={validOptions} />} />
|
||||||
<Route path="/ovms" element={<OvmsPage Options={validOptions} />} />
|
<Route path="/ovms" element={<OvmsPage Options={validOptions} />} />
|
||||||
{/* new-api family providers are mounted dynamically below */}
|
|
||||||
{providers
|
|
||||||
.filter((p) => isNewApiProvider(p))
|
|
||||||
.map((p) => (
|
|
||||||
<Route key={p.id} path={`/${p.id}`} element={<NewApiPage Options={validOptions} />} />
|
|
||||||
))}
|
|
||||||
<Route path="/new-api" element={<NewApiPage Options={validOptions} />} />
|
<Route path="/new-api" element={<NewApiPage Options={validOptions} />} />
|
||||||
|
{/* new-api family providers are mounted dynamically below */}
|
||||||
|
{newApiProviders.map((p) => (
|
||||||
|
<Route key={p.id} path={`/${p.id}`} element={<NewApiPage Options={validOptions} />} />
|
||||||
|
))}
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { RowFlex } from '@cherrystudio/ui'
|
import { RowFlex } from '@cherrystudio/ui'
|
||||||
import { PROVIDER_URLS } from '@renderer/config/providers'
|
import { PROVIDER_URLS } from '@renderer/config/providers'
|
||||||
import { useAwsBedrockSettings } from '@renderer/hooks/useAwsBedrock'
|
import { useAwsBedrockSettings } from '@renderer/hooks/useAwsBedrock'
|
||||||
import { Alert, Input } from 'antd'
|
import { Alert, Input, Radio } from 'antd'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -10,14 +10,25 @@ import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle }
|
|||||||
|
|
||||||
const AwsBedrockSettings: FC = () => {
|
const AwsBedrockSettings: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { accessKeyId, secretAccessKey, region, setAccessKeyId, setSecretAccessKey, setRegion } =
|
const {
|
||||||
useAwsBedrockSettings()
|
authType,
|
||||||
|
accessKeyId,
|
||||||
|
secretAccessKey,
|
||||||
|
apiKey,
|
||||||
|
region,
|
||||||
|
setAuthType,
|
||||||
|
setAccessKeyId,
|
||||||
|
setSecretAccessKey,
|
||||||
|
setApiKey,
|
||||||
|
setRegion
|
||||||
|
} = useAwsBedrockSettings()
|
||||||
|
|
||||||
const providerConfig = PROVIDER_URLS['aws-bedrock']
|
const providerConfig = PROVIDER_URLS['aws-bedrock']
|
||||||
const apiKeyWebsite = providerConfig?.websites?.apiKey
|
const apiKeyWebsite = providerConfig?.websites?.apiKey
|
||||||
|
|
||||||
const [localAccessKeyId, setLocalAccessKeyId] = useState(accessKeyId)
|
const [localAccessKeyId, setLocalAccessKeyId] = useState(accessKeyId)
|
||||||
const [localSecretAccessKey, setLocalSecretAccessKey] = useState(secretAccessKey)
|
const [localSecretAccessKey, setLocalSecretAccessKey] = useState(secretAccessKey)
|
||||||
|
const [localApiKey, setLocalApiKey] = useState(apiKey)
|
||||||
const [localRegion, setLocalRegion] = useState(region)
|
const [localRegion, setLocalRegion] = useState(region)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -25,39 +36,75 @@ const AwsBedrockSettings: FC = () => {
|
|||||||
<SettingSubtitle style={{ marginTop: 5 }}>{t('settings.provider.aws-bedrock.title')}</SettingSubtitle>
|
<SettingSubtitle style={{ marginTop: 5 }}>{t('settings.provider.aws-bedrock.title')}</SettingSubtitle>
|
||||||
<Alert type="info" style={{ marginTop: 5 }} message={t('settings.provider.aws-bedrock.description')} showIcon />
|
<Alert type="info" style={{ marginTop: 5 }} message={t('settings.provider.aws-bedrock.description')} showIcon />
|
||||||
|
|
||||||
<SettingSubtitle style={{ marginTop: 5 }}>{t('settings.provider.aws-bedrock.access_key_id')}</SettingSubtitle>
|
{/* Authentication Type Selector */}
|
||||||
<Input
|
<SettingSubtitle style={{ marginTop: 15 }}>{t('settings.provider.aws-bedrock.auth_type')}</SettingSubtitle>
|
||||||
value={localAccessKeyId}
|
<Radio.Group value={authType} onChange={(e) => setAuthType(e.target.value)} style={{ marginTop: 5 }}>
|
||||||
placeholder="Access Key ID"
|
<Radio value="iam">{t('settings.provider.aws-bedrock.auth_type_iam')}</Radio>
|
||||||
onChange={(e) => setLocalAccessKeyId(e.target.value)}
|
<Radio value="apiKey">{t('settings.provider.aws-bedrock.auth_type_api_key')}</Radio>
|
||||||
onBlur={() => setAccessKeyId(localAccessKeyId)}
|
</Radio.Group>
|
||||||
style={{ marginTop: 5 }}
|
|
||||||
/>
|
|
||||||
<SettingHelpTextRow>
|
<SettingHelpTextRow>
|
||||||
<SettingHelpText>{t('settings.provider.aws-bedrock.access_key_id_help')}</SettingHelpText>
|
<SettingHelpText>{t('settings.provider.aws-bedrock.auth_type_help')}</SettingHelpText>
|
||||||
</SettingHelpTextRow>
|
</SettingHelpTextRow>
|
||||||
|
|
||||||
<SettingSubtitle style={{ marginTop: 5 }}>{t('settings.provider.aws-bedrock.secret_access_key')}</SettingSubtitle>
|
{/* IAM Credentials Fields */}
|
||||||
<Input.Password
|
{authType === 'iam' && (
|
||||||
value={localSecretAccessKey}
|
<>
|
||||||
placeholder="Secret Access Key"
|
<SettingSubtitle style={{ marginTop: 15 }}>
|
||||||
onChange={(e) => setLocalSecretAccessKey(e.target.value)}
|
{t('settings.provider.aws-bedrock.access_key_id')}
|
||||||
onBlur={() => setSecretAccessKey(localSecretAccessKey)}
|
</SettingSubtitle>
|
||||||
style={{ marginTop: 5 }}
|
<Input
|
||||||
spellCheck={false}
|
value={localAccessKeyId}
|
||||||
/>
|
placeholder="Access Key ID"
|
||||||
{apiKeyWebsite && (
|
onChange={(e) => setLocalAccessKeyId(e.target.value)}
|
||||||
<SettingHelpTextRow style={{ justifyContent: 'space-between' }}>
|
onBlur={() => setAccessKeyId(localAccessKeyId)}
|
||||||
<RowFlex>
|
style={{ marginTop: 5 }}
|
||||||
<SettingHelpLink target="_blank" href={apiKeyWebsite}>
|
/>
|
||||||
{t('settings.provider.get_api_key')}
|
<SettingHelpTextRow>
|
||||||
</SettingHelpLink>
|
<SettingHelpText>{t('settings.provider.aws-bedrock.access_key_id_help')}</SettingHelpText>
|
||||||
</RowFlex>
|
</SettingHelpTextRow>
|
||||||
<SettingHelpText>{t('settings.provider.aws-bedrock.secret_access_key_help')}</SettingHelpText>
|
|
||||||
</SettingHelpTextRow>
|
<SettingSubtitle style={{ marginTop: 15 }}>
|
||||||
|
{t('settings.provider.aws-bedrock.secret_access_key')}
|
||||||
|
</SettingSubtitle>
|
||||||
|
<Input.Password
|
||||||
|
value={localSecretAccessKey}
|
||||||
|
placeholder="Secret Access Key"
|
||||||
|
onChange={(e) => setLocalSecretAccessKey(e.target.value)}
|
||||||
|
onBlur={() => setSecretAccessKey(localSecretAccessKey)}
|
||||||
|
style={{ marginTop: 5 }}
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
{apiKeyWebsite && (
|
||||||
|
<SettingHelpTextRow style={{ justifyContent: 'space-between' }}>
|
||||||
|
<RowFlex>
|
||||||
|
<SettingHelpLink target="_blank" href={apiKeyWebsite}>
|
||||||
|
{t('settings.provider.get_api_key')}
|
||||||
|
</SettingHelpLink>
|
||||||
|
</RowFlex>
|
||||||
|
<SettingHelpText>{t('settings.provider.aws-bedrock.secret_access_key_help')}</SettingHelpText>
|
||||||
|
</SettingHelpTextRow>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<SettingSubtitle style={{ marginTop: 5 }}>{t('settings.provider.aws-bedrock.region')}</SettingSubtitle>
|
{authType === 'apiKey' && (
|
||||||
|
<>
|
||||||
|
<SettingSubtitle style={{ marginTop: 15 }}>{t('settings.provider.aws-bedrock.api_key')}</SettingSubtitle>
|
||||||
|
<Input.Password
|
||||||
|
value={localApiKey}
|
||||||
|
placeholder="Bedrock API Key"
|
||||||
|
onChange={(e) => setLocalApiKey(e.target.value)}
|
||||||
|
onBlur={() => setApiKey(localApiKey)}
|
||||||
|
style={{ marginTop: 5 }}
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
<SettingHelpTextRow>
|
||||||
|
<SettingHelpText>{t('settings.provider.aws-bedrock.api_key_help')}</SettingHelpText>
|
||||||
|
</SettingHelpTextRow>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<SettingSubtitle style={{ marginTop: 15 }}>{t('settings.provider.aws-bedrock.region')}</SettingSubtitle>
|
||||||
<Input
|
<Input
|
||||||
value={localRegion}
|
value={localRegion}
|
||||||
placeholder="us-east-1"
|
placeholder="us-east-1"
|
||||||
|
|||||||
@ -131,10 +131,11 @@ const PopupContainer: React.FC<Props> = ({ providerId, resolve }) => {
|
|||||||
(model: Model) => {
|
(model: Model) => {
|
||||||
if (!isEmpty(model.name)) {
|
if (!isEmpty(model.name)) {
|
||||||
if (isNewApiProvider(provider)) {
|
if (isNewApiProvider(provider)) {
|
||||||
if (model.supported_endpoint_types && model.supported_endpoint_types.length > 0) {
|
const endpointTypes = model.supported_endpoint_types
|
||||||
|
if (endpointTypes && endpointTypes.length > 0) {
|
||||||
addModel({
|
addModel({
|
||||||
...model,
|
...model,
|
||||||
endpoint_type: model.supported_endpoint_types[0],
|
endpoint_type: endpointTypes.includes('image-generation') ? 'image-generation' : endpointTypes[0],
|
||||||
supported_text_delta: !isNotSupportedTextDelta(model)
|
supported_text_delta: !isNotSupportedTextDelta(model)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -232,8 +232,10 @@ vi.mock('@renderer/store/llm.ts', () => {
|
|||||||
location: ''
|
location: ''
|
||||||
},
|
},
|
||||||
awsBedrock: {
|
awsBedrock: {
|
||||||
|
authType: 'iam',
|
||||||
accessKeyId: '',
|
accessKeyId: '',
|
||||||
secretAccessKey: '',
|
secretAccessKey: '',
|
||||||
|
apiKey: '',
|
||||||
region: ''
|
region: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,7 +71,7 @@ const persistedReducer = persistReducer(
|
|||||||
{
|
{
|
||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 169,
|
version: 171,
|
||||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'],
|
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'],
|
||||||
migrate
|
migrate
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { createSlice } from '@reduxjs/toolkit'
|
|||||||
import { isLocalAi } from '@renderer/config/env'
|
import { isLocalAi } from '@renderer/config/env'
|
||||||
import { SYSTEM_MODELS } from '@renderer/config/models'
|
import { SYSTEM_MODELS } from '@renderer/config/models'
|
||||||
import { SYSTEM_PROVIDERS } from '@renderer/config/providers'
|
import { SYSTEM_PROVIDERS } from '@renderer/config/providers'
|
||||||
import type { Model, Provider } from '@renderer/types'
|
import type { AwsBedrockAuthType, Model, Provider } from '@renderer/types'
|
||||||
import { uniqBy } from 'lodash'
|
import { uniqBy } from 'lodash'
|
||||||
|
|
||||||
type LlmSettings = {
|
type LlmSettings = {
|
||||||
@ -25,8 +25,10 @@ type LlmSettings = {
|
|||||||
location: string
|
location: string
|
||||||
}
|
}
|
||||||
awsBedrock: {
|
awsBedrock: {
|
||||||
|
authType: AwsBedrockAuthType
|
||||||
accessKeyId: string
|
accessKeyId: string
|
||||||
secretAccessKey: string
|
secretAccessKey: string
|
||||||
|
apiKey: string
|
||||||
region: string
|
region: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,8 +70,10 @@ export const initialState: LlmState = {
|
|||||||
location: ''
|
location: ''
|
||||||
},
|
},
|
||||||
awsBedrock: {
|
awsBedrock: {
|
||||||
|
authType: 'iam',
|
||||||
accessKeyId: '',
|
accessKeyId: '',
|
||||||
secretAccessKey: '',
|
secretAccessKey: '',
|
||||||
|
apiKey: '',
|
||||||
region: ''
|
region: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,12 +201,18 @@ const llmSlice = createSlice({
|
|||||||
setVertexAIServiceAccountClientEmail: (state, action: PayloadAction<string>) => {
|
setVertexAIServiceAccountClientEmail: (state, action: PayloadAction<string>) => {
|
||||||
state.settings.vertexai.serviceAccount.clientEmail = action.payload
|
state.settings.vertexai.serviceAccount.clientEmail = action.payload
|
||||||
},
|
},
|
||||||
|
setAwsBedrockAuthType: (state, action: PayloadAction<AwsBedrockAuthType>) => {
|
||||||
|
state.settings.awsBedrock.authType = action.payload
|
||||||
|
},
|
||||||
setAwsBedrockAccessKeyId: (state, action: PayloadAction<string>) => {
|
setAwsBedrockAccessKeyId: (state, action: PayloadAction<string>) => {
|
||||||
state.settings.awsBedrock.accessKeyId = action.payload
|
state.settings.awsBedrock.accessKeyId = action.payload
|
||||||
},
|
},
|
||||||
setAwsBedrockSecretAccessKey: (state, action: PayloadAction<string>) => {
|
setAwsBedrockSecretAccessKey: (state, action: PayloadAction<string>) => {
|
||||||
state.settings.awsBedrock.secretAccessKey = action.payload
|
state.settings.awsBedrock.secretAccessKey = action.payload
|
||||||
},
|
},
|
||||||
|
setAwsBedrockApiKey: (state, action: PayloadAction<string>) => {
|
||||||
|
state.settings.awsBedrock.apiKey = action.payload
|
||||||
|
},
|
||||||
setAwsBedrockRegion: (state, action: PayloadAction<string>) => {
|
setAwsBedrockRegion: (state, action: PayloadAction<string>) => {
|
||||||
state.settings.awsBedrock.region = action.payload
|
state.settings.awsBedrock.region = action.payload
|
||||||
},
|
},
|
||||||
@ -242,8 +252,10 @@ export const {
|
|||||||
setVertexAILocation,
|
setVertexAILocation,
|
||||||
setVertexAIServiceAccountPrivateKey,
|
setVertexAIServiceAccountPrivateKey,
|
||||||
setVertexAIServiceAccountClientEmail,
|
setVertexAIServiceAccountClientEmail,
|
||||||
|
setAwsBedrockAuthType,
|
||||||
setAwsBedrockAccessKeyId,
|
setAwsBedrockAccessKeyId,
|
||||||
setAwsBedrockSecretAccessKey,
|
setAwsBedrockSecretAccessKey,
|
||||||
|
setAwsBedrockApiKey,
|
||||||
setAwsBedrockRegion,
|
setAwsBedrockRegion,
|
||||||
updateModel
|
updateModel
|
||||||
} = llmSlice.actions
|
} = llmSlice.actions
|
||||||
|
|||||||
@ -2788,6 +2788,40 @@ const migrateConfig = {
|
|||||||
logger.error('migrate 169 error', error as Error)
|
logger.error('migrate 169 error', error as Error)
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'170': (state: RootState) => {
|
||||||
|
try {
|
||||||
|
addProvider(state, 'sophnet')
|
||||||
|
state.llm.providers = moveProvider(state.llm.providers, 'sophnet', 17)
|
||||||
|
state.settings.defaultPaintingProvider = 'cherryin'
|
||||||
|
return state
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('migrate 170 error', error as Error)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'171': (state: RootState) => {
|
||||||
|
try {
|
||||||
|
// Ensure aws-bedrock provider exists
|
||||||
|
addProvider(state, 'aws-bedrock')
|
||||||
|
|
||||||
|
// Ensure awsBedrock settings exist and have all required fields
|
||||||
|
if (!state.llm.settings.awsBedrock) {
|
||||||
|
state.llm.settings.awsBedrock = llmInitialState.settings.awsBedrock
|
||||||
|
} else {
|
||||||
|
// For users who have awsBedrock but missing new fields (authType and apiKey)
|
||||||
|
if (!state.llm.settings.awsBedrock.authType) {
|
||||||
|
state.llm.settings.awsBedrock.authType = 'iam'
|
||||||
|
}
|
||||||
|
if (state.llm.settings.awsBedrock.apiKey === undefined) {
|
||||||
|
state.llm.settings.awsBedrock.apiKey = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('migrate 171 error', error as Error)
|
||||||
|
return state
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -397,7 +397,7 @@ export const initialState: SettingsState = {
|
|||||||
localBackupSyncInterval: 0,
|
localBackupSyncInterval: 0,
|
||||||
localBackupMaxBackups: 0,
|
localBackupMaxBackups: 0,
|
||||||
localBackupSkipBackupFile: false,
|
localBackupSkipBackupFile: false,
|
||||||
defaultPaintingProvider: 'zhipu',
|
defaultPaintingProvider: 'cherryin',
|
||||||
s3: {
|
s3: {
|
||||||
endpoint: '',
|
endpoint: '',
|
||||||
region: '',
|
region: '',
|
||||||
|
|||||||
@ -36,6 +36,7 @@ export type Assistant = {
|
|||||||
description?: string
|
description?: string
|
||||||
model?: Model
|
model?: Model
|
||||||
defaultModel?: Model
|
defaultModel?: Model
|
||||||
|
// This field should be considered as not Partial and not optional in v2
|
||||||
settings?: Partial<AssistantSettings>
|
settings?: Partial<AssistantSettings>
|
||||||
messages?: AssistantMessage[]
|
messages?: AssistantMessage[]
|
||||||
/** enableWebSearch 代表使用模型内置网络搜索功能 */
|
/** enableWebSearch 代表使用模型内置网络搜索功能 */
|
||||||
@ -280,7 +281,7 @@ export type PaintingParams = {
|
|||||||
providerId?: string
|
providerId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PaintingProvider = 'zhipu' | 'aihubmix' | 'silicon' | 'dmxapi' | 'new-api' | 'ovms'
|
export type PaintingProvider = 'zhipu' | 'aihubmix' | 'silicon' | 'dmxapi' | 'new-api' | 'ovms' | 'cherryin'
|
||||||
|
|
||||||
export interface Painting extends PaintingParams {
|
export interface Painting extends PaintingParams {
|
||||||
model?: string
|
model?: string
|
||||||
|
|||||||
@ -73,6 +73,17 @@ export function isServiceTier(tier: string): tier is ServiceTier {
|
|||||||
return isGroqServiceTier(tier) || isOpenAIServiceTier(tier)
|
return isGroqServiceTier(tier) || isOpenAIServiceTier(tier)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const AwsBedrockAuthTypes = {
|
||||||
|
iam: 'iam',
|
||||||
|
apiKey: 'apiKey'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type AwsBedrockAuthType = keyof typeof AwsBedrockAuthTypes
|
||||||
|
|
||||||
|
export function isAwsBedrockAuthType(type: string): type is AwsBedrockAuthType {
|
||||||
|
return Object.hasOwn(AwsBedrockAuthTypes, type)
|
||||||
|
}
|
||||||
|
|
||||||
export type Provider = {
|
export type Provider = {
|
||||||
id: string
|
id: string
|
||||||
type: ProviderType
|
type: ProviderType
|
||||||
@ -123,6 +134,7 @@ export const SystemProviderIds = {
|
|||||||
cephalon: 'cephalon',
|
cephalon: 'cephalon',
|
||||||
lanyun: 'lanyun',
|
lanyun: 'lanyun',
|
||||||
ph8: 'ph8',
|
ph8: 'ph8',
|
||||||
|
sophnet: 'sophnet',
|
||||||
openrouter: 'openrouter',
|
openrouter: 'openrouter',
|
||||||
ollama: 'ollama',
|
ollama: 'ollama',
|
||||||
ovms: 'ovms',
|
ovms: 'ovms',
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import type {
|
|||||||
GoogleGenAI,
|
GoogleGenAI,
|
||||||
Model as GeminiModel,
|
Model as GeminiModel,
|
||||||
SendMessageParameters,
|
SendMessageParameters,
|
||||||
ThinkingConfig,
|
|
||||||
Tool
|
Tool
|
||||||
} from '@google/genai'
|
} from '@google/genai'
|
||||||
|
|
||||||
@ -92,7 +91,10 @@ export type ReasoningEffortOptionalParams = {
|
|||||||
}
|
}
|
||||||
extra_body?: {
|
extra_body?: {
|
||||||
google?: {
|
google?: {
|
||||||
thinking_config: ThinkingConfig
|
thinking_config: {
|
||||||
|
thinking_budget: number
|
||||||
|
include_thoughts?: boolean
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add any other potential reasoning-related keys here if they exist
|
// Add any other potential reasoning-related keys here if they exist
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user