diff --git a/.github/workflows/auto-i18n.yml b/.github/workflows/auto-i18n.yml index a6c1e3791a..1a85b16757 100644 --- a/.github/workflows/auto-i18n.yml +++ b/.github/workflows/auto-i18n.yml @@ -27,9 +27,9 @@ jobs: ref: ${{ github.event.pull_request.head.ref }} - name: 📦 Setting Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 22 package-manager-cache: false - name: 📦 Install dependencies in isolated directory diff --git a/.github/workflows/github-issue-tracker.yml b/.github/workflows/github-issue-tracker.yml index 7cc1ad4762..32bd393145 100644 --- a/.github/workflows/github-issue-tracker.yml +++ b/.github/workflows/github-issue-tracker.yml @@ -5,7 +5,7 @@ on: types: [opened] schedule: # Run every day at 8:30 Beijing Time (00:30 UTC) - - cron: '30 0 * * *' + - cron: "30 0 * * *" workflow_dispatch: jobs: @@ -54,9 +54,9 @@ jobs: - name: Setup Node.js if: steps.check_time.outputs.should_delay == 'false' - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: 22 - name: Process issue with Claude if: steps.check_time.outputs.should_delay == 'false' @@ -121,9 +121,9 @@ jobs: uses: actions/checkout@v4 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: '20' + node-version: 22 - name: Process pending issues with Claude uses: anthropics/claude-code-action@main diff --git a/.github/workflows/issue-management.yml b/.github/workflows/issue-management.yml index f6041f2336..c9ff497386 100644 --- a/.github/workflows/issue-management.yml +++ b/.github/workflows/issue-management.yml @@ -21,7 +21,7 @@ jobs: contents: none steps: - name: Close needs-more-info issues - uses: actions/stale@v9 + uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} only-labels: 'needs-more-info' @@ -42,7 +42,7 @@ jobs: days-before-pr-close: -1 - name: Close inactive issues - uses: actions/stale@v9 + uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: ${{ env.daysBeforeStale }} diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 9e1608b13e..523a670064 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -3,7 +3,7 @@ name: Nightly Build on: workflow_dispatch: schedule: - - cron: '0 17 * * *' # 1:00 BJ Time + - cron: "0 17 * * *" # 1:00 BJ Time permissions: contents: write @@ -56,9 +56,9 @@ jobs: ref: main - name: Install Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 22 - name: macos-latest dependencies fix if: matrix.os == 'macos-latest' @@ -66,7 +66,7 @@ jobs: brew install python-setuptools - 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 id: yarn-cache-dir-path @@ -208,7 +208,7 @@ jobs: echo "总计: $(find renamed-artifacts -type f | wc -l) 个文件" - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: cherry-studio-nightly-${{ steps.date.outputs.date }}-${{ matrix.os }} path: renamed-artifacts/* diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index d992cc8c73..ebd871b3f3 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -24,12 +24,12 @@ jobs: uses: actions/checkout@v5 - name: Install Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 22 - 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 id: yarn-cache-dir-path diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c54504de07..8bbb46ee67 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,9 +4,9 @@ on: workflow_dispatch: inputs: tag: - description: 'Release tag (e.g. v1.0.0)' + description: "Release tag (e.g. v1.0.0)" required: true - default: 'v1.0.0' + default: "v1.0.0" push: tags: - v*.*.* @@ -47,9 +47,9 @@ jobs: npm version "$VERSION" --no-git-tag-version --allow-same-version - name: Install Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: - node-version: 20 + node-version: 22 - name: macos-latest dependencies fix if: matrix.os == 'macos-latest' @@ -57,7 +57,7 @@ jobs: brew install python-setuptools - 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 id: yarn-cache-dir-path @@ -127,5 +127,5 @@ jobs: allowUpdates: true makeLatest: false 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 }} diff --git a/docs/dev.md b/docs/dev.md index 0fdff640ec..fe67742768 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -18,13 +18,13 @@ yarn ### 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 ```bash corepack enable -corepack prepare yarn@4.6.0 --activate +corepack prepare yarn@4.9.1 --activate ``` ### Install Dependencies diff --git a/electron-builder.yml b/electron-builder.yml index 21aa1fe281..b2247e9b89 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -21,6 +21,8 @@ files: - "**/*" - "!**/{.vscode,.yarn,.yarn-lock,.github,.cursorrules,.prettierrc}" - "!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}" - "!**/{.env,.env.*,.npmrc,pnpm-lock.yaml}" - "!**/{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 - Translation: Enhanced translation script with concurrency and validation - 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: - GlobTool: Now counts lines instead of files in output for better clarity @@ -189,6 +194,9 @@ releaseInfo: - Fixed reranker API error response capture - Fixed right-click paste file content into inputbar - 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 v1.7.0-beta.3 新特性 @@ -219,6 +227,9 @@ releaseInfo: - MCP 确认:添加激活协议安装的 MCP 服务器时的确认模态框 - 翻译:增强翻译脚本的并发和验证功能 - Electron & Vite:更新至 Electron 38 和 Vite 4.0.1 + - 二维码生成:优化手机局域网导出性能 + - 企业设置:在关于设置中添加企业部分 + - 助手/Agent 弹窗:增强添加助手和 Agent 的界面 Claude Code 工具改进: - GlobTool:现在计算行数而不是文件数,提供更清晰的输出 @@ -246,4 +257,7 @@ releaseInfo: - 修复 reranker API 错误响应捕获 - 修复右键粘贴文件内容到输入栏 - 修复 aiCore 中的 minimax-m2 支持 + - 修复 Azure embedding 问题 + - 修复 Agent 编辑模态框加载竞态条件 + - 修复文件路径更新时防抖保存取消问题 diff --git a/package.json b/package.json index 0be9e752d7..2be66ace10 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "@libsql/client": "0.14.0", "@libsql/win32-x64-msvc": "^0.4.7", "@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch", + "@paymoapp/electron-shutdown-handler": "^1.1.2", "@strongtz/win32-arm64-msvc": "^0.4.7", "express": "^5.1.0", "font-list": "^2.0.0", @@ -116,9 +117,9 @@ "@ant-design/v5-patch-for-react-19": "^1.0.3", "@anthropic-ai/sdk": "^0.41.0", "@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch", - "@aws-sdk/client-bedrock": "^3.840.0", - "@aws-sdk/client-bedrock-runtime": "^3.840.0", - "@aws-sdk/client-s3": "^3.840.0", + "@aws-sdk/client-bedrock": "^3.910.0", + "@aws-sdk/client-bedrock-runtime": "^3.910.0", + "@aws-sdk/client-s3": "^3.910.0", "@biomejs/biome": "2.2.4", "@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18", "@cherrystudio/embedjs": "^0.1.31", @@ -376,6 +377,7 @@ "zod": "^4.1.5" }, "resolutions": { + "@smithy/types": "4.7.1", "@codemirror/language": "6.11.3", "@codemirror/lint": "6.8.5", "@codemirror/view": "6.38.1", diff --git a/src/main/index.ts b/src/main/index.ts index 2a4b0a022b..d2ea76bf8a 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -22,6 +22,7 @@ import { apiServerService } from './services/ApiServerService' import { appMenuService } from './services/AppMenuService' import mcpService from './services/MCPService' import { nodeTraceService } from './services/NodeTraceService' +import powerMonitorService from './services/PowerMonitorService' import { CHERRY_STUDIO_PROTOCOL, handleProtocolUrl, @@ -31,6 +32,7 @@ import { import selectionService, { initSelectionService } from './services/SelectionService' import { registerShortcuts } from './services/ShortcutService' import { TrayService } from './services/TrayService' +import { versionService } from './services/VersionService' import { windowService } from './services/WindowService' import { dataRefactorMigrateService } from './data/migrate/dataRefactor/DataRefactorMigrateService' import { dataApiService } from '@data/DataApiService' @@ -188,6 +190,10 @@ if (!app.requestSingleInstanceLock()) { /************FOR TESTING ONLY END****************/ + // Record current version for tracking + // A preparation for v2 data refactoring + versionService.recordCurrentVersion() + initWebviewHotkeys() // Set app user model id for windows electronApp.setAppUserModelId(import.meta.env.VITE_MAIN_BUNDLE_ID || 'com.kangfenmao.CherryStudio') @@ -206,6 +212,7 @@ if (!app.requestSingleInstanceLock()) { // Setup macOS application menu appMenuService?.setupApplicationMenu() nodeTraceService.init() + powerMonitorService.init() app.on('activate', function () { const mainWindow = windowService.getMainWindow() diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 67bca3db31..d73a626518 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -51,6 +51,7 @@ import * as NutstoreService from './services/NutstoreService' import ObsidianVaultService from './services/ObsidianVaultService' import { ocrService } from './services/ocr/OcrService' import OvmsManager from './services/OvmsManager' +import powerMonitorService from './services/PowerMonitorService' import { proxyManager } from './services/ProxyManager' import { pythonService } from './services/PythonService' import { FileServiceManager } from './services/remotefile/FileServiceManager' @@ -115,8 +116,17 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { const appUpdater = new AppUpdater() const notificationService = new NotificationService() - // Initialize Python service with main window - pythonService.setMainWindow(mainWindow) + // Register shutdown handlers + powerMonitorService.registerShutdownHandler(() => { + appUpdater.setAutoUpdate(false) + }) + + powerMonitorService.registerShutdownHandler(() => { + const mw = windowService.getMainWindow() + if (mw && !mw.isDestroyed()) { + mw.webContents.send(IpcChannel.App_SaveData) + } + }) const checkMainWindow = () => { if (!mainWindow || mainWindow.isDestroyed()) { diff --git a/src/main/services/PowerMonitorService.ts b/src/main/services/PowerMonitorService.ts new file mode 100644 index 0000000000..aab3906c9e --- /dev/null +++ b/src/main/services/PowerMonitorService.ts @@ -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 + +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 { + 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() diff --git a/src/main/services/PythonService.ts b/src/main/services/PythonService.ts index b4b2596831..e9f59fa3be 100644 --- a/src/main/services/PythonService.ts +++ b/src/main/services/PythonService.ts @@ -1,8 +1,9 @@ import { randomUUID } from 'node:crypto' -import type { BrowserWindow } from 'electron' import { ipcMain } from 'electron' +import { windowService } from './WindowService' + interface PythonExecutionRequest { id: string script: string @@ -21,7 +22,6 @@ interface PythonExecutionResponse { */ export class PythonService { private static instance: PythonService | null = null - private mainWindow: BrowserWindow | null = null private pendingRequests = new Map void; reject: (error: Error) => void }>() 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 */ @@ -63,8 +59,8 @@ export class PythonService { context: Record = {}, timeout: number = 60000 ): Promise { - if (!this.mainWindow) { - throw new Error('Main window not set in PythonService') + if (!windowService.getMainWindow()) { + throw new Error('Main window not found') } return new Promise((resolve, reject) => { @@ -95,7 +91,7 @@ export class PythonService { // Send request to renderer const request: PythonExecutionRequest = { id: requestId, script, context, timeout } - this.mainWindow?.webContents.send('python-execution-request', request) + windowService.getMainWindow()?.webContents.send('python-execution-request', request) }) } } diff --git a/src/main/services/VersionService.ts b/src/main/services/VersionService.ts new file mode 100644 index 0000000000..a853b99074 --- /dev/null +++ b/src/main/services/VersionService.ts @@ -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() diff --git a/src/renderer/src/aiCore/legacy/clients/aws/AwsBedrockAPIClient.ts b/src/renderer/src/aiCore/legacy/clients/aws/AwsBedrockAPIClient.ts index 94f74eecff..c4b0140579 100644 --- a/src/renderer/src/aiCore/legacy/clients/aws/AwsBedrockAPIClient.ts +++ b/src/renderer/src/aiCore/legacy/clients/aws/AwsBedrockAPIClient.ts @@ -1,6 +1,7 @@ import { BedrockClient, ListFoundationModelsCommand, ListInferenceProfilesCommand } from '@aws-sdk/client-bedrock' import { BedrockRuntimeClient, + type BedrockRuntimeClientConfig, ConverseCommand, InvokeModelCommand, InvokeModelWithResponseStreamCommand @@ -11,6 +12,8 @@ import { DEFAULT_MAX_TOKENS } from '@renderer/config/constant' import { findTokenLimit, isReasoningModel } from '@renderer/config/models' import { getAwsBedrockAccessKeyId, + getAwsBedrockApiKey, + getAwsBedrockAuthType, getAwsBedrockRegion, getAwsBedrockSecretAccessKey } from '@renderer/hooks/useAwsBedrock' @@ -75,32 +78,48 @@ export class AwsBedrockAPIClient extends BaseApiClient< } const region = getAwsBedrockRegion() - const accessKeyId = getAwsBedrockAccessKeyId() - const secretAccessKey = getAwsBedrockSecretAccessKey() + const authType = getAwsBedrockAuthType() 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) { - throw new Error('AWS credentials are required. Please configure AWS-Access-Key-ID and AWS-Secret-Access-Key.') + // Build client configuration based on auth type + 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({ - region, - credentials: { - accessKeyId, - secretAccessKey - } - }) - - const bedrockClient = new BedrockClient({ - region, - credentials: { - accessKeyId, - secretAccessKey - } - }) + const client = new BedrockRuntimeClient(clientConfig) + const bedrockClient = new BedrockClient(clientConfig) this.sdkInstance = { client, bedrockClient, region } return this.sdkInstance diff --git a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts index 239890c7a7..8ff25e356d 100644 --- a/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts +++ b/src/renderer/src/aiCore/legacy/clients/openai/OpenAIApiClient.ts @@ -192,7 +192,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient< extra_body: { google: { thinking_config: { - thinkingBudget: 0 + thinking_budget: 0 } } } @@ -327,8 +327,8 @@ export class OpenAIAPIClient extends OpenAIBaseClient< extra_body: { google: { thinking_config: { - thinkingBudget: -1, - includeThoughts: true + thinking_budget: -1, + include_thoughts: true } } } @@ -338,8 +338,8 @@ export class OpenAIAPIClient extends OpenAIBaseClient< extra_body: { google: { thinking_config: { - thinkingBudget: budgetTokens, - includeThoughts: true + thinking_budget: budgetTokens, + include_thoughts: true } } } @@ -670,7 +670,7 @@ export class OpenAIAPIClient extends OpenAIBaseClient< } else if (isClaudeReasoningModel(model) && reasoningEffort.thinking?.budget_tokens) { suffix = ` --thinking_budget ${reasoningEffort.thinking.budget_tokens}` } 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,因此会出问题 // 临时解决方案是强制poe用string content,但是其实poe部分支持array diff --git a/src/renderer/src/aiCore/prepareParams/modelCapabilities.ts b/src/renderer/src/aiCore/prepareParams/modelCapabilities.ts index 4a3c3f4bbf..b6e4b25843 100644 --- a/src/renderer/src/aiCore/prepareParams/modelCapabilities.ts +++ b/src/renderer/src/aiCore/prepareParams/modelCapabilities.ts @@ -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 +} + /** * 获取提供商特定的文件大小限制 */ diff --git a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts index d3fa1cb654..397c481cf3 100644 --- a/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts +++ b/src/renderer/src/aiCore/prepareParams/parameterBuilder.ts @@ -34,6 +34,7 @@ import { setupToolsConfig } from '../utils/mcp' import { buildProviderOptions } from '../utils/options' import { getAnthropicThinkingBudget } from '../utils/reasoning' import { buildProviderBuiltinWebSearchConfig } from '../utils/websearch' +import { supportsTopP } from './modelCapabilities' import { getTemperature, getTopP } from './modelParameters' const logger = loggerService.withContext('parameterBuilder') @@ -176,20 +177,27 @@ export async function buildStreamTextParams( messages: sdkMessages, maxOutputTokens: maxTokens, temperature: getTemperature(assistant, model), - topP: getTopP(assistant, model), abortSignal: options.requestOptions?.signal, headers: options.requestOptions?.headers, providerOptions, stopWhen: stepCountIs(20), maxRetries: 0 } + + if (supportsTopP(model)) { + params.topP = getTopP(assistant, model) + } + if (tools) { params.tools = tools } + if (assistant.prompt) { params.system = await replacePromptVariables(assistant.prompt, model.name) } + logger.debug('params', params) + return { params, modelId: model.id, diff --git a/src/renderer/src/aiCore/provider/__tests__/providerConfig.test.ts b/src/renderer/src/aiCore/provider/__tests__/providerConfig.test.ts index eb6e73c8ae..cc5f20c63e 100644 --- a/src/renderer/src/aiCore/provider/__tests__/providerConfig.test.ts +++ b/src/renderer/src/aiCore/provider/__tests__/providerConfig.test.ts @@ -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 { formatApiHost } from '@renderer/utils/api' import { COPILOT_DEFAULT_HEADERS, COPILOT_EDITOR_VERSION, isCopilotResponsesModel } from '../constants' -import { providerToAiSdkConfig } from '../providerConfig' +import { getActualProvider, providerToAiSdkConfig } from '../providerConfig' const createWindowKeyv = () => { const store = new Map() @@ -46,11 +80,21 @@ const createCopilotProvider = (): Provider => ({ isSystem: true }) -const createModel = (id: string, name = id): Model => ({ +const createModel = (id: string, name = id, provider = 'copilot'): Model => ({ id, name, - provider: 'copilot', - group: 'copilot' + provider, + 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', () => { @@ -87,3 +131,67 @@ describe('Copilot responses routing', () => { 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('') + }) +}) diff --git a/src/renderer/src/aiCore/provider/config/aihubmix.ts b/src/renderer/src/aiCore/provider/config/aihubmix.ts index 432be5ee31..8feed89909 100644 --- a/src/renderer/src/aiCore/provider/config/aihubmix.ts +++ b/src/renderer/src/aiCore/provider/config/aihubmix.ts @@ -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) diff --git a/src/renderer/src/aiCore/provider/providerConfig.ts b/src/renderer/src/aiCore/provider/providerConfig.ts index 4d63e73c67..4dd146e08b 100644 --- a/src/renderer/src/aiCore/provider/providerConfig.ts +++ b/src/renderer/src/aiCore/provider/providerConfig.ts @@ -10,11 +10,14 @@ import { isOpenAIChatCompletionOnlyModel } from '@renderer/config/models' import { isAnthropicProvider, isAzureOpenAIProvider, + isCherryAIProvider, isGeminiProvider, isNewApiProvider } from '@renderer/config/providers' import { getAwsBedrockAccessKeyId, + getAwsBedrockApiKey, + getAwsBedrockAuthType, getAwsBedrockRegion, getAwsBedrockSecretAccessKey } from '@renderer/hooks/useAwsBedrock' @@ -99,6 +102,8 @@ function formatProviderApiHost(provider: Provider): Provider { formatted.apiHost = formatAzureOpenAIApiHost(formatted.apiHost) } else if (isVertexProvider(formatted)) { formatted.apiHost = formatVertexApiHost(formatted) + } else if (isCherryAIProvider(formatted)) { + formatted.apiHost = formatApiHost(formatted.apiHost, false) } else { formatted.apiHost = formatApiHost(formatted.apiHost) } @@ -193,9 +198,15 @@ export function providerToAiSdkConfig( // bedrock if (aiSdkProviderId === 'bedrock') { + const authType = getAwsBedrockAuthType() extraOptions.region = getAwsBedrockRegion() - extraOptions.accessKeyId = getAwsBedrockAccessKeyId() - extraOptions.secretAccessKey = getAwsBedrockSecretAccessKey() + + if (authType === 'apiKey') { + extraOptions.apiKey = getAwsBedrockApiKey() + } else { + extraOptions.accessKeyId = getAwsBedrockAccessKeyId() + extraOptions.secretAccessKey = getAwsBedrockSecretAccessKey() + } } // google-vertex if (aiSdkProviderId === 'google-vertex' || aiSdkProviderId === 'google-vertex-anthropic') { diff --git a/src/renderer/src/aiCore/utils/options.ts b/src/renderer/src/aiCore/utils/options.ts index eaf4764c70..60d9b1e098 100644 --- a/src/renderer/src/aiCore/utils/options.ts +++ b/src/renderer/src/aiCore/utils/options.ts @@ -17,6 +17,7 @@ import { getAiSdkProviderId } from '../provider/factory' import { buildGeminiGenerateImageParams } from './image' import { getAnthropicReasoningParams, + getBedrockReasoningParams, getCustomParameters, getGeminiReasoningParams, getOpenAIReasoningParams, @@ -127,6 +128,9 @@ export function buildProviderOptions( case 'google-vertex-anthropic': providerSpecificOptions = buildAnthropicProviderOptions(assistant, model, capabilities) break + case 'bedrock': + providerSpecificOptions = buildBedrockProviderOptions(assistant, model, capabilities) + break default: // 对于其他 provider,使用通用的构建逻辑 providerSpecificOptions = { @@ -266,6 +270,32 @@ function buildXAIProviderOptions( return providerOptions } +/** + * Build Bedrock providerOptions + */ +function buildBedrockProviderOptions( + assistant: Assistant, + model: Model, + capabilities: { + enableReasoning: boolean + enableWebSearch: boolean + enableGenerateImage: boolean + } +): Record { + const { enableReasoning } = capabilities + let providerOptions: Record = {} + + if (enableReasoning) { + const reasoningParams = getBedrockReasoningParams(assistant, model) + providerOptions = { + ...providerOptions, + ...reasoningParams + } + } + + return providerOptions +} + /** * 构建通用的 providerOptions(用于其他 provider) */ diff --git a/src/renderer/src/aiCore/utils/reasoning.ts b/src/renderer/src/aiCore/utils/reasoning.ts index 0246ac31cb..3a36fb658a 100644 --- a/src/renderer/src/aiCore/utils/reasoning.ts +++ b/src/renderer/src/aiCore/utils/reasoning.ts @@ -98,7 +98,7 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin extra_body: { google: { thinking_config: { - thinkingBudget: 0 + thinking_budget: 0 } } } @@ -259,8 +259,8 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin extra_body: { google: { thinking_config: { - thinkingBudget: -1, - includeThoughts: true + thinking_budget: -1, + include_thoughts: true } } } @@ -270,8 +270,8 @@ export function getReasoningEffort(assistant: Assistant, model: Model): Reasonin extra_body: { google: { thinking_config: { - thinkingBudget: budgetTokens, - includeThoughts: true + thinking_budget: budgetTokens ?? -1, + include_thoughts: true } } } @@ -431,8 +431,8 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re if (reasoningEffort === undefined) { return { thinkingConfig: { - includeThoughts: false, - ...(GEMINI_FLASH_MODEL_REGEX.test(model.id) ? { thinkingBudget: 0 } : {}) + include_thoughts: false, + ...(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) { return { thinkingConfig: { - includeThoughts: true + include_thoughts: true } } } @@ -452,8 +452,8 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re return { thinkingConfig: { - ...(budget > 0 ? { thinkingBudget: budget } : {}), - includeThoughts: true + ...(budget > 0 ? { thinking_budget: budget } : {}), + 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 { + 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 设置中提取自定义参数 diff --git a/src/renderer/src/assets/images/providers/sophnet.svg b/src/renderer/src/assets/images/providers/sophnet.svg new file mode 100644 index 0000000000..aae1e03239 --- /dev/null +++ b/src/renderer/src/assets/images/providers/sophnet.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/renderer/src/components/QuickPanel/types.ts b/src/renderer/src/components/QuickPanel/types.ts index 812ec153a6..519180c5b7 100644 --- a/src/renderer/src/components/QuickPanel/types.ts +++ b/src/renderer/src/components/QuickPanel/types.ts @@ -64,6 +64,7 @@ export type QuickPanelListItem = { isSelected?: boolean isMenu?: boolean disabled?: boolean + hidden?: boolean /** * 固定显示项:不参与过滤,始终出现在列表顶部。 * 例如“清除”按钮可设置为 alwaysVisible,从而在有匹配项时始终可见; diff --git a/src/renderer/src/components/QuickPanel/view.tsx b/src/renderer/src/components/QuickPanel/view.tsx index 297f2c55f5..08e485c355 100644 --- a/src/renderer/src/components/QuickPanel/view.tsx +++ b/src/renderer/src/components/QuickPanel/view.tsx @@ -143,7 +143,8 @@ export const QuickPanelView: React.FC = ({ setInputText }) => { prevSymbolRef.current = ctx.symbol // 固定项置顶 + 过滤后的普通项 - return [...pinnedItems, ...filteredNormalItems] + const pinnedFiltered = [...pinnedItems, ...filteredNormalItems] + return pinnedFiltered.filter((item) => !item.hidden) }, [ctx.isVisible, ctx.symbol, ctx.list, searchText]) const canForwardAndBackward = useMemo(() => { diff --git a/src/renderer/src/config/models/default.ts b/src/renderer/src/config/models/default.ts index 5b242f297d..a56ea7f6cb 100644 --- a/src/renderer/src/config/models/default.ts +++ b/src/renderer/src/config/models/default.ts @@ -27,6 +27,7 @@ export const SYSTEM_MODELS: Record = ], cherryin: [], vertexai: [], + sophnet: [], '302ai': [ { id: 'deepseek-chat', diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index 4c25dfff99..5fbae73dbf 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -46,6 +46,7 @@ import Ph8ProviderLogo from '@renderer/assets/images/providers/ph8.png' import PPIOProviderLogo from '@renderer/assets/images/providers/ppio.png' import QiniuProviderLogo from '@renderer/assets/images/providers/qiniu.webp' 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 TencentCloudProviderLogo from '@renderer/assets/images/providers/tencent-cloud-ti.png' import TogetherProviderLogo from '@renderer/assets/images/providers/together.png' @@ -246,6 +247,16 @@ export const SYSTEM_PROVIDERS_CONFIG: Record = isSystem: true, 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: { id: 'ppio', name: 'PPIO', @@ -729,7 +740,8 @@ export const PROVIDER_LOGO_MAP: AtLeast = { poe: 'poe', // use svg icon component aionly: AiOnlyProviderLogo, longcat: LongCatProviderLogo, - huggingface: HuggingfaceProviderLogo + huggingface: HuggingfaceProviderLogo, + sophnet: SophnetProviderLogo } as const export function getProviderLogo(providerId: string) { @@ -808,6 +820,17 @@ export const PROVIDER_URLS: Record = { 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: { api: { 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' } +export function isCherryAIProvider(provider: Provider): boolean { + return provider.id === 'cherryai' +} + /** * 判断是否为 OpenAI 兼容的提供商 * @param {Provider} provider 提供商对象 diff --git a/src/renderer/src/hooks/useAwsBedrock.ts b/src/renderer/src/hooks/useAwsBedrock.ts index e84608a3bb..619eedd4ea 100644 --- a/src/renderer/src/hooks/useAwsBedrock.ts +++ b/src/renderer/src/hooks/useAwsBedrock.ts @@ -1,5 +1,12 @@ 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' export function useAwsBedrockSettings() { @@ -8,8 +15,10 @@ export function useAwsBedrockSettings() { return { ...settings, + setAuthType: (authType: AwsBedrockAuthType) => dispatch(setAwsBedrockAuthType(authType)), setAccessKeyId: (accessKeyId: string) => dispatch(setAwsBedrockAccessKeyId(accessKeyId)), setSecretAccessKey: (secretAccessKey: string) => dispatch(setAwsBedrockSecretAccessKey(secretAccessKey)), + setApiKey: (apiKey: string) => dispatch(setAwsBedrockApiKey(apiKey)), setRegion: (region: string) => dispatch(setAwsBedrockRegion(region)) } } @@ -18,6 +27,10 @@ export function getAwsBedrockSettings() { return store.getState().llm.settings.awsBedrock } +export function getAwsBedrockAuthType() { + return store.getState().llm.settings.awsBedrock.authType +} + export function getAwsBedrockAccessKeyId() { return store.getState().llm.settings.awsBedrock.accessKeyId } @@ -26,6 +39,10 @@ export function getAwsBedrockSecretAccessKey() { return store.getState().llm.settings.awsBedrock.secretAccessKey } +export function getAwsBedrockApiKey() { + return store.getState().llm.settings.awsBedrock.apiKey +} + export function getAwsBedrockRegion() { return store.getState().llm.settings.awsBedrock.region } diff --git a/src/renderer/src/i18n/label.ts b/src/renderer/src/i18n/label.ts index 3736437fc8..f8806359d4 100644 --- a/src/renderer/src/i18n/label.ts +++ b/src/renderer/src/i18n/label.ts @@ -85,7 +85,8 @@ const providerKeyMap = { poe: 'provider.poe', aionly: 'provider.aionly', longcat: 'provider.longcat', - huggingface: 'provider.huggingface' + huggingface: 'provider.huggingface', + sophnet: 'provider.sophnet' } as const /** @@ -238,7 +239,7 @@ const paintingsImageSizeOptionsKeyMap = { } as const export const getPaintingsImageSizeOptionsLabel = (key: string): string => { - return getLabel(paintingsImageSizeOptionsKeyMap, key) + return paintingsImageSizeOptionsKeyMap[key] ? getLabel(paintingsImageSizeOptionsKeyMap, key) : key } const paintingsQualityOptionsKeyMap = { diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index b3b89851d7..36ce60edb6 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -2482,6 +2482,7 @@ "qiniu": "Qiniu AI", "qwenlm": "QwenLM", "silicon": "SiliconFlow", + "sophnet": "SophNet", "stepfun": "StepFun", "tencent-cloud-ti": "Tencent Cloud TI", "together": "Together", @@ -4259,6 +4260,12 @@ "aws-bedrock": { "access_key_id": "AWS Access Key ID", "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", "region": "AWS Region", "region_help": "Your AWS service region, e.g., us-east-1", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 8f91651bac..0acb816a1c 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -2482,6 +2482,7 @@ "qiniu": "七牛云 AI 推理", "qwenlm": "QwenLM", "silicon": "硅基流动", + "sophnet": "SophNet", "stepfun": "阶跃星辰", "tencent-cloud-ti": "腾讯云 TI", "together": "Together", @@ -4259,6 +4260,12 @@ "aws-bedrock": { "access_key_id": "AWS 访问密钥 ID", "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 是亚马逊提供的全托管基础模型服务,支持多种先进的大语言模型", "region": "AWS 区域", "region_help": "您的 AWS 服务区域,例如 us-east-1", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index f89802ee23..913a4de5ad 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -2482,6 +2482,7 @@ "qiniu": "七牛雲 AI 推理", "qwenlm": "QwenLM", "silicon": "SiliconFlow", + "sophnet": "SophNet", "stepfun": "StepFun", "tencent-cloud-ti": "騰訊雲 TI", "together": "Together", @@ -4259,6 +4260,12 @@ "aws-bedrock": { "access_key_id": "AWS 存取密鑰 ID", "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 是亞馬遜提供的全托管基础模型服務,支持多種先進的大語言模型", "region": "AWS 區域", "region_help": "您的 AWS 服務區域,例如 us-east-1", diff --git a/src/renderer/src/i18n/translate/de-de.json b/src/renderer/src/i18n/translate/de-de.json index 78eb594c2b..b90addff79 100644 --- a/src/renderer/src/i18n/translate/de-de.json +++ b/src/renderer/src/i18n/translate/de-de.json @@ -1,7 +1,7 @@ { "agent": { "add": { - "description": "[to be translated]:Handle complex tasks with various tools", + "description": "Bewältigen Sie komplexe Aufgaben mit verschiedenen Werkzeugen", "error": { "failed": "Agent hinzufügen fehlgeschlagen", "invalid_agent": "Ungültiger Agent" @@ -548,11 +548,11 @@ "chat": { "add": { "assistant": { - "description": "[to be translated]:Daily conversations and quick Q&A", + "description": "Tägliche Gespräche und schnelle Fragen & Antworten", "title": "Assistent hinzufügen" }, "option": { - "title": "[to be translated]:Select Type" + "title": "Typ auswählen" }, "topic": { "title": "Neues Thema erstellen" @@ -2476,12 +2476,13 @@ "openrouter": "OpenRouter", "ovms": "Intel OVMS", "perplexity": "Perplexity", - "ph8": "PH8 Großmodell-Plattform", + "ph8": "PH8", "poe": "Poe", "ppio": "PPIO Cloud", "qiniu": "Qiniu Cloud KI-Inferenz", "qwenlm": "QwenLM", "silicon": "SiliconFlow", + "sophnet": "SophNet", "stepfun": "StepFun", "tencent-cloud-ti": "Tencent Cloud TI", "together": "Together", @@ -4259,6 +4260,12 @@ "aws-bedrock": { "access_key_id": "AWS-Zugriffsschlüssel-ID", "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", "region": "AWS-Region", "region_help": "Ihre AWS-Serviceregion, z.B. us-east-1", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 7d326346ca..4c6a0b4b20 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -1,7 +1,7 @@ { "agent": { "add": { - "description": "[to be translated]:Handle complex tasks with various tools", + "description": "Χειριστείτε πολύπλοκες εργασίες με διάφορα εργαλεία", "error": { "failed": "Αποτυχία προσθήκης πράκτορα", "invalid_agent": "Μη έγκυρος Agent" @@ -548,11 +548,11 @@ "chat": { "add": { "assistant": { - "description": "[to be translated]:Daily conversations and quick Q&A", + "description": "Καθημερινές συνομιλίες και γρήγορες ερωταπαντήσεις", "title": "Προσθήκη βοηθού" }, "option": { - "title": "[to be translated]:Select Type" + "title": "Επιλέξτε Τύπο" }, "topic": { "title": "Δημιουργία νέου θέματος" @@ -2476,12 +2476,13 @@ "openrouter": "OpenRouter", "ovms": "Intel OVMS", "perplexity": "Perplexity", - "ph8": "Πλατφόρμα Ανοιχτής Μεγάλης Μοντέλου PH8", + "ph8": "PH8", "poe": "Poe", "ppio": "PPIO Piao Yun", "qiniu": "Qiniu AI", "qwenlm": "QwenLM", "silicon": "Σιδηρική Παρουσία", + "sophnet": "SophNet", "stepfun": "Βήμα Ουράς", "tencent-cloud-ti": "Tencent Cloud TI", "together": "Together", @@ -4259,6 +4260,12 @@ "aws-bedrock": { "access_key_id": "Αναγνωριστικό κλειδιού πρόσβασης AWS", "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 και υποστηρίζει διάφορα προηγμένα μεγάλα γλωσσικά μοντέλα.", "region": "Περιοχές AWS", "region_help": "Η περιοχή υπηρεσίας AWS σας, για παράδειγμα us-east-1", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index b8018d8219..cf1b029db3 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -1,7 +1,7 @@ { "agent": { "add": { - "description": "[to be translated]:Handle complex tasks with various tools", + "description": "Maneja tareas complejas con varias herramientas", "error": { "failed": "Error al añadir agente", "invalid_agent": "Agent inválido" @@ -548,11 +548,11 @@ "chat": { "add": { "assistant": { - "description": "[to be translated]:Daily conversations and quick Q&A", + "description": "Conversaciones diarias y preguntas y respuestas rápidas", "title": "Agregar asistente" }, "option": { - "title": "[to be translated]:Select Type" + "title": "Seleccionar Tipo" }, "topic": { "title": "Crear nuevo tema" @@ -2476,12 +2476,13 @@ "openrouter": "OpenRouter", "ovms": "Intel OVMS", "perplexity": "Perplejidad", - "ph8": "Plataforma Abierta de Grandes Modelos PH8", + "ph8": "PH8", "poe": "Poe", "ppio": "PPIO Cloud Piao", "qiniu": "Qiniu AI", "qwenlm": "QwenLM", "silicon": "Silicio Fluido", + "sophnet": "SophNet", "stepfun": "Función Salto", "tencent-cloud-ti": "Tencent Nube TI", "together": "Juntos", @@ -4259,6 +4260,12 @@ "aws-bedrock": { "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", + "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.", "region": "Región de AWS", "region_help": "Su región de servicio AWS, por ejemplo us-east-1", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index 60eada88f3..c1c699afaf 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -1,7 +1,7 @@ { "agent": { "add": { - "description": "[to be translated]:Handle complex tasks with various tools", + "description": "Gérez des tâches complexes avec divers outils", "error": { "failed": "Échec de l'ajout de l'agent", "invalid_agent": "Agent invalide" @@ -548,11 +548,11 @@ "chat": { "add": { "assistant": { - "description": "[to be translated]:Daily conversations and quick Q&A", + "description": "Conversations quotidiennes et Q&R rapides", "title": "Ajouter un assistant" }, "option": { - "title": "[to be translated]:Select Type" + "title": "Sélectionner le type" }, "topic": { "title": "Nouveau sujet" @@ -2476,12 +2476,13 @@ "openrouter": "OpenRouter", "ovms": "Intel OVMS", "perplexity": "Perplexité", - "ph8": "Plateforme ouverte de grands modèles PH8", + "ph8": "PH8", "poe": "Poe", "ppio": "PPIO Cloud Piou", "qiniu": "Qiniu AI", "qwenlm": "QwenLM", "silicon": "Silicium Fluide", + "sophnet": "SophNet", "stepfun": "Échelon Étoile", "tencent-cloud-ti": "Tencent Cloud TI", "together": "Ensemble", @@ -4259,6 +4260,12 @@ "aws-bedrock": { "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", + "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.", "region": "Région AWS", "region_help": "Votre région de service AWS, par exemple us-east-1", diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index f6b7cbf98e..b12587ecf4 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -1,7 +1,7 @@ { "agent": { "add": { - "description": "[to be translated]:Handle complex tasks with various tools", + "description": "さまざまなツールを使って複雑なタスクを処理する", "error": { "failed": "エージェントの追加に失敗しました", "invalid_agent": "無効なエージェント" @@ -548,11 +548,11 @@ "chat": { "add": { "assistant": { - "description": "[to be translated]:Daily conversations and quick Q&A", + "description": "日常会話と簡単なQ&A", "title": "アシスタントを追加" }, "option": { - "title": "[to be translated]:Select Type" + "title": "種類を選択" }, "topic": { "title": "新しいトピック" @@ -2482,6 +2482,7 @@ "qiniu": "七牛云 AI 推理", "qwenlm": "QwenLM", "silicon": "SiliconFlow", + "sophnet": "SophNet", "stepfun": "StepFun", "tencent-cloud-ti": "Tencent Cloud TI", "together": "Together", @@ -4259,6 +4260,12 @@ "aws-bedrock": { "access_key_id": "AWS アクセスキー ID", "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 が提供する完全に管理されたベースモデルサービスで、さまざまな最先端の大言語モデルをサポートしています", "region": "AWS リージョン", "region_help": "あなたの AWS サービスリージョン、例:us-east-1", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 8e8d993d70..7ad1184b15 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -1,7 +1,7 @@ { "agent": { "add": { - "description": "[to be translated]:Handle complex tasks with various tools", + "description": "Lide com tarefas complexas usando várias ferramentas", "error": { "failed": "Falha ao adicionar agente", "invalid_agent": "Agent inválido" @@ -548,11 +548,11 @@ "chat": { "add": { "assistant": { - "description": "[to be translated]:Daily conversations and quick Q&A", + "description": "Conversas diárias e perguntas e respostas rápidas", "title": "Adicionar assistente" }, "option": { - "title": "[to be translated]:Select Type" + "title": "Selecionar Tipo" }, "topic": { "title": "Novo Tópico" @@ -2476,12 +2476,13 @@ "openrouter": "OpenRouter", "ovms": "Intel OVMS", "perplexity": "Perplexidade", - "ph8": "Plataforma Aberta de Grandes Modelos PH8", + "ph8": "PH8", "poe": "Poe", "ppio": "PPIO Nuvem Piao", "qiniu": "Qiniu AI", "qwenlm": "QwenLM", "silicon": "Silício em Fluxo", + "sophnet": "SophNet", "stepfun": "Função de Passo Estelar", "tencent-cloud-ti": "Nuvem TI da Tencent", "together": "Juntos", @@ -4259,6 +4260,12 @@ "aws-bedrock": { "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", + "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.", "region": "Região da AWS", "region_help": "A sua região de serviço da AWS, por exemplo, us-east-1", diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index a0b1d52624..ecdc0ecef0 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -1,7 +1,7 @@ { "agent": { "add": { - "description": "[to be translated]:Handle complex tasks with various tools", + "description": "Справляйтесь со сложными задачами с помощью различных инструментов", "error": { "failed": "Не удалось добавить агента", "invalid_agent": "Недействительный агент" @@ -548,11 +548,11 @@ "chat": { "add": { "assistant": { - "description": "[to be translated]:Daily conversations and quick Q&A", + "description": "Ежедневные разговоры и быстрые вопросы и ответы", "title": "Добавить ассистента" }, "option": { - "title": "[to be translated]:Select Type" + "title": "Выберите тип" }, "topic": { "title": "Новый топик" @@ -2482,6 +2482,7 @@ "qiniu": "Qiniu AI", "qwenlm": "QwenLM", "silicon": "SiliconFlow", + "sophnet": "SophNet", "stepfun": "StepFun", "tencent-cloud-ti": "Tencent Cloud TI", "together": "Together", @@ -4259,6 +4260,12 @@ "aws-bedrock": { "access_key_id": "AWS Ключ доступа ID", "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 — это полное управляемое сервисное предложение для моделей, поддерживающее различные современные модели языка", "region": "AWS регион", "region_help": "Ваш регион AWS, например us-east-1", diff --git a/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx b/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx index b7c0cf6c3b..f3f90326c2 100644 --- a/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx +++ b/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx @@ -3,6 +3,7 @@ import type { DropResult } from '@hello-pangea/dnd' import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd' import { loggerService } from '@logger' import { ActionIconButton } from '@renderer/components/Buttons' +import { MdiLightbulbOn } from '@renderer/components/Icons' import type { QuickPanelListItem } from '@renderer/components/QuickPanel' import { isAnthropicModel, @@ -231,6 +232,15 @@ const InputbarTools = ({ quickPhrasesButtonRef.current?.openQuickPanel() } }, + { + label: t('assistants.settings.reasoning_effort.label'), + description: '', + icon: , + isMenu: true, + action: () => { + thinkingButtonRef.current?.openQuickPanel() + } + }, { label: t('assistants.presets.edit.model.select.title'), description: '', @@ -246,6 +256,7 @@ const InputbarTools = ({ icon: , isMenu: true, disabled: files.length > 0, + hidden: !showKnowledgeBaseButton, action: () => { knowledgeBaseButtonRef.current?.openQuickPanel() } @@ -313,7 +324,7 @@ const InputbarTools = ({ translate() } } - ] + ] satisfies QuickPanelListItem[] } const handleDragEnd = (result: DropResult) => { diff --git a/src/renderer/src/pages/home/Tabs/components/AddButton.tsx b/src/renderer/src/pages/home/Tabs/components/AddButton.tsx index 68488142d3..407fa7a271 100644 --- a/src/renderer/src/pages/home/Tabs/components/AddButton.tsx +++ b/src/renderer/src/pages/home/Tabs/components/AddButton.tsx @@ -1,16 +1,11 @@ import { Button, cn } from '@cherrystudio/ui' import { PlusIcon } from 'lucide-react' -import type { FC } from 'react' -interface Props extends React.ComponentProps { - children: React.ReactNode -} - -const AddButton: FC = ({ children, className, ...props }) => { +const AddButton = ({ children, className, ...props }) => { return (