diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 96c2e73aad..f886d01f8b 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -98,8 +98,11 @@ jobs: yarn build:linux env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} NODE_OPTIONS: --max-old-space-size=8192 + MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }} + MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }} + RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} + RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }} - name: Build Mac if: matrix.os == 'macos-latest' @@ -112,9 +115,12 @@ jobs: APPLE_ID: ${{ vars.APPLE_ID }} APPLE_APP_SPECIFIC_PASSWORD: ${{ vars.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }} - RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_OPTIONS: --max-old-space-size=8192 + MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }} + MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }} + RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} + RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }} - name: Build Windows if: matrix.os == 'windows-latest' @@ -123,8 +129,11 @@ jobs: yarn build:win env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} NODE_OPTIONS: --max-old-space-size=8192 + MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }} + MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }} + RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} + RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }} - name: Rename artifacts with nightly format shell: bash diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fa3aa91a19..41b915953c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -86,6 +86,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_OPTIONS: --max-old-space-size=8192 + MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }} MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }} RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }} @@ -104,6 +105,7 @@ jobs: APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_OPTIONS: --max-old-space-size=8192 + MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }} MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }} RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }} @@ -116,6 +118,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_OPTIONS: --max-old-space-size=8192 + MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }} MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }} RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }} diff --git a/.prettierignore b/.prettierignore index 4ff98b4519..5f6cea6dad 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,3 +7,4 @@ tsconfig.*.json CHANGELOG*.md agents.json src/renderer/src/integration/nutstore/sso/lib +src/main/integration/cherryin/index.js diff --git a/eslint.config.mjs b/eslint.config.mjs index abaadac841..133025b1fd 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -122,7 +122,8 @@ export default defineConfig([ '.yarn/**', '.gitignore', 'scripts/cloudflare-worker.js', - 'src/main/integration/nutstore/sso/lib/**' + 'src/main/integration/nutstore/sso/lib/**', + 'src/main/integration/cherryin/index.js' ] } ]) diff --git a/package.json b/package.json index cc36229f26..517f914748 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "CherryStudio", - "version": "1.5.7-rc.2", + "version": "1.5.8", "private": true, "description": "A powerful AI assistant for producer.", "main": "./out/main/index.js", diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index f2b856ef1d..19df95332e 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -285,5 +285,8 @@ export enum IpcChannel { CodeTools_Run = 'code-tools:run', // OCR - OCR_ocr = 'ocr:ocr' + OCR_ocr = 'ocr:ocr', + + // Cherryin + Cherryin_GetSignature = 'cherryin:get-signature' } diff --git a/src/main/config.ts b/src/main/config.ts index 5a6f667d18..0cffcd1768 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -20,3 +20,5 @@ export const titleBarOverlayLight = { color: 'rgba(255,255,255,0)', symbolColor: '#000' } + +global.CHERRYIN_CLIENT_SECRET = import.meta.env.MAIN_VITE_CHERRYIN_CLIENT_SECRET diff --git a/src/main/integration/cherryin/index.js b/src/main/integration/cherryin/index.js new file mode 100644 index 0000000000..af185389eb --- /dev/null +++ b/src/main/integration/cherryin/index.js @@ -0,0 +1 @@ +var _0x6gg;const crypto=require("\u0063\u0072\u0079\u0070\u0074\u006F");_0x6gg='\u006D\u006F\u006C\u006A\u0065\u0065';var _0x111cbe;const CLIENT_ID="oiduts-yrrehc".split("").reverse().join("");_0x111cbe=(977158^977167)+(164595^164594);var _0x6d6adc=(756649^756650)+(497587^497587);const CLIENT_SECRET_SUFFIX="\u0047\u0076\u0049\u0036\u0049\u0035\u005A\u0072\u0045\u0048\u0063\u0047\u004F\u0057\u006A\u004F\u0035\u0041\u004B\u0068\u004A\u004B\u0047\u006D\u006E\u0077\u0077\u0047\u0066\u004D\u0036\u0032\u0058\u004B\u0070\u0057\u0071\u006B\u006A\u0068\u0076\u007A\u0052\u0055\u0032\u004E\u005A\u0049\u0069\u006E\u004D\u0037\u0037\u0061\u0054\u0047\u0049\u0071\u0068\u0071\u0079\u0073\u0030\u0067";_0x6d6adc=233169^233176;const CLIENT_SECRET=global['\u0043\u0048\u0045\u0052\u0052\u0059\u0049\u004E\u005F\u0043\u004C\u0049\u0045\u004E\u0054\u005F\u0053\u0045\u0043\u0052\u0045\u0054']+"\u002E"+CLIENT_SECRET_SUFFIX;class SignatureClient{constructor(clientId,clientSecret){this['\u0063\u006C\u0069\u0065\u006E\u0074\u0049\u0064']=clientId||CLIENT_ID;this['\u0063\u006C\u0069\u0065\u006E\u0074\u0053\u0065\u0063\u0072\u0065\u0074']=clientSecret||CLIENT_SECRET;this['\u0067\u0065\u006E\u0065\u0072\u0061\u0074\u0065\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065']=this['\u0067\u0065\u006E\u0065\u0072\u0061\u0074\u0065\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065']['\u0062\u0069\u006E\u0064'](this);}generateSignature(options){const{"method":method,"path":path,"query":query='',"body":body=''}=options;const timestamp=Math['\u0066\u006C\u006F\u006F\u0072'](Date['\u006E\u006F\u0077']()/(110765^111429))['\u0074\u006F\u0053\u0074\u0072\u0069\u006E\u0067']();var _0xe08cc=(212246^212244)+(773521^773523);let bodyString='';_0xe08cc=(606778^606776)+(962748^962740);if(body){if(typeof body==="\u006F\u0062\u006A\u0065\u0063\u0074"){bodyString=JSON['\u0073\u0074\u0072\u0069\u006E\u0067\u0069\u0066\u0079'](body);}else{bodyString=body['\u0074\u006F\u0053\u0074\u0072\u0069\u006E\u0067']();}}const signatureParts=[method['\u0074\u006F\u0055\u0070\u0070\u0065\u0072\u0043\u0061\u0073\u0065'](),path,query,this['\u0063\u006C\u0069\u0065\u006E\u0074\u0049\u0064'],timestamp,bodyString];var _0x5693g=(936664^936668)+(685268^685277);const signatureString=signatureParts['\u006A\u006F\u0069\u006E']("\u000A");_0x5693g=(266582^266576)+(337322^337315);const hmac=crypto['\u0063\u0072\u0065\u0061\u0074\u0065\u0048\u006D\u0061\u0063']("\u0073\u0068\u0061\u0032\u0035\u0036",this['\u0063\u006C\u0069\u0065\u006E\u0074\u0053\u0065\u0063\u0072\u0065\u0074']);hmac['\u0075\u0070\u0064\u0061\u0074\u0065'](signatureString);var _0x5fba=(354480^354481)+(537437^537434);const signature=hmac['\u0064\u0069\u0067\u0065\u0073\u0074']("\u0068\u0065\u0078");_0x5fba=(249614^249610)+(915906^915914);return{'X-Client-ID':this['\u0063\u006C\u0069\u0065\u006E\u0074\u0049\u0064'],'X-Timestamp':timestamp,'X-Signature':signature};}}const signatureClient=new SignatureClient();const generateSignature=signatureClient['\u0067\u0065\u006E\u0065\u0072\u0061\u0074\u0065\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065'];module['\u0065\u0078\u0070\u006F\u0072\u0074\u0073']={'\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065\u0043\u006C\u0069\u0065\u006E\u0074':SignatureClient,'\u0067\u0065\u006E\u0065\u0072\u0061\u0074\u0065\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065':generateSignature}; \ No newline at end of file diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 20ccf06d76..33d45531e0 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -4,6 +4,7 @@ import path from 'node:path' import { loggerService } from '@logger' import { isLinux, isMac, isPortable, isWin } from '@main/constant' +import { generateSignature } from '@main/integration/cherryin' import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process' import { handleZoomFactor } from '@main/utils/zoom' import { SpanEntity, TokenUsage } from '@mcp-trace/trace-core' @@ -714,4 +715,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { // OCR ipcMain.handle(IpcChannel.OCR_ocr, (_, ...args: Parameters) => ocrService.ocr(...args)) + + // CherryIN + ipcMain.handle(IpcChannel.Cherryin_GetSignature, (_, params) => generateSignature(params)) } diff --git a/src/preload/index.ts b/src/preload/index.ts index 49c40d1166..fb9e37c89e 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -415,6 +415,10 @@ const api = { ocr: { ocr: (file: SupportedOcrFile, provider: OcrProvider): Promise => ipcRenderer.invoke(IpcChannel.OCR_ocr, file, provider) + }, + cherryin: { + generateSignature: (params: { method: string; path: string; query: string; body: Record }) => + ipcRenderer.invoke(IpcChannel.Cherryin_GetSignature, params) } } diff --git a/src/renderer/src/aiCore/__tests__/index.clientCompatibilityTypes.test.ts b/src/renderer/src/aiCore/__tests__/index.clientCompatibilityTypes.test.ts index b69d761307..12571875db 100644 --- a/src/renderer/src/aiCore/__tests__/index.clientCompatibilityTypes.test.ts +++ b/src/renderer/src/aiCore/__tests__/index.clientCompatibilityTypes.test.ts @@ -1,9 +1,9 @@ -import { AihubmixAPIClient } from '@renderer/aiCore/clients/AihubmixAPIClient' +import { AihubmixAPIClient } from '@renderer/aiCore/clients/aihubmix/AihubmixAPIClient' import { AnthropicAPIClient } from '@renderer/aiCore/clients/anthropic/AnthropicAPIClient' import { ApiClientFactory } from '@renderer/aiCore/clients/ApiClientFactory' import { GeminiAPIClient } from '@renderer/aiCore/clients/gemini/GeminiAPIClient' import { VertexAPIClient } from '@renderer/aiCore/clients/gemini/VertexAPIClient' -import { NewAPIClient } from '@renderer/aiCore/clients/NewAPIClient' +import { NewAPIClient } from '@renderer/aiCore/clients/newapi/NewAPIClient' import { OpenAIAPIClient } from '@renderer/aiCore/clients/openai/OpenAIApiClient' import { OpenAIResponseAPIClient } from '@renderer/aiCore/clients/openai/OpenAIResponseAPIClient' import { EndpointType, Model, Provider } from '@renderer/types' @@ -16,6 +16,7 @@ vi.mock('@renderer/config/models', () => ({ { id: 'gpt-4', name: 'GPT-4' }, { id: 'gpt-4', name: 'GPT-4' } ], + zhipu: [], silicon: [], openai: [], anthropic: [], @@ -32,7 +33,13 @@ vi.mock('@renderer/config/models', () => ({ isWebSearchModel: vi.fn().mockReturnValue(false), findTokenLimit: vi.fn().mockReturnValue(4096), isFunctionCallingModel: vi.fn().mockReturnValue(false), - DEFAULT_MAX_TOKENS: 4096 + DEFAULT_MAX_TOKENS: 4096, + glm45FlashModel: { + id: 'glm-4.5-flash', + name: 'GLM-4.5-Flash', + provider: 'cherryin', + group: 'GLM-4.5' + } })) vi.mock('@renderer/services/AssistantService', () => ({ diff --git a/src/renderer/src/aiCore/clients/ApiClientFactory.ts b/src/renderer/src/aiCore/clients/ApiClientFactory.ts index e708ab8c42..7c5575aa08 100644 --- a/src/renderer/src/aiCore/clients/ApiClientFactory.ts +++ b/src/renderer/src/aiCore/clients/ApiClientFactory.ts @@ -1,16 +1,18 @@ import { loggerService } from '@logger' import { Provider } from '@renderer/types' -import { AihubmixAPIClient } from './AihubmixAPIClient' +import { AihubmixAPIClient } from './aihubmix/AihubmixAPIClient' import { AnthropicAPIClient } from './anthropic/AnthropicAPIClient' import { AwsBedrockAPIClient } from './aws/AwsBedrockAPIClient' import { BaseApiClient } from './BaseApiClient' +import { CherryinAPIClient } from './cherryin/CherryinAPIClient' import { GeminiAPIClient } from './gemini/GeminiAPIClient' import { VertexAPIClient } from './gemini/VertexAPIClient' -import { NewAPIClient } from './NewAPIClient' +import { NewAPIClient } from './newapi/NewAPIClient' import { OpenAIAPIClient } from './openai/OpenAIApiClient' import { OpenAIResponseAPIClient } from './openai/OpenAIResponseAPIClient' import { PPIOAPIClient } from './ppio/PPIOAPIClient' +import { ZhipuAPIClient } from './zhipu/ZhipuAPIClient' const logger = loggerService.withContext('ApiClientFactory') @@ -31,24 +33,36 @@ export class ApiClientFactory { let instance: BaseApiClient - // 首先检查特殊的provider id + // 首先检查特殊的 Provider ID + if (provider.id === 'cherryin') { + instance = new CherryinAPIClient(provider) as BaseApiClient + return instance + } + if (provider.id === 'aihubmix') { logger.debug(`Creating AihubmixAPIClient for provider: ${provider.id}`) instance = new AihubmixAPIClient(provider) as BaseApiClient return instance } + if (provider.id === 'new-api') { logger.debug(`Creating NewAPIClient for provider: ${provider.id}`) instance = new NewAPIClient(provider) as BaseApiClient return instance } + if (provider.id === 'ppio') { logger.debug(`Creating PPIOAPIClient for provider: ${provider.id}`) instance = new PPIOAPIClient(provider) as BaseApiClient return instance } - // 然后检查标准的provider type + if (provider.id === 'zhipu') { + instance = new ZhipuAPIClient(provider) as BaseApiClient + return instance + } + + // 然后检查标准的 Provider Type switch (provider.type) { case 'openai': instance = new OpenAIAPIClient(provider) as BaseApiClient @@ -78,8 +92,3 @@ export class ApiClientFactory { return instance } } - -// 移除这个函数,它已经移动到 utils/index.ts -// export function isOpenAIProvider(provider: Provider) { -// return !['anthropic', 'gemini'].includes(provider.type) -// } diff --git a/src/renderer/src/aiCore/clients/__tests__/ApiClientFactory.test.ts b/src/renderer/src/aiCore/clients/__tests__/ApiClientFactory.test.ts index 5ec3bf6404..4d58c78772 100644 --- a/src/renderer/src/aiCore/clients/__tests__/ApiClientFactory.test.ts +++ b/src/renderer/src/aiCore/clients/__tests__/ApiClientFactory.test.ts @@ -2,13 +2,13 @@ import { Provider } from '@renderer/types' import { isOpenAIProvider } from '@renderer/utils' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { AihubmixAPIClient } from '../AihubmixAPIClient' +import { AihubmixAPIClient } from '../aihubmix/AihubmixAPIClient' import { AnthropicAPIClient } from '../anthropic/AnthropicAPIClient' import { ApiClientFactory } from '../ApiClientFactory' import { AwsBedrockAPIClient } from '../aws/AwsBedrockAPIClient' import { GeminiAPIClient } from '../gemini/GeminiAPIClient' import { VertexAPIClient } from '../gemini/VertexAPIClient' -import { NewAPIClient } from '../NewAPIClient' +import { NewAPIClient } from '../newapi/NewAPIClient' import { OpenAIAPIClient } from '../openai/OpenAIApiClient' import { OpenAIResponseAPIClient } from '../openai/OpenAIResponseAPIClient' import { PPIOAPIClient } from '../ppio/PPIOAPIClient' @@ -26,7 +26,7 @@ const createTestProvider = (id: string, type: string): Provider => ({ }) // Mock 所有客户端模块 -vi.mock('../AihubmixAPIClient', () => ({ +vi.mock('../aihubmix/AihubmixAPIClient', () => ({ AihubmixAPIClient: vi.fn().mockImplementation(() => ({})) })) vi.mock('../anthropic/AnthropicAPIClient', () => ({ @@ -41,7 +41,7 @@ vi.mock('../gemini/GeminiAPIClient', () => ({ vi.mock('../gemini/VertexAPIClient', () => ({ VertexAPIClient: vi.fn().mockImplementation(() => ({})) })) -vi.mock('../NewAPIClient', () => ({ +vi.mock('../newapi/NewAPIClient', () => ({ NewAPIClient: vi.fn().mockImplementation(() => ({})) })) vi.mock('../openai/OpenAIApiClient', () => ({ diff --git a/src/renderer/src/aiCore/clients/AihubmixAPIClient.ts b/src/renderer/src/aiCore/clients/aihubmix/AihubmixAPIClient.ts similarity index 88% rename from src/renderer/src/aiCore/clients/AihubmixAPIClient.ts rename to src/renderer/src/aiCore/clients/aihubmix/AihubmixAPIClient.ts index f27674174d..1149c04b35 100644 --- a/src/renderer/src/aiCore/clients/AihubmixAPIClient.ts +++ b/src/renderer/src/aiCore/clients/aihubmix/AihubmixAPIClient.ts @@ -1,12 +1,12 @@ import { isOpenAILLMModel } from '@renderer/config/models' import { Model, Provider } from '@renderer/types' -import { AnthropicAPIClient } from './anthropic/AnthropicAPIClient' -import { BaseApiClient } from './BaseApiClient' -import { GeminiAPIClient } from './gemini/GeminiAPIClient' -import { MixedBaseAPIClient } from './MixedBaseApiClient' -import { OpenAIAPIClient } from './openai/OpenAIApiClient' -import { OpenAIResponseAPIClient } from './openai/OpenAIResponseAPIClient' +import { AnthropicAPIClient } from '../anthropic/AnthropicAPIClient' +import { BaseApiClient } from '../BaseApiClient' +import { GeminiAPIClient } from '../gemini/GeminiAPIClient' +import { MixedBaseAPIClient } from '../MixedBaseApiClient' +import { OpenAIAPIClient } from '../openai/OpenAIApiClient' +import { OpenAIResponseAPIClient } from '../openai/OpenAIResponseAPIClient' /** * AihubmixAPIClient - 根据模型类型自动选择合适的ApiClient diff --git a/src/renderer/src/aiCore/clients/cherryin/CherryinAPIClient.ts b/src/renderer/src/aiCore/clients/cherryin/CherryinAPIClient.ts new file mode 100644 index 0000000000..bf3ed7d718 --- /dev/null +++ b/src/renderer/src/aiCore/clients/cherryin/CherryinAPIClient.ts @@ -0,0 +1,51 @@ +import { Provider } from '@renderer/types' +import { OpenAISdkParams, OpenAISdkRawOutput } from '@renderer/types/sdk' +import OpenAI from 'openai' + +import { OpenAIAPIClient } from '../openai/OpenAIApiClient' + +export class CherryinAPIClient extends OpenAIAPIClient { + constructor(provider: Provider) { + super(provider) + } + + override async createCompletions( + payload: OpenAISdkParams, + options?: OpenAI.RequestOptions + ): Promise { + const sdk = await this.getSdkInstance() + options = options || {} + options.headers = options.headers || {} + + const signature = await window.api.cherryin.generateSignature({ + method: 'POST', + path: '/chat/completions', + query: '', + body: payload + }) + + options.headers = { + ...options.headers, + ...signature + } + + // @ts-ignore - SDK参数可能有额外的字段 + return await sdk.chat.completions.create(payload, options) + } + + override getClientCompatibilityType(): string[] { + return ['CherryinAPIClient'] + } + + public async listModels(): Promise { + const models = ['glm-4.5-flash', 'Qwen/Qwen3-8B'] + + const created = Date.now() + return models.map((id) => ({ + id, + owned_by: 'cherryin', + object: 'model' as const, + created + })) + } +} diff --git a/src/renderer/src/aiCore/clients/index.ts b/src/renderer/src/aiCore/clients/index.ts index ec7f9d9d7e..f364dbcee6 100644 --- a/src/renderer/src/aiCore/clients/index.ts +++ b/src/renderer/src/aiCore/clients/index.ts @@ -3,4 +3,6 @@ export * from './BaseApiClient' export * from './types' // Export specific clients from subdirectories +export * from './anthropic/AnthropicAPIClient' export * from './openai/OpenAIApiClient' +export * from './openai/OpenAIResponseAPIClient' diff --git a/src/renderer/src/aiCore/clients/NewAPIClient.ts b/src/renderer/src/aiCore/clients/newapi/NewAPIClient.ts similarity index 89% rename from src/renderer/src/aiCore/clients/NewAPIClient.ts rename to src/renderer/src/aiCore/clients/newapi/NewAPIClient.ts index e87d54ae3e..58b349a2be 100644 --- a/src/renderer/src/aiCore/clients/NewAPIClient.ts +++ b/src/renderer/src/aiCore/clients/newapi/NewAPIClient.ts @@ -3,12 +3,12 @@ import { isSupportedModel } from '@renderer/config/models' import { Model, Provider } from '@renderer/types' import { NewApiModel } from '@renderer/types/sdk' -import { AnthropicAPIClient } from './anthropic/AnthropicAPIClient' -import { BaseApiClient } from './BaseApiClient' -import { GeminiAPIClient } from './gemini/GeminiAPIClient' -import { MixedBaseAPIClient } from './MixedBaseApiClient' -import { OpenAIAPIClient } from './openai/OpenAIApiClient' -import { OpenAIResponseAPIClient } from './openai/OpenAIResponseAPIClient' +import { AnthropicAPIClient } from '../anthropic/AnthropicAPIClient' +import { BaseApiClient } from '../BaseApiClient' +import { GeminiAPIClient } from '../gemini/GeminiAPIClient' +import { MixedBaseAPIClient } from '../MixedBaseApiClient' +import { OpenAIAPIClient } from '../openai/OpenAIApiClient' +import { OpenAIResponseAPIClient } from '../openai/OpenAIResponseAPIClient' const logger = loggerService.withContext('NewAPIClient') diff --git a/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts b/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts index 1665c580de..b9da840164 100644 --- a/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts +++ b/src/renderer/src/aiCore/clients/openai/OpenAIApiClient.ts @@ -122,13 +122,11 @@ export class OpenAIAPIClient extends OpenAIBaseClient< if (!isReasoningModel(model)) { return {} } + const reasoningEffort = assistant?.settings?.reasoning_effort if (isSupportedThinkingTokenZhipuModel(model)) { - if (!reasoningEffort) { - return { thinking: { type: 'disabled' } } - } - return { thinking: { type: 'enabled' } } + return { thinking: { type: reasoningEffort ? 'enabled' : 'disabled' } } } if (!reasoningEffort) { diff --git a/src/renderer/src/aiCore/clients/zhipu/ZhipuAPIClient.ts b/src/renderer/src/aiCore/clients/zhipu/ZhipuAPIClient.ts new file mode 100644 index 0000000000..c1d7b8f562 --- /dev/null +++ b/src/renderer/src/aiCore/clients/zhipu/ZhipuAPIClient.ts @@ -0,0 +1,100 @@ +import { loggerService } from '@logger' +import { Provider } from '@renderer/types' +import { GenerateImageParams } from '@renderer/types' +import OpenAI from 'openai' + +import { OpenAIAPIClient } from '../openai/OpenAIApiClient' + +const logger = loggerService.withContext('ZhipuAPIClient') + +export class ZhipuAPIClient extends OpenAIAPIClient { + constructor(provider: Provider) { + super(provider) + } + + override getClientCompatibilityType(): string[] { + return ['ZhipuAPIClient'] + } + + override async generateImage({ + model, + prompt, + negativePrompt, + imageSize, + batchSize, + signal, + quality + }: GenerateImageParams): Promise { + const sdk = await this.getSdkInstance() + + // 智谱AI使用不同的参数格式 + const body: any = { + model, + prompt + } + + // 智谱AI特有的参数格式 + body.size = imageSize + body.n = batchSize + if (negativePrompt) { + body.negative_prompt = negativePrompt + } + + // 只有cogview-4-250304模型支持quality和style参数 + if (model === 'cogview-4-250304') { + if (quality) { + body.quality = quality + } + body.style = 'vivid' + } + + try { + logger.debug('Calling Zhipu image generation API with params:', body) + + const response = await sdk.images.generate(body, { signal }) + + if (response.data && response.data.length > 0) { + return response.data.map((image: any) => image.url).filter(Boolean) + } + + return [] + } catch (error) { + logger.error('Zhipu image generation failed:', error as Error) + throw error + } + } + + public async listModels(): Promise { + const models = [ + 'glm-4.5', + 'glm-4.5-x', + 'glm-4.5-air', + 'glm-4.5-airx', + 'glm-4.5-flash', + 'glm-4.5v', + 'glm-z1-air', + 'glm-z1-airx', + 'cogview-3-flash', + 'cogview-4-250304', + 'glm-4-long', + 'glm-4-plus', + 'glm-4-air-250414', + 'glm-4-airx', + 'glm-4-flashx', + 'glm-4v', + 'glm-4v-flash', + 'glm-4v-plus-0111', + 'glm-4.1v-thinking-flash', + 'glm-4-alltools', + 'embedding-3' + ] + + const created = Date.now() + return models.map((id) => ({ + id, + owned_by: 'zhipu', + object: 'model' as const, + created + })) + } +} diff --git a/src/renderer/src/aiCore/index.ts b/src/renderer/src/aiCore/index.ts index 99eb6c940b..2b48137b24 100644 --- a/src/renderer/src/aiCore/index.ts +++ b/src/renderer/src/aiCore/index.ts @@ -9,9 +9,9 @@ import type { GenerateImageParams, Model, Provider } from '@renderer/types' import type { RequestOptions, SdkModel } from '@renderer/types/sdk' import { isEnabledToolUse } from '@renderer/utils/mcp-tools' -import { AihubmixAPIClient } from './clients/AihubmixAPIClient' +import { AihubmixAPIClient } from './clients/aihubmix/AihubmixAPIClient' import { VertexAPIClient } from './clients/gemini/VertexAPIClient' -import { NewAPIClient } from './clients/NewAPIClient' +import { NewAPIClient } from './clients/newapi/NewAPIClient' import { OpenAIResponseAPIClient } from './clients/openai/OpenAIResponseAPIClient' import { CompletionsMiddlewareBuilder } from './middleware/builder' import { MIDDLEWARE_NAME as AbortHandlerMiddlewareName } from './middleware/common/AbortHandlerMiddleware' diff --git a/src/renderer/src/aiCore/middleware/common/ErrorHandlerMiddleware.ts b/src/renderer/src/aiCore/middleware/common/ErrorHandlerMiddleware.ts index 26d9342ebc..d80c9d2f83 100644 --- a/src/renderer/src/aiCore/middleware/common/ErrorHandlerMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/common/ErrorHandlerMiddleware.ts @@ -1,7 +1,9 @@ import { loggerService } from '@logger' +import { isZhipuModel } from '@renderer/config/models' +import store from '@renderer/store' import { Chunk } from '@renderer/types/chunk' -import { CompletionsResult } from '../schemas' +import { CompletionsParams, CompletionsResult } from '../schemas' import { CompletionsContext } from '../types' import { createErrorChunk } from '../utils' @@ -28,17 +30,22 @@ export const ErrorHandlerMiddleware = // 尝试执行下一个中间件 return await next(ctx, params) } catch (error: any) { - logger.error('ErrorHandlerMiddleware_error', error) + logger.error(error) + + let processedError = error + processedError = handleError(error, params) + // 1. 使用通用的工具函数将错误解析为标准格式 - const errorChunk = createErrorChunk(error) + const errorChunk = createErrorChunk(processedError) + // 2. 调用从外部传入的 onError 回调 if (params.onError) { - params.onError(error) + params.onError(processedError) } // 3. 根据配置决定是重新抛出错误,还是将其作为流的一部分向下传递 if (shouldThrow) { - throw error + throw processedError } // 如果不抛出,则创建一个只包含该错误块的流并向下传递 @@ -57,3 +64,70 @@ export const ErrorHandlerMiddleware = } } } + +function handleError(error: any, params: CompletionsParams): any { + if (isZhipuModel(params.assistant.model) && error.status && !params.enableGenerateImage) { + return handleZhipuError(error) + } + + if (error.status === 401 || error.message.includes('401')) { + return { + ...error, + i18nKey: 'chat.no_api_key', + providerId: params.assistant?.model?.provider + } + } + + return error +} + +/** + * 处理智谱特定错误 + * 1. 只有对话功能(enableGenerateImage为false)才使用自定义错误处理 + * 2. 绘画功能(enableGenerateImage为true)使用通用错误处理 + */ +function handleZhipuError(error: any): any { + const provider = store.getState().llm.providers.find((p) => p.id === 'zhipu') + const logger = loggerService.withContext('handleZhipuError') + + // 定义错误模式映射 + const errorPatterns = [ + { + condition: () => error.status === 401 || /令牌已过期|AuthenticationError|Unauthorized/i.test(error.message), + i18nKey: 'chat.no_api_key', + providerId: provider?.id + }, + { + condition: () => error.error?.code === '1304' || /限额|免费配额|free quota|rate limit/i.test(error.message), + i18nKey: 'chat.quota_exceeded', + providerId: provider?.id + }, + { + condition: () => + (error.status === 429 && error.error?.code === '1113') || /余额不足|insufficient balance/i.test(error.message), + i18nKey: 'chat.insufficient_balance', + providerId: provider?.id + }, + { + condition: () => !provider?.apiKey?.trim(), + i18nKey: 'chat.no_api_key', + providerId: provider?.id + } + ] + + // 遍历错误模式,返回第一个匹配的错误 + for (const pattern of errorPatterns) { + if (pattern.condition()) { + return { + ...error, + providerId: pattern.providerId, + i18nKey: pattern.i18nKey + } + } + } + + // 如果不是智谱特定错误,返回原始错误 + logger.debug('🔧 不是智谱特定错误,返回原始错误') + + return error +} diff --git a/src/renderer/src/assets/images/models/chatglm.png b/src/renderer/src/assets/images/models/chatglm.png index 6ef7f44519..f078fbfe15 100644 Binary files a/src/renderer/src/assets/images/models/chatglm.png and b/src/renderer/src/assets/images/models/chatglm.png differ diff --git a/src/renderer/src/assets/images/models/zhipu.png b/src/renderer/src/assets/images/models/zhipu.png index aedb3811c7..f078fbfe15 100644 Binary files a/src/renderer/src/assets/images/models/zhipu.png and b/src/renderer/src/assets/images/models/zhipu.png differ diff --git a/src/renderer/src/assets/images/models/zhipu_dark.png b/src/renderer/src/assets/images/models/zhipu_dark.png index 4f578081ef..f078fbfe15 100644 Binary files a/src/renderer/src/assets/images/models/zhipu_dark.png and b/src/renderer/src/assets/images/models/zhipu_dark.png differ diff --git a/src/renderer/src/assets/images/providers/cherryin.png b/src/renderer/src/assets/images/providers/cherryin.png new file mode 100644 index 0000000000..1a75ff570a Binary files /dev/null and b/src/renderer/src/assets/images/providers/cherryin.png differ diff --git a/src/renderer/src/assets/images/providers/zhipu.png b/src/renderer/src/assets/images/providers/zhipu.png index aedb3811c7..f078fbfe15 100644 Binary files a/src/renderer/src/assets/images/providers/zhipu.png and b/src/renderer/src/assets/images/providers/zhipu.png differ diff --git a/src/renderer/src/assets/images/search/zhipu.png b/src/renderer/src/assets/images/search/zhipu.png new file mode 100644 index 0000000000..f078fbfe15 Binary files /dev/null and b/src/renderer/src/assets/images/search/zhipu.png differ diff --git a/src/renderer/src/components/FreeTrialModelTag.tsx b/src/renderer/src/components/FreeTrialModelTag.tsx new file mode 100644 index 0000000000..0ce63ade37 --- /dev/null +++ b/src/renderer/src/components/FreeTrialModelTag.tsx @@ -0,0 +1,81 @@ +import { getProviderLabel } from '@renderer/i18n/label' +import NavigationService from '@renderer/services/NavigationService' +import { Model } from '@renderer/types' +import { ArrowUpRight } from 'lucide-react' +import { FC, MouseEvent } from 'react' +import styled from 'styled-components' + +import IndicatorLight from './IndicatorLight' +import SelectModelPopup from './Popups/SelectModelPopup' +import CustomTag from './Tags/CustomTag' + +interface Props { + model: Model + showLabel?: boolean +} + +export const FreeTrialModelTag: FC = ({ model, showLabel = true }) => { + if (model.provider !== 'cherryin') { + return null + } + + let providerId + + if (model.id === 'glm-4.5-flash') { + providerId = 'zhipu' + } + + if (model.id === 'Qwen/Qwen3-8B') { + providerId = 'silicon' + } + + const onSelectProvider = () => { + NavigationService.navigate!(`/settings/provider?id=${providerId}`) + } + + const onNavigateProvider = (e: MouseEvent) => { + e.stopPropagation() + SelectModelPopup.hide() + NavigationService.navigate!(`/settings/provider?id=${providerId}`) + } + + if (!showLabel) { + return ( + + + {getProviderLabel(providerId)} + + + + ) + } + + return ( + + + Powered by + {getProviderLabel(providerId)} + + ) +} + +const Container = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 4px; +` + +const PoweredBy = styled.span` + font-size: 12px; + color: var(--color-text-2); +` + +const LinkText = styled.a` + font-size: 12px; + color: var(--color-link); +` diff --git a/src/renderer/src/components/Icons/SVGIcon.tsx b/src/renderer/src/components/Icons/SVGIcon.tsx index 8cae9c94de..a83685e4db 100644 --- a/src/renderer/src/components/Icons/SVGIcon.tsx +++ b/src/renderer/src/components/Icons/SVGIcon.tsx @@ -239,6 +239,18 @@ export function BochaLogo(props: SVGProps) { ) } +export function ZhipuLogo(props: SVGProps) { + return ( + + + + ) +} export function PoeLogo(props: SVGProps) { return ( = ({ model, resolve, modelFilter, userFilt (model: Model, provider: Provider, isPinned: boolean): FlatListModel => { const modelId = getModelUniqId(model) const groupName = getFancyProviderName(provider) + const isCherryin = provider.id === 'cherryin' return { key: isPinned ? `${modelId}_pinned` : modelId, @@ -209,11 +211,12 @@ const PopupContainer: React.FC = ({ model, resolve, modelFilter, userFilt {model.name} {isPinned && | {groupName}} + {isCherryin && } ), tags: ( - + ), icon: ( @@ -280,7 +283,7 @@ const PopupContainer: React.FC = ({ model, resolve, modelFilter, userFilt type="text" size="small" shape="circle" - icon={} + icon={} onClick={(e) => { e.stopPropagation() setOpen(false) @@ -576,7 +579,7 @@ const ListContainer = styled.div` const GroupItem = styled.div` display: flex; align-items: center; - justify-content: space-between; + gap: 2px; position: relative; font-size: 12px; font-weight: normal; @@ -585,6 +588,17 @@ const GroupItem = styled.div` color: var(--color-text-3); z-index: 1; background: var(--modal-background); + + &:hover { + .ant-btn { + opacity: 1; + } + } + + .ant-btn { + opacity: 0; + transition: opacity 0.2s; + } ` const ModelItem = styled.div` @@ -640,13 +654,16 @@ const ModelItemLeft = styled.div` } ` -const ModelName = styled.span` +const ModelName = styled.div` + display: flex; + flex-direction: row; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1; margin: 0 8px; min-width: 0; + gap: 5px; ` const TagsContainer = styled.div` diff --git a/src/renderer/src/components/Tags/CustomTag.tsx b/src/renderer/src/components/Tags/CustomTag.tsx index c7abd15a62..d078d46d38 100644 --- a/src/renderer/src/components/Tags/CustomTag.tsx +++ b/src/renderer/src/components/Tags/CustomTag.tsx @@ -1,6 +1,6 @@ import { CloseOutlined } from '@ant-design/icons' import { Tooltip } from 'antd' -import { CSSProperties, FC, memo, useMemo } from 'react' +import { CSSProperties, FC, memo, MouseEventHandler, useMemo } from 'react' import styled from 'styled-components' export interface CustomTagProps { @@ -12,7 +12,7 @@ export interface CustomTagProps { tooltip?: string closable?: boolean onClose?: () => void - onClick?: () => void + onClick?: MouseEventHandler disabled?: boolean inactive?: boolean } diff --git a/src/renderer/src/components/Tags/Model/FreeTag.tsx b/src/renderer/src/components/Tags/Model/FreeTag.tsx index a046449dcf..44b19012e0 100644 --- a/src/renderer/src/components/Tags/Model/FreeTag.tsx +++ b/src/renderer/src/components/Tags/Model/FreeTag.tsx @@ -15,6 +15,7 @@ export const FreeTag = ({ size, showTooltip, ...restProps }: Props) => { color="#7cb305" icon={t('models.type.free')} tooltip={showTooltip ? t('models.type.free') : undefined} - {...restProps}> + {...restProps} + /> ) } diff --git a/src/renderer/src/config/minapps.ts b/src/renderer/src/config/minapps.ts index aa2cee372c..8fdfff9beb 100644 --- a/src/renderer/src/config/minapps.ts +++ b/src/renderer/src/config/minapps.ts @@ -35,7 +35,6 @@ import NamiAiSearchLogo from '@renderer/assets/images/apps/nm-search.webp?url' import NotebookLMAppLogo from '@renderer/assets/images/apps/notebooklm.svg?url' import PerplexityAppLogo from '@renderer/assets/images/apps/perplexity.webp?url' import PoeAppLogo from '@renderer/assets/images/apps/poe.webp?url' -import ZhipuProviderLogo from '@renderer/assets/images/apps/qingyan.png?url' import QwenlmAppLogo from '@renderer/assets/images/apps/qwenlm.webp?url' import SensetimeAppLogo from '@renderer/assets/images/apps/sensetime.png?url' import SparkDeskAppLogo from '@renderer/assets/images/apps/sparkdesk.webp?url' @@ -56,6 +55,7 @@ import DeepSeekProviderLogo from '@renderer/assets/images/providers/deepseek.png import GroqProviderLogo from '@renderer/assets/images/providers/groq.png?url' import OpenAiProviderLogo from '@renderer/assets/images/providers/openai.png?url' import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png?url' +import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png?url' import i18n from '@renderer/i18n' import { MinAppType } from '@renderer/types' @@ -126,7 +126,8 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ id: 'zhipu', name: i18n.t('minapps.chatglm'), url: 'https://chatglm.cn/main/alltoolsdetail', - logo: ZhipuProviderLogo + logo: ZhipuProviderLogo, + bodered: true }, { id: 'moonshot', diff --git a/src/renderer/src/config/models.ts b/src/renderer/src/config/models.ts index bba48781f4..a5fae53794 100644 --- a/src/renderer/src/config/models.ts +++ b/src/renderer/src/config/models.ts @@ -146,8 +146,11 @@ import XirangModelLogo from '@renderer/assets/images/models/xirang.png' import XirangModelLogoDark from '@renderer/assets/images/models/xirang_dark.png' import YiModelLogo from '@renderer/assets/images/models/yi.png' import YiModelLogoDark from '@renderer/assets/images/models/yi_dark.png' +import ZhipuModelLogo from '@renderer/assets/images/models/zhipu.png' +import ZhipuModelLogoDark from '@renderer/assets/images/models/zhipu_dark.png' import YoudaoLogo from '@renderer/assets/images/providers/netease-youdao.svg' import NomicLogo from '@renderer/assets/images/providers/nomic.png' +import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png' import { getProviderByModel } from '@renderer/services/AssistantService' import { isSystemProviderId, @@ -277,7 +280,8 @@ const FUNCTION_CALLING_EXCLUDED_MODELS = [ 'AIDC-AI/Marco-o1', 'gemini-1(?:\\.[\\w-]+)?', 'qwen-mt(?:-[\\w-]+)?', - 'gpt-5-chat(?:-[\\w-]+)?' + 'gpt-5-chat(?:-[\\w-]+)?', + 'glm-4\\.5v' ] export const FUNCTION_CALLING_REGEX = new RegExp( @@ -515,6 +519,7 @@ export function getModelLogo(modelId: string) { xirang: isLight ? XirangModelLogo : XirangModelLogoDark, hugging: isLight ? HuggingfaceModelLogo : HuggingfaceModelLogoDark, youdao: YoudaoLogo, + 'embedding-3': ZhipuProviderLogo, embedding: isLight ? EmbeddingModelLogo : EmbeddingModelLogoDark, perplexity: isLight ? PerplexityModelLogo : PerplexityModelLogoDark, sonar: isLight ? PerplexityModelLogo : PerplexityModelLogoDark, @@ -522,7 +527,9 @@ export function getModelLogo(modelId: string) { 'voyage-': VoyageModelLogo, tokenflux: isLight ? TokenFluxModelLogo : TokenFluxModelLogoDark, 'nomic-': NomicLogo, - 'pangu-': PanguModelLogo + 'pangu-': PanguModelLogo, + cogview: isLight ? ZhipuModelLogo : ZhipuModelLogoDark, + zhipu: isLight ? ZhipuModelLogo : ZhipuModelLogoDark } for (const key in logoMap) { @@ -535,35 +542,43 @@ export function getModelLogo(modelId: string) { return undefined } +export const glm45FlashModel: Model = { + id: 'glm-4.5-flash', + name: 'GLM-4.5-Flash', + provider: 'cherryin', + group: 'GLM-4.5' +} + +export const qwen38bModel: Model = { + id: 'Qwen/Qwen3-8B', + name: 'Qwen3-8B', + provider: 'cherryin', + group: 'Qwen' +} + export const SYSTEM_MODELS: Record = { defaultModel: [ + // Default assistant model + glm45FlashModel, + // Default topic naming model + qwen38bModel, + // Default translation model + glm45FlashModel, + // Default quick assistant model + glm45FlashModel + ], + cherryin: [ { - // 默认助手模型 - id: 'deepseek-ai/DeepSeek-V3', - name: 'deepseek-ai/DeepSeek-V3', - provider: 'silicon', - group: 'deepseek-ai' + id: 'glm-4.5-flash', + name: 'GLM-4.5-Flash', + provider: 'cherryin', + group: 'GLM-4.5' }, { - // 默认话题命名模型 id: 'Qwen/Qwen3-8B', - name: 'Qwen/Qwen3-8B', - provider: 'silicon', + name: 'Qwen3-8B', + provider: 'cherryin', group: 'Qwen' - }, - { - // 默认翻译模型 - id: 'deepseek-ai/DeepSeek-V3', - name: 'deepseek-ai/DeepSeek-V3', - provider: 'silicon', - group: 'deepseek-ai' - }, - { - // 默认快捷助手模型 - id: 'deepseek-ai/DeepSeek-V3', - name: 'deepseek-ai/DeepSeek-V3', - provider: 'silicon', - group: 'deepseek-ai' } ], vertexai: [], @@ -1225,113 +1240,35 @@ export const SYSTEM_MODELS: Record = { id: 'yi-vision-v2', name: 'Yi Vision v2', provider: 'yi', group: 'yi-vision', owned_by: '01.ai' } ], zhipu: [ - { - id: 'glm-4.5', - provider: 'zhipu', - name: 'GLM-4.5', - group: 'GLM-4.5' - }, { id: 'glm-4.5-flash', provider: 'zhipu', name: 'GLM-4.5-Flash', group: 'GLM-4.5' }, + { + id: 'glm-4.5', + provider: 'zhipu', + name: 'GLM-4.5', + group: 'GLM-4.5' + }, { id: 'glm-4.5-air', provider: 'zhipu', - name: 'GLM-4.5-AIR', + name: 'GLM-4.5-Air', group: 'GLM-4.5' }, { id: 'glm-4.5-airx', provider: 'zhipu', - name: 'GLM-4.5-AIRX', + name: 'GLM-4.5-AirX', group: 'GLM-4.5' }, { - id: 'glm-4.5-x', + id: 'glm-4.5v', provider: 'zhipu', - name: 'GLM-4.5-X', - group: 'GLM-4.5' - }, - { - id: 'glm-z1-air', - provider: 'zhipu', - name: 'GLM-Z1-AIR', - group: 'GLM-Z1' - }, - { - id: 'glm-z1-airx', - provider: 'zhipu', - name: 'GLM-Z1-AIRX', - group: 'GLM-Z1' - }, - { - id: 'glm-z1-flash', - provider: 'zhipu', - name: 'GLM-Z1-FLASH', - group: 'GLM-Z1' - }, - { - id: 'glm-4-long', - provider: 'zhipu', - name: 'GLM-4-Long', - group: 'GLM-4' - }, - { - id: 'glm-4-plus', - provider: 'zhipu', - name: 'GLM-4-Plus', - group: 'GLM-4' - }, - { - id: 'glm-4-air-250414', - provider: 'zhipu', - name: 'GLM-4-Air-250414', - group: 'GLM-4' - }, - { - id: 'glm-4-airx', - provider: 'zhipu', - name: 'GLM-4-AirX', - group: 'GLM-4' - }, - { - id: 'glm-4-flash-250414', - provider: 'zhipu', - name: 'GLM-4-Flash-250414', - group: 'GLM-4' - }, - { - id: 'glm-4-flashx', - provider: 'zhipu', - name: 'GLM-4-FlashX', - group: 'GLM-4' - }, - { - id: 'glm-4v', - provider: 'zhipu', - name: 'GLM 4V', - group: 'GLM-4v' - }, - { - id: 'glm-4v-flash', - provider: 'zhipu', - name: 'GLM-4V-Flash', - group: 'GLM-4v' - }, - { - id: 'glm-4v-plus-0111', - provider: 'zhipu', - name: 'GLM-4V-Plus-0111', - group: 'GLM-4v' - }, - { - id: 'glm-4-alltools', - provider: 'zhipu', - name: 'GLM-4-AllTools', - group: 'GLM-4-AllTools' + name: 'GLM-4.5V', + group: 'GLM-4.5V' }, { id: 'embedding-3', diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index 795b243c62..2ad123f32f 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -11,6 +11,7 @@ import BaiduCloudProviderLogo from '@renderer/assets/images/providers/baidu-clou import BailianProviderLogo from '@renderer/assets/images/providers/bailian.png' import BurnCloudProviderLogo from '@renderer/assets/images/providers/burncloud.png' import CephalonProviderLogo from '@renderer/assets/images/providers/cephalon.jpeg' +import CherryInProviderLogo from '@renderer/assets/images/providers/cherryin.png' import DeepSeekProviderLogo from '@renderer/assets/images/providers/deepseek.png' import DmxapiProviderLogo from '@renderer/assets/images/providers/DMXAPI.png' import FireworksProviderLogo from '@renderer/assets/images/providers/fireworks.png' @@ -65,6 +66,16 @@ import { TOKENFLUX_HOST } from './constant' import { SYSTEM_MODELS } from './models' export const SYSTEM_PROVIDERS_CONFIG: Record = { + cherryin: { + id: 'cherryin', + name: 'CherryIN', + type: 'openai', + apiKey: '', + apiHost: 'https://api.cherry-ai.com/', + models: SYSTEM_MODELS.cherryin, + isSystem: true, + enabled: true + }, silicon: { id: 'silicon', name: 'Silicon', @@ -73,7 +84,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record = apiHost: 'https://api.siliconflow.cn', models: SYSTEM_MODELS.silicon, isSystem: true, - enabled: true + enabled: false }, aihubmix: { id: 'aihubmix', @@ -95,6 +106,16 @@ export const SYSTEM_PROVIDERS_CONFIG: Record = isSystem: true, enabled: false }, + zhipu: { + id: 'zhipu', + name: 'ZhiPu', + type: 'openai', + apiKey: '', + apiHost: 'https://open.bigmodel.cn/api/paas/v4/', + models: SYSTEM_MODELS.zhipu, + isSystem: true, + enabled: false + }, deepseek: { id: 'deepseek', name: 'deepseek', @@ -320,16 +341,6 @@ export const SYSTEM_PROVIDERS_CONFIG: Record = enabled: false, isAuthed: false }, - zhipu: { - id: 'zhipu', - name: 'ZhiPu', - type: 'openai', - apiKey: '', - apiHost: 'https://open.bigmodel.cn/api/paas/v4/', - models: SYSTEM_MODELS.zhipu, - isSystem: true, - enabled: false - }, yi: { id: 'yi', name: 'Yi', @@ -595,6 +606,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record = export const SYSTEM_PROVIDERS: SystemProvider[] = Object.values(SYSTEM_PROVIDERS_CONFIG) export const PROVIDER_LOGO_MAP: AtLeast = { + cherryin: CherryInProviderLogo, ph8: Ph8ProviderLogo, '302ai': Ai302ProviderLogo, openai: OpenAiProviderLogo, @@ -673,6 +685,16 @@ type ProviderUrls = { } export const PROVIDER_URLS: Record = { + cherryin: { + api: { + url: 'https://api.cherry-ai.com' + }, + websites: { + official: 'https://cherry-ai.com', + docs: 'https://docs.cherry-ai.com', + models: 'https://docs.cherry-ai.com/pre-basic/providers/cherryin' + } + }, ph8: { api: { url: 'https://ph8.co' diff --git a/src/renderer/src/config/webSearchProviders.ts b/src/renderer/src/config/webSearchProviders.ts index 62c0536f4d..abeac52938 100644 --- a/src/renderer/src/config/webSearchProviders.ts +++ b/src/renderer/src/config/webSearchProviders.ts @@ -8,6 +8,12 @@ type WebSearchProviderConfig = { } export const WEB_SEARCH_PROVIDER_CONFIG: Record = { + zhipu: { + websites: { + official: 'https://docs.bigmodel.cn/cn/guide/tools/web-search', + apiKey: 'https://zhipuaishengchan.datasink.sensorsdata.cn/t/yv' + } + }, tavily: { websites: { official: 'https://tavily.com', @@ -49,6 +55,12 @@ export const WEB_SEARCH_PROVIDER_CONFIG: Record state.paintings.paintings) - const generate = useAppSelector((state) => state.paintings.generate) - const remix = useAppSelector((state) => state.paintings.remix) - const edit = useAppSelector((state) => state.paintings.edit) - const upscale = useAppSelector((state) => state.paintings.upscale) - const DMXAPIPaintings = useAppSelector((state) => state.paintings.DMXAPIPaintings) - const tokenFluxPaintings = useAppSelector((state) => state.paintings.tokenFluxPaintings) + const siliconflow_paintings = useAppSelector((state) => state.paintings.siliconflow_paintings) + const dmxapi_paintings = useAppSelector((state) => state.paintings.dmxapi_paintings) + const tokenflux_paintings = useAppSelector((state) => state.paintings.tokenflux_paintings) + const zhipu_paintings = useAppSelector((state) => state.paintings.zhipu_paintings) + const aihubmix_image_generate = useAppSelector((state) => state.paintings.aihubmix_image_generate) + const aihubmix_image_remix = useAppSelector((state) => state.paintings.aihubmix_image_remix) + const aihubmix_image_edit = useAppSelector((state) => state.paintings.aihubmix_image_edit) + const aihubmix_image_upscale = useAppSelector((state) => state.paintings.aihubmix_image_upscale) const openai_image_generate = useAppSelector((state) => state.paintings.openai_image_generate) const openai_image_edit = useAppSelector((state) => state.paintings.openai_image_edit) const dispatch = useAppDispatch() return { - paintings, - DMXAPIPaintings, - tokenFluxPaintings, - persistentData: { - generate, - remix, - edit, - upscale, - tokenFluxPaintings - }, - newApiPaintings: { - openai_image_generate, - openai_image_edit - }, + siliconflow_paintings, + dmxapi_paintings, + tokenflux_paintings, + zhipu_paintings, + aihubmix_image_generate, + aihubmix_image_remix, + aihubmix_image_edit, + aihubmix_image_upscale, + openai_image_generate, + openai_image_edit, addPainting: (namespace: keyof PaintingsState, painting: PaintingAction) => { dispatch(addPainting({ namespace, painting })) return painting diff --git a/src/renderer/src/i18n/label.ts b/src/renderer/src/i18n/label.ts index 2e85dbaf62..63ffc2e40e 100644 --- a/src/renderer/src/i18n/label.ts +++ b/src/renderer/src/i18n/label.ts @@ -13,7 +13,7 @@ const t = i18n.t const logger = loggerService.withContext('i18n:label') -const getLabel = (key: string, keyMap: Record, fallback?: string) => { +const getLabel = (keyMap: Record, key: string, fallback?: string) => { const result = keyMap[key] if (result) { return t(result) @@ -34,6 +34,7 @@ const providerKeyMap = { 'baidu-cloud': 'provider.baidu-cloud', burncloud: 'provider.burncloud', cephalon: 'provider.cephalon', + cherryin: 'provider.cherryin', copilot: 'provider.copilot', dashscope: 'provider.dashscope', deepseek: 'provider.deepseek', @@ -92,7 +93,7 @@ const providerKeyMap = { * 对于可能处理自定义供应商的情况,使用 getProviderName 或 getFancyProviderName 更安全 */ export const getProviderLabel = (id: string): string => { - return getLabel(id, providerKeyMap) + return getLabel(providerKeyMap, id) } const backupProgressKeyMap = { @@ -106,7 +107,7 @@ const backupProgressKeyMap = { } as const export const getBackupProgressLabel = (key: string): string => { - return getLabel(key, backupProgressKeyMap) + return getLabel(backupProgressKeyMap, key) } const restoreProgressKeyMap = { @@ -120,7 +121,7 @@ const restoreProgressKeyMap = { } export const getRestoreProgressLabel = (key: string): string => { - return getLabel(key, restoreProgressKeyMap) + return getLabel(restoreProgressKeyMap, key) } const titleKeyMap = { @@ -139,7 +140,7 @@ const titleKeyMap = { } as const export const getTitleLabel = (key: string): string => { - return getLabel(key, titleKeyMap) + return getLabel(titleKeyMap, key) } const themeModeKeyMap = { @@ -149,7 +150,7 @@ const themeModeKeyMap = { } as const export const getThemeModeLabel = (key: string): string => { - return getLabel(key, themeModeKeyMap) + return getLabel(themeModeKeyMap, key) } const sidebarIconKeyMap = { @@ -164,7 +165,7 @@ const sidebarIconKeyMap = { } as const export const getSidebarIconLabel = (key: string): string => { - return getLabel(key, sidebarIconKeyMap) + return getLabel(sidebarIconKeyMap, key) } const shortcutKeyMap = { @@ -198,7 +199,7 @@ const shortcutKeyMap = { } as const export const getShortcutLabel = (key: string): string => { - return getLabel(key, shortcutKeyMap) + return getLabel(shortcutKeyMap, key) } const selectionDescriptionKeyMap = { @@ -207,7 +208,7 @@ const selectionDescriptionKeyMap = { } as const export const getSelectionDescriptionLabel = (key: string): string => { - return getLabel(key, selectionDescriptionKeyMap) + return getLabel(selectionDescriptionKeyMap, key) } const paintingsImageSizeOptionsKeyMap = { @@ -215,7 +216,7 @@ const paintingsImageSizeOptionsKeyMap = { } as const export const getPaintingsImageSizeOptionsLabel = (key: string): string => { - return getLabel(key, paintingsImageSizeOptionsKeyMap) + return getLabel(paintingsImageSizeOptionsKeyMap, key) } const paintingsQualityOptionsKeyMap = { @@ -226,7 +227,7 @@ const paintingsQualityOptionsKeyMap = { } as const export const getPaintingsQualityOptionsLabel = (key: string): string => { - return getLabel(key, paintingsQualityOptionsKeyMap) + return getLabel(paintingsQualityOptionsKeyMap, key) } const paintingsModerationOptionsKeyMap = { @@ -235,7 +236,7 @@ const paintingsModerationOptionsKeyMap = { } as const export const getPaintingsModerationOptionsLabel = (key: string): string => { - return getLabel(key, paintingsModerationOptionsKeyMap) + return getLabel(paintingsModerationOptionsKeyMap, key) } const paintingsBackgroundOptionsKeyMap = { @@ -245,7 +246,7 @@ const paintingsBackgroundOptionsKeyMap = { } as const export const getPaintingsBackgroundOptionsLabel = (key: string): string => { - return getLabel(key, paintingsBackgroundOptionsKeyMap) + return getLabel(paintingsBackgroundOptionsKeyMap, key) } const mcpTypeKeyMap = { @@ -256,7 +257,7 @@ const mcpTypeKeyMap = { } as const export const getMcpTypeLabel = (key: string): string => { - return getLabel(key, mcpTypeKeyMap) + return getLabel(mcpTypeKeyMap, key) } const miniappsStatusKeyMap = { @@ -265,7 +266,7 @@ const miniappsStatusKeyMap = { } as const export const getMiniappsStatusLabel = (key: string): string => { - return getLabel(key, miniappsStatusKeyMap) + return getLabel(miniappsStatusKeyMap, key) } const httpMessageKeyMap = { @@ -281,7 +282,7 @@ const httpMessageKeyMap = { } as const export const getHttpMessageLabel = (key: string): string => { - return getLabel(key, httpMessageKeyMap) + return getLabel(httpMessageKeyMap, key) } const reasoningEffortOptionsKeyMap: Record = { @@ -294,7 +295,7 @@ const reasoningEffortOptionsKeyMap: Record = { } as const export const getReasoningEffortOptionsLabel = (key: string): string => { - return getLabel(key, reasoningEffortOptionsKeyMap) + return getLabel(reasoningEffortOptionsKeyMap, key) } const fileFieldKeyMap = { @@ -304,7 +305,7 @@ const fileFieldKeyMap = { } as const export const getFileFieldLabel = (key: string): string => { - return getLabel(key, fileFieldKeyMap) + return getLabel(fileFieldKeyMap, key) } const builtInMcpDescriptionKeyMap: Record = { @@ -319,7 +320,7 @@ const builtInMcpDescriptionKeyMap: Record = { } as const export const getBuiltInMcpServerDescriptionLabel = (key: string): string => { - return getLabel(key, builtInMcpDescriptionKeyMap, t('settings.mcp.builtinServersDescriptions.no')) + return getLabel(builtInMcpDescriptionKeyMap, key, t('settings.mcp.builtinServersDescriptions.no')) } const builtinOcrProviderKeyMap = { @@ -329,5 +330,5 @@ const builtinOcrProviderKeyMap = { export const getBuiltinOcrProviderLabel = (key: BuiltinOcrProviderId) => { if (key === 'tesseract') return 'Tesseract' - else return getLabel(key, builtinOcrProviderKeyMap) + else return getLabel(builtinOcrProviderKeyMap, key) } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index b87aa8f686..86e278fadd 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -756,6 +756,7 @@ "footnote": "Reference content", "footnotes": "References", "fullscreen": "Entered fullscreen mode. Press F11 to exit", + "go_to_settings": "Go to settings", "i_know": "I know", "inspect": "Inspect", "knowledge_base": "Knowledge Base", @@ -824,6 +825,9 @@ "chunk": { "non_json": "Returned an invalid data format" }, + "insufficient_balance": "Please go to the {{provider}} to recharge.", + "no_api_key": "You have not configured an API key. Please go to the {{provider}} to obtain an API key.", + "quota_exceeded": "Your daily {{quota}} free quota has been exhausted. Please go to the {{provider}} to obtain an API key and configure the API key to continue using.", "response": "Something went wrong. Please check if you have set your API key in the Settings > Providers" }, "http": { @@ -1824,6 +1828,7 @@ "baidu-cloud": "Baidu Cloud", "burncloud": "BurnCloud", "cephalon": "Cephalon", + "cherryin": "CherryIN", "copilot": "GitHub Copilot", "dashscope": "Alibaba Cloud", "deepseek": "DeepSeek", @@ -1869,7 +1874,7 @@ "xirang": "State Cloud Xirang", "yi": "Yi", "zhinao": "360AI", - "zhipu": "ZHIPU AI" + "zhipu": "BigModel" }, "restore": { "confirm": { @@ -2911,7 +2916,8 @@ "modelscope": "ModelScope Community MCP Server", "official": "Official MCP Server Collection", "pulsemcp": "Pulse MCP Server", - "smithery": "Smithery MCP Tools" + "smithery": "Smithery MCP Tools", + "zhipu": "Curated MCP, Fast Integration" }, "name": "Name", "newServer": "MCP Server", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index d8c33f98ad..8c0570a9e8 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -756,6 +756,7 @@ "footnote": "引用内容", "footnotes": "脚注", "fullscreen": "全画面モードに入りました。F11キーで終了します", + "go_to_settings": "設定に移動", "i_know": "わかりました", "inspect": "検査", "knowledge_base": "ナレッジベース", @@ -824,6 +825,9 @@ "chunk": { "non_json": "無効なデータ形式が返されました" }, + "insufficient_balance": "{{provider}}でチャージしてください。", + "no_api_key": "APIキーが設定されていません。{{provider}}でAPIキーを取得してください。", + "quota_exceeded": "本日の{{quota}}無料クォータが使い果たされました。{{provider}}でAPIキーを取得し、APIキーを設定して使用を続けてください。", "response": "エラーが発生しました。APIキーが設定されていない場合は、設定 > プロバイダーでキーを設定してください" }, "http": { @@ -1824,6 +1828,7 @@ "baidu-cloud": "Baidu Cloud", "burncloud": "BurnCloud", "cephalon": "Cephalon", + "cherryin": "CherryIN", "copilot": "GitHub Copilot", "dashscope": "Alibaba Cloud", "deepseek": "DeepSeek", @@ -1869,7 +1874,7 @@ "xirang": "天翼クラウド 息壤", "yi": "零一万物", "zhinao": "360智脳", - "zhipu": "智譜AI" + "zhipu": "BigModel" }, "restore": { "confirm": { @@ -2911,7 +2916,8 @@ "modelscope": "魔搭コミュニティ MCP サーバー", "official": "公式 MCP サーバーコレクション", "pulsemcp": "Pulse MCP サーバー", - "smithery": "Smithery MCP ツール" + "smithery": "Smithery MCP ツール", + "zhipu": "厳選MCP、高速統合" }, "name": "名前", "newServer": "MCP サーバー", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 929c574906..d3211cfcad 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -756,6 +756,7 @@ "footnote": "Цитируемый контент", "footnotes": "Сноски", "fullscreen": "Вы вошли в полноэкранный режим. Нажмите F11 для выхода", + "go_to_settings": "Перейти в настройки", "i_know": "Я понял", "inspect": "Осмотреть", "knowledge_base": "База знаний", @@ -824,6 +825,9 @@ "chunk": { "non_json": "Вернулся недопустимый формат данных" }, + "insufficient_balance": "Пожалуйста, перейдите в {{provider}} для пополнения баланса.", + "no_api_key": "Вы не настроили ключ API. Пожалуйста, перейдите в {{provider}} для получения ключа API.", + "quota_exceeded": "Ваша ежедневная {{quota}} бесплатная квота исчерпана. Пожалуйста, перейдите в {{provider}} для получения ключа API и настройте ключ API для продолжения использования.", "response": "Что-то пошло не так. Пожалуйста, проверьте, установлен ли ваш ключ API в Настройки > Провайдеры" }, "http": { @@ -1824,6 +1828,7 @@ "baidu-cloud": "Baidu Cloud", "burncloud": "BurnCloud", "cephalon": "Cephalon", + "cherryin": "CherryIN", "copilot": "GitHub Copilot", "dashscope": "Alibaba Cloud", "deepseek": "DeepSeek", @@ -1869,7 +1874,7 @@ "xirang": "State Cloud Xirang", "yi": "Yi", "zhinao": "360AI", - "zhipu": "ZHIPU AI" + "zhipu": "BigModel" }, "restore": { "confirm": { @@ -2911,7 +2916,8 @@ "modelscope": "Сервер MCP сообщества ModelScope", "official": "Официальная коллекция серверов MCP", "pulsemcp": "Сервер Pulse MCP", - "smithery": "Инструменты Smithery MCP" + "smithery": "Инструменты Smithery MCP", + "zhipu": "Кураторские MCP, быстрая интеграция" }, "name": "Имя", "newServer": "MCP сервер", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 794ed78bc5..6361f705ed 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -756,6 +756,7 @@ "footnote": "引用内容", "footnotes": "引用内容", "fullscreen": "已进入全屏模式,按 F11 退出", + "go_to_settings": "前往设置", "i_know": "我知道了", "inspect": "检查", "knowledge_base": "知识库", @@ -824,6 +825,9 @@ "chunk": { "non_json": "返回了无效的数据格式" }, + "insufficient_balance": "请前往 {{provider}} 充值", + "no_api_key": "您未配置 API 密钥,请前往 {{provider}} 获取API密钥", + "quota_exceeded": "您今日免费配额已用尽,请前往 {{provider}} 获取API密钥,配置API密钥后继续使用", "response": "出错了,如果没有配置 API 密钥,请前往设置 > 模型提供商中配置密钥" }, "http": { @@ -1824,6 +1828,7 @@ "baidu-cloud": "百度云千帆", "burncloud": "BurnCloud", "cephalon": "Cephalon", + "cherryin": "CherryIN", "copilot": "GitHub Copilot", "dashscope": "阿里云百炼", "deepseek": "深度求索", @@ -1869,7 +1874,7 @@ "xirang": "天翼云息壤", "yi": "零一万物", "zhinao": "360 智脑", - "zhipu": "智谱 AI" + "zhipu": "智谱开放平台" }, "restore": { "confirm": { @@ -2911,7 +2916,8 @@ "modelscope": "魔搭社区 MCP 服务器", "official": "官方 MCP 服务器集合", "pulsemcp": "Pulse MCP 服务器", - "smithery": "Smithery MCP 工具" + "smithery": "Smithery MCP 工具", + "zhipu": "精选MCP,极速接入" }, "name": "名称", "newServer": "MCP 服务器", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 8622b64b3a..eaf621da8f 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -756,6 +756,7 @@ "footnote": "引用內容", "footnotes": "引用", "fullscreen": "已進入全螢幕模式,按 F11 結束", + "go_to_settings": "前往設定", "i_know": "我知道了", "inspect": "檢查", "knowledge_base": "知識庫", @@ -824,6 +825,9 @@ "chunk": { "non_json": "返回了無效的資料格式" }, + "insufficient_balance": "請前往 {{provider}} 充值", + "no_api_key": "您未配置 API 密钥,请前往 {{provider}} 获取API密钥", + "quota_exceeded": "您今日{{quota}}免费配额已用尽,请前往 {{provider}} 获取API密钥,配置API密钥后继续使用", "response": "出現錯誤。如果尚未設定 API 金鑰,請前往設定 > 模型提供者中設定金鑰" }, "http": { @@ -1824,6 +1828,7 @@ "baidu-cloud": "百度雲千帆", "burncloud": "BurnCloud", "cephalon": "Cephalon", + "cherryin": "CherryIN", "copilot": "GitHub Copilot", "dashscope": "阿里雲百鍊", "deepseek": "深度求索", @@ -1869,7 +1874,7 @@ "xirang": "天翼雲息壤", "yi": "零一萬物", "zhinao": "360 智腦", - "zhipu": "智譜 AI" + "zhipu": "智譜開放平台" }, "restore": { "confirm": { @@ -2911,7 +2916,8 @@ "modelscope": "魔搭社區 MCP 伺服器", "official": "官方 MCP 伺服器集合", "pulsemcp": "Pulse MCP 伺服器", - "smithery": "Smithery MCP 工具" + "smithery": "Smithery MCP 工具", + "zhipu": "精選MCP,極速接入" }, "name": "名稱", "newServer": "MCP 伺服器", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index ff51752b1e..ca305f0013 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -756,6 +756,7 @@ "footnote": "Παραπομπή", "footnotes": "Παραπομπές", "fullscreen": "Εισήχθη σε πλήρη οθόνη, πατήστε F11 για να έξω", + "go_to_settings": "Πηγαίνετε στις ρυθμίσεις", "i_know": "Το έχω καταλάβει", "inspect": "Επιθεώρηση", "knowledge_base": "Βάση Γνώσεων", @@ -824,6 +825,9 @@ "chunk": { "non_json": "Επέστρεψε μη έγκυρη μορφή δεδομένων" }, + "insufficient_balance": "Παρακαλώ μεταβείτε στο {{provider}} για επαναφόρτωση.", + "no_api_key": "Δεν έχετε ρυθμίσει το κλειδί API. Παρακαλώ μεταβείτε στο {{provider}} για να λάβετε ένα κλειδί API.", + "quota_exceeded": "Η ημερήσια δωρεάν ποσόστωση {{quota}} tokens σας έχει εξαντληθεί. Παρακαλώ μεταβείτε στο {{provider}} για να λάβετε ένα κλειδί API και να ρυθμίσετε το κλειδί API για να συνεχίσετε τη χρήση.", "response": "Σφάλμα. Εάν δεν έχετε ρυθμίσει το κλειδί API, πηγαίνετε στο ρυθμισμένα > παρέχοντας το πρόσωπο του μοντέλου" }, "http": { @@ -1824,6 +1828,7 @@ "baidu-cloud": "Baidu Cloud Qianfan", "burncloud": "BurnCloud", "cephalon": "Cephalon", + "cherryin": "CherryIN", "copilot": "GitHub Copilot", "dashscope": "AliCloud Bailian", "deepseek": "Βαθιά Αναζήτηση", @@ -1869,7 +1874,7 @@ "xirang": "China Telecom Xiran", "yi": "Zero One Wanyiwu", "zhinao": "360 Intelligent Brain", - "zhipu": "Zhipu AI" + "zhipu": "BigModel" }, "restore": { "confirm": { @@ -2911,7 +2916,8 @@ "modelscope": "Διακομιστής MCP κοινότητας ModelScope", "official": "Επίσημη συλλογή διακομιστών MCP", "pulsemcp": "Διακομιστής Pulse MCP", - "smithery": "Εργαλείο Smithery MCP" + "smithery": "Εργαλείο Smithery MCP", + "zhipu": "Επιλεγμένο MCP, Γρήγορη Ενσωμάτωση" }, "name": "Όνομα", "newServer": "Διακομιστής MCP", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 679e62b39f..890192771c 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -756,6 +756,7 @@ "footnote": "Nota al pie", "footnotes": "Notas al pie", "fullscreen": "En modo pantalla completa, presione F11 para salir", + "go_to_settings": "Ir a la configuración", "i_know": "Entendido", "inspect": "Inspeccionar", "knowledge_base": "Base de conocimiento", @@ -824,6 +825,9 @@ "chunk": { "non_json": "Devuelve un formato de datos no válido" }, + "insufficient_balance": "Por favor, vaya a {{provider}} para recargar.", + "no_api_key": "No ha configurado una clave API. Por favor, vaya a {{provider}} para obtener una clave API.", + "quota_exceeded": "Su cuota gratuita diaria de {{quota}} tokens se ha agotado. Por favor, vaya a {{provider}} para obtener una clave API y configurar la clave API para continuar usando.", "response": "Ha ocurrido un error, si no ha configurado la clave API, vaya a Configuración > Proveedor de modelos para configurar la clave" }, "http": { @@ -1824,6 +1828,7 @@ "baidu-cloud": "Baidu Nube Qiánfān", "burncloud": "BurnCloud", "cephalon": "Cephalon", + "cherryin": "CherryIN", "copilot": "GitHub Copiloto", "dashscope": "Álibaba Nube BaiLiàn", "deepseek": "Profundo Buscar", @@ -1869,7 +1874,7 @@ "xirang": "Telecom Nube XiRang", "yi": "Cero Uno Todo", "zhinao": "360 Inteligente", - "zhipu": "ZhiPu IA" + "zhipu": "BigModel" }, "restore": { "confirm": { @@ -2911,7 +2916,8 @@ "modelscope": "Servidor MCP de la comunidad ModelScope", "official": "Colección oficial de servidores MCP", "pulsemcp": "Servidor MCP Pulse", - "smithery": "Herramienta Smithery MCP" + "smithery": "Herramienta Smithery MCP", + "zhipu": "MCP Curado, Integración Rápida" }, "name": "Nombre", "newServer": "Servidor MCP", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index ba76018ae9..4adcb04286 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -756,6 +756,7 @@ "footnote": "Note de bas de page", "footnotes": "Notes de bas de page", "fullscreen": "Mode plein écran, appuyez sur F11 pour quitter", + "go_to_settings": "Aller aux paramètres", "i_know": "J'ai compris", "inspect": "Vérifier", "knowledge_base": "Base de connaissances", @@ -824,6 +825,9 @@ "chunk": { "non_json": "a renvoyé un format de données invalide" }, + "insufficient_balance": "Veuillez vous rendre sur {{provider}} pour recharger.", + "no_api_key": "Vous n'avez pas configuré de clé API. Veuillez vous rendre sur {{provider}} pour obtenir une clé API.", + "quota_exceeded": "Votre quota gratuit quotidien de {{quota}} tokens a été épuisé. Veuillez vous rendre sur {{provider}} pour obtenir une clé API et configurer la clé API pour continuer à utiliser.", "response": "Une erreur s'est produite, si l'API n'est pas configurée, veuillez aller dans Paramètres > Fournisseurs de modèles pour configurer la clé" }, "http": { @@ -1824,6 +1828,7 @@ "baidu-cloud": "Baidu Cloud Qianfan", "burncloud": "BurnCloud", "cephalon": "Cephalon", + "cherryin": "CherryIN", "copilot": "GitHub Copilote", "dashscope": "AliCloud BaiLian", "deepseek": "DeepSeek", @@ -1869,7 +1874,7 @@ "xirang": "CTyun XiRang", "yi": "ZéroUnInfini", "zhinao": "360 ZhiNao", - "zhipu": "ZhiPu IA" + "zhipu": "BigModel" }, "restore": { "confirm": { @@ -2911,7 +2916,8 @@ "modelscope": "Serveur MCP de la communauté ModelScope", "official": "Collection officielle de serveurs MCP", "pulsemcp": "Serveur MCP Pulse", - "smithery": "Outils Smithery MCP" + "smithery": "Outils Smithery MCP", + "zhipu": "MCP Curaté, Intégration Rapide" }, "name": "Nom", "newServer": "Сервер MCP", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 777d512173..3e3d83b030 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -756,6 +756,7 @@ "footnote": "Nota de rodapé", "footnotes": "Notas de rodapé", "fullscreen": "Entrou no modo de tela cheia, pressione F11 para sair", + "go_to_settings": "Ir para configurações", "i_know": "Entendi", "inspect": "Verificar", "knowledge_base": "Base de Conhecimento", @@ -824,6 +825,9 @@ "chunk": { "non_json": "Devolveu um formato de dados inválido" }, + "insufficient_balance": "Por favor, vá para {{provider}} para recarregar.", + "no_api_key": "Você não configurou uma chave API. Por favor, vá para {{provider}} para obter uma chave API.", + "quota_exceeded": "Sua cota gratuita diária de {{quota}} tokens foi esgotada. Por favor, vá para {{provider}} para obter uma chave API e configurar a chave API para continuar usando.", "response": "Ocorreu um erro, se a chave da API não foi configurada, por favor vá para Configurações > Provedores de Modelo para configurar a chave" }, "http": { @@ -1824,6 +1828,7 @@ "baidu-cloud": "Nuvem Baidu", "burncloud": "BurnCloud", "cephalon": "Cephalon", + "cherryin": "CherryIN", "copilot": "GitHub Copiloto", "dashscope": "Área de Atuação AliCloud", "deepseek": "Busca Profunda", @@ -1869,7 +1874,7 @@ "xirang": "XiRang do Nuvem Telecom", "yi": "ZeroUmTudo", "zhinao": "360 Inteligência Artificial", - "zhipu": "ZhiPu IA" + "zhipu": "BigModel" }, "restore": { "confirm": { @@ -2911,7 +2916,8 @@ "modelscope": "Servidor MCP da comunidade ModelScope", "official": "Coleção oficial de servidores MCP", "pulsemcp": "Servidor MCP Pulse", - "smithery": "Ferramentas Smithery MCP" + "smithery": "Ferramentas Smithery MCP", + "zhipu": "MCP Curado, Integração Rápida" }, "name": "Nome", "newServer": "Servidor MCP", diff --git a/src/renderer/src/pages/code/CodeToolsPage.tsx b/src/renderer/src/pages/code/CodeToolsPage.tsx index 22b98408dd..2b600547fe 100644 --- a/src/renderer/src/pages/code/CodeToolsPage.tsx +++ b/src/renderer/src/pages/code/CodeToolsPage.tsx @@ -57,6 +57,9 @@ const CodeToolsPage: FC = () => { if (isEmbeddingModel(m) || isRerankModel(m) || isTextToImageModel(m)) { return false } + if (m.provider === 'cherryin') { + return false + } if (selectedCliTool === 'claude-code') { return m.id.includes('claude') || CLAUDE_OFFICIAL_SUPPORTED_PROVIDERS.includes(m.provider) } diff --git a/src/renderer/src/pages/files/FilesPage.tsx b/src/renderer/src/pages/files/FilesPage.tsx index edf2b38273..3a7a3f445c 100644 --- a/src/renderer/src/pages/files/FilesPage.tsx +++ b/src/renderer/src/pages/files/FilesPage.tsx @@ -53,8 +53,11 @@ const FilesPage: FC = () => { const selectedFiles = await Promise.all(selectedFileIds.map((id) => FileManager.getFile(id))) const validFiles = selectedFiles.filter((file) => file !== null && file !== undefined) - const paintings = store.getState().paintings.paintings - const paintingsFiles = paintings.flatMap((p) => p.files) + const paintings = store.getState().paintings + const paintingsFiles = Object.values(paintings) + .flat() + .filter((painting) => painting?.files?.length > 0) + .flatMap((painting) => painting.files) const filesInPaintings = validFiles.filter((file) => paintingsFiles.some((p) => p.id === file.id)) diff --git a/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx b/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx index 9f71e0a84d..07a9e29107 100644 --- a/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx @@ -1,6 +1,6 @@ import { BaiduOutlined, GoogleOutlined } from '@ant-design/icons' import { loggerService } from '@logger' -import { BingLogo, BochaLogo, ExaLogo, SearXNGLogo, TavilyLogo } from '@renderer/components/Icons' +import { BingLogo, BochaLogo, ExaLogo, SearXNGLogo, TavilyLogo, ZhipuLogo } from '@renderer/components/Icons' import { QuickPanelListItem, useQuickPanel } from '@renderer/components/QuickPanel' import { isGeminiModel, isWebSearchModel } from '@renderer/config/models' import { isGeminiWebSearchProvider } from '@renderer/config/providers' @@ -50,6 +50,8 @@ const WebSearchButton: FC = ({ ref, assistant, ToolbarButton }) => { return case 'tavily': return + case 'zhipu': + return case 'searxng': return case 'local-baidu': diff --git a/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx index 1ed33e7475..514eb96634 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/ErrorBlock.tsx @@ -1,13 +1,17 @@ import { useTimer } from '@renderer/hooks/useTimer' -import { getHttpMessageLabel } from '@renderer/i18n/label' +import { getHttpMessageLabel, getProviderLabel } from '@renderer/i18n/label' +import { getProviderById } from '@renderer/services/ProviderService' import { useAppDispatch } from '@renderer/store' import { removeBlocksThunk } from '@renderer/store/thunk/messageThunk' import type { ErrorMessageBlock, Message } from '@renderer/types/newMessage' import { Alert as AntdAlert } from 'antd' import React from 'react' -import { useTranslation } from 'react-i18next' +import { Trans, useTranslation } from 'react-i18next' +import { Link } from 'react-router-dom' import styled from 'styled-components' +const HTTP_ERROR_CODES = [400, 401, 403, 404, 429, 500, 502, 503, 504] + interface Props { block: ErrorMessageBlock message: Message @@ -17,36 +21,68 @@ const ErrorBlock: React.FC = ({ block, message }) => { return } -const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock; message: Message }> = ({ block, message }) => { +const ErrorMessage: React.FC<{ block: ErrorMessageBlock }> = ({ block }) => { const { t, i18n } = useTranslation() + + const i18nKey = `error.${block.error?.i18nKey}` + const errorKey = `error.${block.error?.message}` + const errorStatus = block.error?.status + + if (i18n.exists(i18nKey)) { + const providerId = block.error?.providerId + if (providerId) { + return ( + + ) + }} + /> + ) + } + } + + if (i18n.exists(errorKey)) { + return t(errorKey) + } + + if (HTTP_ERROR_CODES.includes(errorStatus)) { + return ( +
+ {getHttpMessageLabel(errorStatus)} {block.error?.message} +
+ ) + } + + return block.error?.message || '' +} + +const ErrorDescription: React.FC<{ block: ErrorMessageBlock }> = ({ block }) => { + const { t } = useTranslation() + + if (block.error) { + return + } + + return <>{t('error.chat.response')} +} + +const MessageErrorInfo: React.FC<{ block: ErrorMessageBlock; message: Message }> = ({ block, message }) => { const dispatch = useAppDispatch() const { setTimeoutTimer } = useTimer() - const HTTP_ERROR_CODES = [400, 401, 403, 404, 429, 500, 502, 503, 504] - const onRemoveBlock = () => { setTimeoutTimer('onRemoveBlock', () => dispatch(removeBlocksThunk(message.topicId, message.id, [block.id])), 350) } - if (block.error && HTTP_ERROR_CODES.includes(block.error?.status)) { - return ( - - ) - } - - if (block?.error?.message) { - const errorKey = `error.${block.error.message}` - const pauseErrorLanguagePlaceholder = i18n.exists(errorKey) ? t(errorKey) : block.error.message - return - } - - return + return } type="error" closable onClose={onRemoveBlock} /> } const Alert = styled(AntdAlert)` diff --git a/src/renderer/src/pages/home/Messages/Message.tsx b/src/renderer/src/pages/home/Messages/Message.tsx index f6f6926b6f..10e01bb0b6 100644 --- a/src/renderer/src/pages/home/Messages/Message.tsx +++ b/src/renderer/src/pages/home/Messages/Message.tsx @@ -264,7 +264,7 @@ const MessageContainer = styled.div` const MessageContentContainer = styled(Scrollbar)` max-width: 100%; padding-left: 46px; - margin-top: 5px; + margin-top: 0; overflow-y: auto; ` diff --git a/src/renderer/src/pages/home/components/SelectModelButton.tsx b/src/renderer/src/pages/home/components/SelectModelButton.tsx index a912fb78e5..b4fa5e5074 100644 --- a/src/renderer/src/pages/home/components/SelectModelButton.tsx +++ b/src/renderer/src/pages/home/components/SelectModelButton.tsx @@ -49,7 +49,7 @@ const SelectModelButton: FC = ({ assistant }) => { return null } - const providerName = getProviderName(model?.provider) + const providerName = getProviderName(model) return ( diff --git a/src/renderer/src/pages/knowledge/KnowledgeContent.tsx b/src/renderer/src/pages/knowledge/KnowledgeContent.tsx index 6d0e350ac0..e7135f66f3 100644 --- a/src/renderer/src/pages/knowledge/KnowledgeContent.tsx +++ b/src/renderer/src/pages/knowledge/KnowledgeContent.tsx @@ -34,7 +34,7 @@ const KnowledgeContent: FC = ({ selectedBase }) => { const [progressMap, setProgressMap] = useState>(new Map()) const [preprocessMap, setPreprocessMap] = useState>(new Map()) - const providerName = getProviderName(base?.model.provider || '') + const providerName = getProviderName(base?.model) useEffect(() => { const handlers = [ diff --git a/src/renderer/src/pages/knowledge/items/KnowledgeDirectories.tsx b/src/renderer/src/pages/knowledge/items/KnowledgeDirectories.tsx index 7ece478b17..4868a3f483 100644 --- a/src/renderer/src/pages/knowledge/items/KnowledgeDirectories.tsx +++ b/src/renderer/src/pages/knowledge/items/KnowledgeDirectories.tsx @@ -43,7 +43,7 @@ const KnowledgeDirectories: FC = ({ selectedBase, progres selectedBase.id || '' ) - const providerName = getProviderName(base?.model.provider || '') + const providerName = getProviderName(base?.model) const disabled = !base?.version || !providerName const reversedItems = useMemo(() => [...directoryItems].reverse(), [directoryItems]) diff --git a/src/renderer/src/pages/knowledge/items/KnowledgeFiles.tsx b/src/renderer/src/pages/knowledge/items/KnowledgeFiles.tsx index aa461d6878..9dfcd955b6 100644 --- a/src/renderer/src/pages/knowledge/items/KnowledgeFiles.tsx +++ b/src/renderer/src/pages/knowledge/items/KnowledgeFiles.tsx @@ -62,7 +62,7 @@ const KnowledgeFiles: FC = ({ selectedBase, progressMap, return () => window.removeEventListener('resize', handleResize) }, []) - const providerName = getProviderName(base?.model.provider || '') + const providerName = getProviderName(base?.model) const disabled = !base?.version || !providerName const estimateSize = useCallback(() => 75, []) diff --git a/src/renderer/src/pages/knowledge/items/KnowledgeNotes.tsx b/src/renderer/src/pages/knowledge/items/KnowledgeNotes.tsx index aa340a5c9f..0fb1b14d10 100644 --- a/src/renderer/src/pages/knowledge/items/KnowledgeNotes.tsx +++ b/src/renderer/src/pages/knowledge/items/KnowledgeNotes.tsx @@ -31,7 +31,7 @@ const KnowledgeNotes: FC = ({ selectedBase }) => { selectedBase.id || '' ) - const providerName = getProviderName(base?.model.provider || '') + const providerName = getProviderName(base?.model) const disabled = !base?.version || !providerName const reversedItems = useMemo(() => [...noteItems].reverse(), [noteItems]) diff --git a/src/renderer/src/pages/knowledge/items/KnowledgeSitemaps.tsx b/src/renderer/src/pages/knowledge/items/KnowledgeSitemaps.tsx index 8bb6556672..06aa8d4c19 100644 --- a/src/renderer/src/pages/knowledge/items/KnowledgeSitemaps.tsx +++ b/src/renderer/src/pages/knowledge/items/KnowledgeSitemaps.tsx @@ -43,7 +43,7 @@ const KnowledgeSitemaps: FC = ({ selectedBase }) => { selectedBase.id || '' ) - const providerName = getProviderName(base?.model.provider || '') + const providerName = getProviderName(base?.model) const disabled = !base?.version || !providerName const reversedItems = useMemo(() => [...sitemapItems].reverse(), [sitemapItems]) diff --git a/src/renderer/src/pages/knowledge/items/KnowledgeUrls.tsx b/src/renderer/src/pages/knowledge/items/KnowledgeUrls.tsx index 7ffe98c9fb..93673ad7a8 100644 --- a/src/renderer/src/pages/knowledge/items/KnowledgeUrls.tsx +++ b/src/renderer/src/pages/knowledge/items/KnowledgeUrls.tsx @@ -40,7 +40,7 @@ const KnowledgeUrls: FC = ({ selectedBase }) => { selectedBase.id || '' ) - const providerName = getProviderName(base?.model.provider || '') + const providerName = getProviderName(base?.model) const disabled = !base?.version || !providerName const reversedItems = useMemo(() => [...urlItems].reverse(), [urlItems]) diff --git a/src/renderer/src/pages/paintings/AihubmixPage.tsx b/src/renderer/src/pages/paintings/AihubmixPage.tsx index 2c3075148c..6c9236e7dd 100644 --- a/src/renderer/src/pages/paintings/AihubmixPage.tsx +++ b/src/renderer/src/pages/paintings/AihubmixPage.tsx @@ -36,6 +36,7 @@ import { SettingHelpLink, SettingTitle } from '../settings' import Artboard from './components/Artboard' import PaintingsList from './components/PaintingsList' import { type ConfigItem, createModeConfigs, DEFAULT_PAINTING } from './config/aihubmixConfig' +import { checkProviderEnabled } from './utils' const logger = loggerService.withContext('AihubmixPage') @@ -43,9 +44,27 @@ const logger = loggerService.withContext('AihubmixPage') const modeConfigs = createModeConfigs() const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { - const [mode, setMode] = useState('generate') - const { addPainting, removePainting, updatePainting, persistentData } = usePaintings() - const filteredPaintings = useMemo(() => persistentData[mode] || [], [persistentData, mode]) + const [mode, setMode] = useState('aihubmix_image_generate') + const { + addPainting, + removePainting, + updatePainting, + aihubmix_image_generate, + aihubmix_image_remix, + aihubmix_image_edit, + aihubmix_image_upscale + } = usePaintings() + + const paintings = useMemo(() => { + return { + aihubmix_image_generate, + aihubmix_image_remix, + aihubmix_image_edit, + aihubmix_image_upscale + } + }, [aihubmix_image_generate, aihubmix_image_remix, aihubmix_image_edit, aihubmix_image_upscale]) + + const filteredPaintings = useMemo(() => paintings[mode] || [], [paintings, mode]) const [painting, setPainting] = useState(filteredPaintings[0] || DEFAULT_PAINTING) const [currentImageIndex, setCurrentImageIndex] = useState(0) const [isLoading, setIsLoading] = useState(false) @@ -88,7 +107,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { const getNewPainting = useCallback(() => { return { ...DEFAULT_PAINTING, - model: mode === 'generate' ? 'gpt-image-1' : 'V_3', + model: mode === 'aihubmix_image_generate' ? 'gpt-image-1' : 'V_3', id: uuid() } }, [mode]) @@ -143,6 +162,8 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { } const onGenerate = async () => { + await checkProviderEnabled(aihubmixProvider, t) + if (painting.files.length > 0) { const confirmed = await window.modal.confirm({ content: t('paintings.regenerate.confirm'), @@ -156,14 +177,6 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { const prompt = textareaRef.current?.resizableTextArea?.textArea?.value || '' updatePaintingState({ prompt }) - if (!aihubmixProvider.enabled) { - window.modal.error({ - content: t('error.provider_disabled'), - centered: true - }) - return - } - if (!aihubmixProvider.apiKey) { window.modal.error({ content: t('error.no_api_key'), @@ -188,7 +201,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { let url = aihubmixProvider.apiHost + `/ideogram/` + mode try { - if (mode === 'generate') { + if (mode === 'aihubmix_image_generate') { if (painting.model.startsWith('imagen-')) { const AI = new AiProvider(aihubmixProvider) const base64s = await AI.generateImage({ @@ -344,7 +357,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { body = JSON.stringify(requestData) headers['Content-Type'] = 'application/json' } - } else if (mode === 'remix') { + } else if (mode === 'aihubmix_image_remix') { if (!painting.imageFile) { window.modal.error({ content: t('paintings.image_file_required'), @@ -439,7 +452,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { form.append('image_file', fileMap[painting.imageFile] as unknown as Blob) body = form } - } else if (mode === 'upscale') { + } else if (mode === 'aihubmix_image_upscale') { if (!painting.imageFile) { window.modal.error({ content: t('paintings.image_file_required'), @@ -470,7 +483,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { } // 只针对非V3模型使用通用接口 - if (!painting.model?.includes('V_3') || mode === 'upscale') { + if (!painting.model?.includes('V_3') || mode === 'aihubmix_image_upscale') { // 直接调用自定义接口 const response = await fetch(url, { method: 'POST', headers, body }) @@ -617,8 +630,8 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { // 处理模式切换 const handleModeChange = (value: string) => { setMode(value as keyof PaintingsState) - if (persistentData[value as keyof PaintingsState] && persistentData[value as keyof PaintingsState].length > 0) { - setPainting(persistentData[value as keyof PaintingsState][0]) + if (paintings[value as keyof PaintingsState] && paintings[value as keyof PaintingsState].length > 0) { + setPainting(paintings[value as keyof PaintingsState][0]) } else { setPainting(DEFAULT_PAINTING) } @@ -843,7 +856,7 @@ const AihubmixPage: FC<{ Options: string[] }> = ({ Options }) => { - {providerOptions.map((provider) => ( diff --git a/src/renderer/src/pages/paintings/DmxapiPage.tsx b/src/renderer/src/pages/paintings/DmxapiPage.tsx index d9167ac69e..21a784397f 100644 --- a/src/renderer/src/pages/paintings/DmxapiPage.tsx +++ b/src/renderer/src/pages/paintings/DmxapiPage.tsx @@ -12,7 +12,7 @@ import { getProviderLabel } from '@renderer/i18n/label' import FileManager from '@renderer/services/FileManager' import { useAppDispatch } from '@renderer/store' import { setGenerating } from '@renderer/store/runtime' -import type { FileMetadata, PaintingsState } from '@renderer/types' +import type { FileMetadata } from '@renderer/types' import { convertToBase64, uuid } from '@renderer/utils' import { DmxapiPainting } from '@types' import { Avatar, Button, Input, InputNumber, Segmented, Select, Switch, Tooltip } from 'antd' @@ -37,13 +37,13 @@ import { STYLE_TYPE_OPTIONS, TOP_UP_URL } from './config/DmxapiConfig' +import { checkProviderEnabled } from './utils' const generateRandomSeed = () => Math.floor(Math.random() * 1000000).toString() const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { - const [mode] = useState('DMXAPIPaintings') - const { DMXAPIPaintings, addPainting, removePainting, updatePainting } = usePaintings() - const [painting, setPainting] = useState(DMXAPIPaintings?.[0] || DEFAULT_PAINTING) + const { dmxapi_paintings, addPainting, removePainting, updatePainting } = usePaintings() + const [painting, setPainting] = useState(dmxapi_paintings?.[0] || DEFAULT_PAINTING) const { t } = useTranslation() const providers = useAllProviders() const providerOptions = Options.map((option) => { @@ -144,7 +144,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { const updatePaintingState = (updates: Partial) => { const updatedPainting = { ...painting, ...updates } setPainting(updatedPainting) - updatePainting('DMXAPIPaintings', updatedPainting) + updatePainting('dmxapi_paintings', updatedPainting) } const getFirstModelInfo = (v: generationModeType) => { @@ -197,7 +197,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { id: uuid() } - setPainting(addPainting('DMXAPIPaintings', copyPainting)) + setPainting(addPainting('dmxapi_paintings', copyPainting)) } const onSelectModel = (modelId: string) => { @@ -316,7 +316,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { generationMode: v, model }) - const addedPainting = addPainting('DMXAPIPaintings', newPainting) + const addedPainting = addPainting('dmxapi_paintings', newPainting) setPainting(addedPainting) } else { // 否则更新当前painting @@ -333,7 +333,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { if (isLoading) { return } - setPainting(addPainting('DMXAPIPaintings', getNewPainting())) + setPainting(addPainting('dmxapi_paintings', getNewPainting())) } // 检查提供者状态函数 @@ -536,6 +536,12 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { if (isLoading) { return } + + if (!dmxapiProvider.enabled) { + checkProviderEnabled(dmxapiProvider, t) + return + } + try { // 获取提示词 const prompt = textareaRef.current?.resizableTextArea?.textArea?.value || '' @@ -624,23 +630,23 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { return } - const currentIndex = DMXAPIPaintings.findIndex((p) => p.id === paintingToDelete.id) + const currentIndex = dmxapi_paintings.findIndex((p) => p.id === paintingToDelete.id) if (currentIndex > 0) { - setPainting(DMXAPIPaintings[currentIndex - 1]) - } else if (DMXAPIPaintings.length > 1) { - setPainting(DMXAPIPaintings[1]) + setPainting(dmxapi_paintings[currentIndex - 1]) + } else if (dmxapi_paintings.length > 1) { + setPainting(dmxapi_paintings[1]) } } // 删除绘画 - await removePainting(mode, paintingToDelete) + await removePainting('dmxapi_paintings', paintingToDelete) // 检查是否删除空了 - if (!DMXAPIPaintings || DMXAPIPaintings.length === 1) { + if (!dmxapi_paintings || dmxapi_paintings.length === 1) { // 如果删除后没有绘画了,创建一个新的 const newPainting = getNewPainting() - const addedPainting = addPainting('DMXAPIPaintings', newPainting) + const addedPainting = addPainting('dmxapi_paintings', newPainting) setPainting(addedPainting) } } @@ -692,7 +698,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { } } - if (painting?.urls?.length > 0 || DMXAPIPaintings?.length > 1) { + if (painting?.urls?.length > 0 || dmxapi_paintings?.length > 1) { return null } else { return ( @@ -733,22 +739,22 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { return } - if (!DMXAPIPaintings || DMXAPIPaintings.length === 0) { + if (!dmxapi_paintings || dmxapi_paintings.length === 0) { const newPainting = getNewPainting() - addPainting('DMXAPIPaintings', newPainting) + addPainting('dmxapi_paintings', newPainting) setPainting(newPainting) } else if (painting && !painting.generationMode) { // 如果当前painting没有generationMode,添加默认值 const updatedPainting = { ...painting, generationMode: MODEOPTIONS[0].value } setPainting(updatedPainting) - updatePainting('DMXAPIPaintings', updatedPainting) + updatePainting('dmxapi_paintings', updatedPainting) } // 确保所有paintings都有generationMode属性 - DMXAPIPaintings.forEach((p) => { + dmxapi_paintings.forEach((p) => { if (!p.generationMode) { const updatedPainting = { ...p, generationMode: MODEOPTIONS[0].value } - updatePainting('DMXAPIPaintings', updatedPainting) + updatePainting('dmxapi_paintings', updatedPainting) } }) @@ -816,7 +822,7 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { /> - {providerOptions.map((provider) => ( @@ -1005,8 +1011,8 @@ const DmxapiPage: FC<{ Options: string[] }> = ({ Options }) => { = ({ Options }) => { const [mode, setMode] = useState('openai_image_generate') - const { addPainting, removePainting, updatePainting, newApiPaintings } = usePaintings() + const { addPainting, removePainting, updatePainting, openai_image_generate, openai_image_edit } = usePaintings() + + const newApiPaintings = useMemo(() => { + return { + openai_image_generate, + openai_image_edit + } + }, [openai_image_generate, openai_image_edit]) + const filteredPaintings = useMemo(() => newApiPaintings[mode] || [], [newApiPaintings, mode]) const [painting, setPainting] = useState(filteredPaintings[0] || DEFAULT_PAINTING) const [currentImageIndex, setCurrentImageIndex] = useState(0) @@ -216,6 +225,8 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => { } const onGenerate = async () => { + await checkProviderEnabled(newApiProvider, t) + if (painting.files.length > 0) { const confirmed = await window.modal.confirm({ content: t('paintings.regenerate.confirm'), @@ -229,14 +240,6 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => { const prompt = textareaRef.current?.resizableTextArea?.textArea?.value || '' updatePaintingState({ prompt }) - if (!newApiProvider.enabled) { - window.modal.error({ - content: t('error.provider_disabled'), - centered: true - }) - return - } - const AI = new AiProvider(newApiProvider) if (!AI.getApiKey()) { diff --git a/src/renderer/src/pages/paintings/PaintingsRoutePage.tsx b/src/renderer/src/pages/paintings/PaintingsRoutePage.tsx index 0de582065c..9c0ec8a2af 100644 --- a/src/renderer/src/pages/paintings/PaintingsRoutePage.tsx +++ b/src/renderer/src/pages/paintings/PaintingsRoutePage.tsx @@ -10,10 +10,11 @@ import DmxapiPage from './DmxapiPage' import NewApiPage from './NewApiPage' import SiliconPage from './SiliconPage' import TokenFluxPage from './TokenFluxPage' +import ZhipuPage from './ZhipuPage' const logger = loggerService.withContext('PaintingsRoutePage') -const Options = ['aihubmix', 'silicon', 'dmxapi', 'tokenflux', 'new-api'] +const Options = ['zhipu', 'aihubmix', 'silicon', 'dmxapi', 'tokenflux', 'new-api'] const PaintingsRoutePage: FC = () => { const params = useParams() @@ -29,7 +30,8 @@ const PaintingsRoutePage: FC = () => { return ( - } /> + } /> + } /> } /> } /> } /> diff --git a/src/renderer/src/pages/paintings/SiliconPage.tsx b/src/renderer/src/pages/paintings/SiliconPage.tsx index 1d09bcfb96..3d23e8fdbc 100644 --- a/src/renderer/src/pages/paintings/SiliconPage.tsx +++ b/src/renderer/src/pages/paintings/SiliconPage.tsx @@ -40,6 +40,7 @@ import SendMessageButton from '../home/Inputbar/SendMessageButton' import { SettingTitle } from '../settings' import Artboard from './components/Artboard' import PaintingsList from './components/PaintingsList' +import { checkProviderEnabled } from './utils' const logger = loggerService.withContext('SiliconPage') @@ -95,8 +96,8 @@ const DEFAULT_PAINTING: Painting = { const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { const { t } = useTranslation() - const { paintings, addPainting, removePainting, updatePainting } = usePaintings() - const [painting, setPainting] = useState(paintings[0] || DEFAULT_PAINTING) + const { siliconflow_paintings, addPainting, removePainting, updatePainting } = usePaintings() + const [painting, setPainting] = useState(siliconflow_paintings[0] || DEFAULT_PAINTING) const { theme } = useTheme() const providers = useAllProviders() const providerOptions = Options.map((option) => { @@ -113,6 +114,8 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { } } }) + + const siliconflowProvider = providers.find((p) => p.id === 'silicon') const [currentImageIndex, setCurrentImageIndex] = useState(0) const [isLoading, setIsLoading] = useState(false) @@ -141,7 +144,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { const updatePaintingState = (updates: Partial) => { const updatedPainting = { ...painting, ...updates } setPainting(updatedPainting) - updatePainting('paintings', updatedPainting) + updatePainting('siliconflow_paintings', updatedPainting) } const onSelectModel = (modelId: string) => { @@ -152,6 +155,8 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { } const onGenerate = async () => { + await checkProviderEnabled(siliconflowProvider!, t) + if (painting.files.length > 0) { const confirmed = await window.modal.confirm({ content: t('paintings.regenerate.confirm'), @@ -172,14 +177,6 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { const model = TEXT_TO_IMAGES_MODELS.find((m) => m.id === painting.model) const provider = getProviderByModel(model) - if (!provider.enabled) { - window.modal.error({ - content: t('error.provider_disabled'), - centered: true - }) - return - } - if (!provider.apiKey) { window.modal.error({ content: t('error.no_api_key'), @@ -280,16 +277,16 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { const onDeletePainting = (paintingToDelete: Painting) => { if (paintingToDelete.id === painting.id) { - const currentIndex = paintings.findIndex((p) => p.id === paintingToDelete.id) + const currentIndex = siliconflow_paintings.findIndex((p) => p.id === paintingToDelete.id) if (currentIndex > 0) { - setPainting(paintings[currentIndex - 1]) - } else if (paintings.length > 1) { - setPainting(paintings[1]) + setPainting(siliconflow_paintings[currentIndex - 1]) + } else if (siliconflow_paintings.length > 1) { + setPainting(siliconflow_paintings[1]) } } - removePainting('paintings', paintingToDelete) + removePainting('siliconflow_paintings', paintingToDelete) } const onSelectPainting = (newPainting: Painting) => { @@ -351,9 +348,9 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { } useEffect(() => { - if (paintings.length === 0) { + if (siliconflow_paintings.length === 0) { const newPainting = getNewPainting() - addPainting('paintings', newPainting) + addPainting('siliconflow_paintings', newPainting) setPainting(newPainting) } @@ -362,7 +359,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { clearTimeout(spaceClickTimer.current) } } - }, [paintings.length, addPainting]) + }, [siliconflow_paintings.length, addPainting]) return ( @@ -374,7 +371,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { size="small" className="nodrag" icon={} - onClick={() => setPainting(addPainting('paintings', getNewPainting()))}> + onClick={() => setPainting(addPainting('siliconflow_paintings', getNewPainting()))}> {t('paintings.button.new.image')} @@ -383,7 +380,7 @@ const SiliconPage: FC<{ Options: string[] }> = ({ Options }) => { {t('common.provider')} - {t('common.model')} + {providerOptions.map((provider) => ( + + + + {provider.label} + + + ))} + + + {t('common.model')} + + {IMAGE_SIZES.map((size) => ( + + {size.label} + + ))} + + {t('paintings.custom_size')} + + + + {/* 自定义尺寸输入框 */} + {isCustomSize && ( +
+ + onCustomSizeChange(value || undefined, 'width')} + min={512} + max={2048} + style={{ width: 80, flex: 1 }} + /> + x + onCustomSizeChange(value || undefined, 'height')} + min={512} + max={2048} + style={{ width: 80, flex: 1 }} + /> + px + +
+ 长宽均需满足512px-2048px之间, 需被16整除, 并保证最大像素数不超过2^21px +
+
+ )} +
+ + + +