Merge branch 'main' of github.com:CherryHQ/cherry-studio into v2
This commit is contained in:
@@ -10,6 +10,7 @@ import { getBinaryName } from '@main/utils/process'
|
||||
import type { TerminalConfig, TerminalConfigWithCommand } from '@shared/config/constant'
|
||||
import {
|
||||
codeTools,
|
||||
HOME_CHERRY_DIR,
|
||||
MACOS_TERMINALS,
|
||||
MACOS_TERMINALS_WITH_COMMANDS,
|
||||
terminalApps,
|
||||
@@ -66,7 +67,7 @@ class CodeToolsService {
|
||||
}
|
||||
|
||||
public async getBunPath() {
|
||||
const dir = path.join(os.homedir(), '.cherrystudio', 'bin')
|
||||
const dir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||
const bunName = await getBinaryName('bun')
|
||||
const bunPath = path.join(dir, bunName)
|
||||
return bunPath
|
||||
@@ -362,7 +363,7 @@ class CodeToolsService {
|
||||
|
||||
private async isPackageInstalled(cliTool: string): Promise<boolean> {
|
||||
const executableName = await this.getCliExecutableName(cliTool)
|
||||
const binDir = path.join(os.homedir(), '.cherrystudio', 'bin')
|
||||
const binDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||
const executablePath = path.join(binDir, executableName + (isWin ? '.exe' : ''))
|
||||
|
||||
// Ensure bin directory exists
|
||||
@@ -389,7 +390,7 @@ class CodeToolsService {
|
||||
logger.info(`${cliTool} is installed, getting current version`)
|
||||
try {
|
||||
const executableName = await this.getCliExecutableName(cliTool)
|
||||
const binDir = path.join(os.homedir(), '.cherrystudio', 'bin')
|
||||
const binDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||
const executablePath = path.join(binDir, executableName + (isWin ? '.exe' : ''))
|
||||
|
||||
const { stdout } = await execAsync(`"${executablePath}" --version`, {
|
||||
@@ -500,7 +501,7 @@ class CodeToolsService {
|
||||
try {
|
||||
const packageName = await this.getPackageName(cliTool)
|
||||
const bunPath = await this.getBunPath()
|
||||
const bunInstallPath = path.join(os.homedir(), '.cherrystudio')
|
||||
const bunInstallPath = path.join(os.homedir(), HOME_CHERRY_DIR)
|
||||
const registryUrl = await this.getNpmRegistryUrl()
|
||||
|
||||
const installEnvPrefix = isWin
|
||||
@@ -550,7 +551,7 @@ class CodeToolsService {
|
||||
const packageName = await this.getPackageName(cliTool)
|
||||
const bunPath = await this.getBunPath()
|
||||
const executableName = await this.getCliExecutableName(cliTool)
|
||||
const binDir = path.join(os.homedir(), '.cherrystudio', 'bin')
|
||||
const binDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||
const executablePath = path.join(binDir, executableName + (isWin ? '.exe' : ''))
|
||||
|
||||
logger.debug(`Package name: ${packageName}`)
|
||||
@@ -652,7 +653,7 @@ class CodeToolsService {
|
||||
baseCommand = `${baseCommand} ${configParams}`
|
||||
}
|
||||
|
||||
const bunInstallPath = path.join(os.homedir(), '.cherrystudio')
|
||||
const bunInstallPath = path.join(os.homedir(), HOME_CHERRY_DIR)
|
||||
|
||||
if (isInstalled) {
|
||||
// If already installed, run executable directly (with optional update message)
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
ToolListChangedNotificationSchema
|
||||
} from '@modelcontextprotocol/sdk/types.js'
|
||||
import { nanoid } from '@reduxjs/toolkit'
|
||||
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
||||
import type { MCPProgressEvent } from '@shared/config/types'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import { defaultAppHeaders } from '@shared/utils'
|
||||
@@ -715,7 +716,7 @@ class McpService {
|
||||
}
|
||||
|
||||
public async getInstallInfo() {
|
||||
const dir = path.join(os.homedir(), '.cherrystudio', 'bin')
|
||||
const dir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||
const uvName = await getBinaryName('uv')
|
||||
const bunName = await getBinaryName('bun')
|
||||
const uvPath = path.join(dir, uvName)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { homedir } from 'node:os'
|
||||
import { promisify } from 'node:util'
|
||||
|
||||
import { loggerService } from '@logger'
|
||||
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
||||
import * as fs from 'fs-extra'
|
||||
import * as path from 'path'
|
||||
|
||||
@@ -145,7 +146,7 @@ class OvmsManager {
|
||||
*/
|
||||
public async runOvms(): Promise<{ success: boolean; message?: string }> {
|
||||
const homeDir = homedir()
|
||||
const ovmsDir = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms')
|
||||
const ovmsDir = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms')
|
||||
const configPath = path.join(ovmsDir, 'models', 'config.json')
|
||||
const runBatPath = path.join(ovmsDir, 'run.bat')
|
||||
|
||||
@@ -195,7 +196,7 @@ class OvmsManager {
|
||||
*/
|
||||
public async getOvmsStatus(): Promise<'not-installed' | 'not-running' | 'running'> {
|
||||
const homeDir = homedir()
|
||||
const ovmsPath = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms', 'ovms.exe')
|
||||
const ovmsPath = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms', 'ovms.exe')
|
||||
|
||||
try {
|
||||
// Check if OVMS executable exists
|
||||
@@ -273,7 +274,7 @@ class OvmsManager {
|
||||
}
|
||||
|
||||
const homeDir = homedir()
|
||||
const configPath = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms', 'models', 'config.json')
|
||||
const configPath = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms', 'models', 'config.json')
|
||||
try {
|
||||
if (!(await fs.pathExists(configPath))) {
|
||||
logger.warn(`Config file does not exist: ${configPath}`)
|
||||
@@ -304,7 +305,7 @@ class OvmsManager {
|
||||
|
||||
private async applyModelPath(modelDirPath: string): Promise<boolean> {
|
||||
const homeDir = homedir()
|
||||
const patchDir = path.join(homeDir, '.cherrystudio', 'ovms', 'patch')
|
||||
const patchDir = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'patch')
|
||||
if (!(await fs.pathExists(patchDir))) {
|
||||
return true
|
||||
}
|
||||
@@ -355,7 +356,7 @@ class OvmsManager {
|
||||
logger.info(`Adding model: ${modelName} with ID: ${modelId}, Source: ${modelSource}, Task: ${task}`)
|
||||
|
||||
const homeDir = homedir()
|
||||
const ovdndDir = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms')
|
||||
const ovdndDir = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms')
|
||||
const pathModel = path.join(ovdndDir, 'models', modelId)
|
||||
|
||||
try {
|
||||
@@ -468,7 +469,7 @@ class OvmsManager {
|
||||
*/
|
||||
public async checkModelExists(modelId: string): Promise<boolean> {
|
||||
const homeDir = homedir()
|
||||
const ovmsDir = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms')
|
||||
const ovmsDir = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms')
|
||||
const configPath = path.join(ovmsDir, 'models', 'config.json')
|
||||
|
||||
try {
|
||||
@@ -495,7 +496,7 @@ class OvmsManager {
|
||||
*/
|
||||
public async updateModelConfig(modelName: string, modelId: string): Promise<boolean> {
|
||||
const homeDir = homedir()
|
||||
const ovmsDir = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms')
|
||||
const ovmsDir = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms')
|
||||
const configPath = path.join(ovmsDir, 'models', 'config.json')
|
||||
|
||||
try {
|
||||
@@ -548,7 +549,7 @@ class OvmsManager {
|
||||
*/
|
||||
public async getModels(): Promise<ModelConfig[]> {
|
||||
const homeDir = homedir()
|
||||
const ovmsDir = path.join(homeDir, '.cherrystudio', 'ovms', 'ovms')
|
||||
const ovmsDir = path.join(homeDir, HOME_CHERRY_DIR, 'ovms', 'ovms')
|
||||
const configPath = path.join(ovmsDir, 'models', 'config.json')
|
||||
|
||||
try {
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { Attributes, SpanEntity, TokenUsage, TraceCache } from '@mcp-trace/
|
||||
import { convertSpanToSpanEntity } from '@mcp-trace/trace-core'
|
||||
import { SpanStatusCode } from '@opentelemetry/api'
|
||||
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
||||
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
||||
import fs from 'fs/promises'
|
||||
import * as os from 'os'
|
||||
import * as path from 'path'
|
||||
@@ -17,7 +18,7 @@ class SpanCacheService implements TraceCache {
|
||||
pri
|
||||
|
||||
constructor() {
|
||||
this.fileDir = path.join(os.homedir(), '.cherrystudio', 'trace')
|
||||
this.fileDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'trace')
|
||||
}
|
||||
|
||||
createSpan: (span: ReadableSpan) => void = (span: ReadableSpan) => {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { configManager } from '@main/services/ConfigManager'
|
||||
import { locales } from '@main/utils/locales'
|
||||
import type EventEmitter from 'events'
|
||||
import http from 'http'
|
||||
import { URL } from 'url'
|
||||
@@ -7,6 +9,36 @@ import type { OAuthCallbackServerOptions } from './types'
|
||||
|
||||
const logger = loggerService.withContext('MCP:OAuthCallbackServer')
|
||||
|
||||
function getTranslation(key: string): string {
|
||||
const language = configManager.getLanguage()
|
||||
const localeData = locales[language]
|
||||
|
||||
if (!localeData) {
|
||||
logger.warn(`No locale data found for language: ${language}`)
|
||||
return key
|
||||
}
|
||||
|
||||
const translations = localeData.translation as any
|
||||
if (!translations) {
|
||||
logger.warn(`No translations found for language: ${language}`)
|
||||
return key
|
||||
}
|
||||
|
||||
const keys = key.split('.')
|
||||
let value = translations
|
||||
|
||||
for (const k of keys) {
|
||||
if (value && typeof value === 'object' && k in value) {
|
||||
value = value[k]
|
||||
} else {
|
||||
logger.warn(`Translation key not found: ${key} (failed at: ${k})`)
|
||||
return key // fallback to key if translation not found
|
||||
}
|
||||
}
|
||||
|
||||
return typeof value === 'string' ? value : key
|
||||
}
|
||||
|
||||
export class CallBackServer {
|
||||
private server: Promise<http.Server>
|
||||
private events: EventEmitter
|
||||
@@ -28,6 +60,55 @@ export class CallBackServer {
|
||||
if (code) {
|
||||
// Emit the code event
|
||||
this.events.emit('auth-code-received', code)
|
||||
// Send success response to browser
|
||||
const title = getTranslation('settings.mcp.oauth.callback.title')
|
||||
const message = getTranslation('settings.mcp.oauth.callback.message')
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
||||
res.end(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>${title}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background: #ffffff;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
h1 {
|
||||
color: #2d3748;
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
p {
|
||||
color: #718096;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>${title}</h1>
|
||||
<p>${message}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
} else {
|
||||
res.writeHead(400, { 'Content-Type': 'text/plain' })
|
||||
res.end('Missing authorization code')
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error processing OAuth callback:', error as Error)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { isWin } from '@main/constant'
|
||||
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
||||
import type { OcrOvConfig, OcrResult, SupportedOcrFile } from '@types'
|
||||
import { isImageFileMetadata } from '@types'
|
||||
import { exec } from 'child_process'
|
||||
@@ -13,7 +14,7 @@ import { OcrBaseService } from './OcrBaseService'
|
||||
const logger = loggerService.withContext('OvOcrService')
|
||||
const execAsync = promisify(exec)
|
||||
|
||||
const PATH_BAT_FILE = path.join(os.homedir(), '.cherrystudio', 'ovms', 'ovocr', 'run.npu.bat')
|
||||
const PATH_BAT_FILE = path.join(os.homedir(), HOME_CHERRY_DIR, 'ovms', 'ovocr', 'run.npu.bat')
|
||||
|
||||
export class OvOcrService extends OcrBaseService {
|
||||
constructor() {
|
||||
@@ -30,7 +31,7 @@ export class OvOcrService extends OcrBaseService {
|
||||
}
|
||||
|
||||
private getOvOcrPath(): string {
|
||||
return path.join(os.homedir(), '.cherrystudio', 'ovms', 'ovocr')
|
||||
return path.join(os.homedir(), HOME_CHERRY_DIR, 'ovms', 'ovocr')
|
||||
}
|
||||
|
||||
private getImgDir(): string {
|
||||
|
||||
@@ -5,7 +5,7 @@ import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
|
||||
import { loggerService } from '@logger'
|
||||
import { audioExts, documentExts, imageExts, MB, textExts, videoExts } from '@shared/config/constant'
|
||||
import { audioExts, documentExts, HOME_CHERRY_DIR, imageExts, MB, textExts, videoExts } from '@shared/config/constant'
|
||||
import type { FileMetadata, NotesTreeNode } from '@types'
|
||||
import { FileTypes } from '@types'
|
||||
import chardet from 'chardet'
|
||||
@@ -160,7 +160,7 @@ export function getNotesDir() {
|
||||
}
|
||||
|
||||
export function getConfigDir() {
|
||||
return path.join(os.homedir(), '.cherrystudio', 'config')
|
||||
return path.join(os.homedir(), HOME_CHERRY_DIR, 'config')
|
||||
}
|
||||
|
||||
export function getCacheDir() {
|
||||
@@ -172,7 +172,7 @@ export function getAppConfigDir(name: string) {
|
||||
}
|
||||
|
||||
export function getMcpDir() {
|
||||
return path.join(os.homedir(), '.cherrystudio', 'mcp')
|
||||
return path.join(os.homedir(), HOME_CHERRY_DIR, 'mcp')
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@ import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
|
||||
import { isLinux, isPortable, isWin } from '@main/constant'
|
||||
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
||||
import { app } from 'electron'
|
||||
|
||||
// Please don't import any other modules which is not node/electron built-in modules
|
||||
@@ -17,7 +18,7 @@ function hasWritePermission(path: string) {
|
||||
}
|
||||
|
||||
function getConfigDir() {
|
||||
return path.join(os.homedir(), '.cherrystudio', 'config')
|
||||
return path.join(os.homedir(), HOME_CHERRY_DIR, 'config')
|
||||
}
|
||||
|
||||
export function initAppDataDir() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
||||
import { spawn } from 'child_process'
|
||||
import fs from 'fs'
|
||||
import os from 'os'
|
||||
@@ -46,11 +47,11 @@ export async function getBinaryName(name: string): Promise<string> {
|
||||
|
||||
export async function getBinaryPath(name?: string): Promise<string> {
|
||||
if (!name) {
|
||||
return path.join(os.homedir(), '.cherrystudio', 'bin')
|
||||
return path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||
}
|
||||
|
||||
const binaryName = await getBinaryName(name)
|
||||
const binariesDir = path.join(os.homedir(), '.cherrystudio', 'bin')
|
||||
const binariesDir = path.join(os.homedir(), HOME_CHERRY_DIR, 'bin')
|
||||
const binariesDirExists = fs.existsSync(binariesDir)
|
||||
return binariesDirExists ? path.join(binariesDir, binaryName) : binaryName
|
||||
}
|
||||
|
||||
@@ -418,6 +418,8 @@ export function getAnthropicReasoningParams(assistant: Assistant, model: Model):
|
||||
/**
|
||||
* 获取 Gemini 推理参数
|
||||
* 从 GeminiAPIClient 中提取的逻辑
|
||||
* 注意:Gemini/GCP 端点所使用的 thinkingBudget 等参数应该按照驼峰命名法传递
|
||||
* 而在 Google 官方提供的 OpenAI 兼容端点中则使用蛇形命名法 thinking_budget
|
||||
*/
|
||||
export function getGeminiReasoningParams(assistant: Assistant, model: Model): Record<string, any> {
|
||||
if (!isReasoningModel(model)) {
|
||||
@@ -431,8 +433,8 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re
|
||||
if (reasoningEffort === undefined) {
|
||||
return {
|
||||
thinkingConfig: {
|
||||
include_thoughts: false,
|
||||
...(GEMINI_FLASH_MODEL_REGEX.test(model.id) ? { thinking_budget: 0 } : {})
|
||||
includeThoughts: false,
|
||||
...(GEMINI_FLASH_MODEL_REGEX.test(model.id) ? { thinkingBudget: 0 } : {})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -442,7 +444,7 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re
|
||||
if (effortRatio > 1) {
|
||||
return {
|
||||
thinkingConfig: {
|
||||
include_thoughts: true
|
||||
includeThoughts: true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,8 +454,8 @@ export function getGeminiReasoningParams(assistant: Assistant, model: Model): Re
|
||||
|
||||
return {
|
||||
thinkingConfig: {
|
||||
...(budget > 0 ? { thinking_budget: budget } : {}),
|
||||
include_thoughts: true
|
||||
...(budget > 0 ? { thinkingBudget: budget } : {}),
|
||||
includeThoughts: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ import {
|
||||
updateMessageAndBlocksThunk,
|
||||
updateTranslationBlockThunk
|
||||
} from '@renderer/store/thunk/messageThunk'
|
||||
import type { Assistant, Model, Topic, TranslateLanguageCode } from '@renderer/types'
|
||||
import { type Assistant, type Model, objectKeys, type Topic, type TranslateLanguageCode } from '@renderer/types'
|
||||
import type { Message, MessageBlock } from '@renderer/types/newMessage'
|
||||
import { MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage'
|
||||
import { abortCompletion } from '@renderer/utils/abortController'
|
||||
import { throttle } from 'lodash'
|
||||
import { difference, throttle } from 'lodash'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
const logger = loggerService.withContext('UseMessageOperations')
|
||||
@@ -82,10 +82,12 @@ export function useMessageOperations(topic: Topic) {
|
||||
logger.error('[editMessage] Topic prop is not valid.')
|
||||
return
|
||||
}
|
||||
|
||||
const uiStates = ['multiModelMessageStyle', 'foldSelected'] as const satisfies (keyof Message)[]
|
||||
const extraUpdate = difference(objectKeys(updates), uiStates)
|
||||
const isUiUpdateOnly = extraUpdate.length === 0
|
||||
const messageUpdates: Partial<Message> & Pick<Message, 'id'> = {
|
||||
id: messageId,
|
||||
updatedAt: new Date().toISOString(),
|
||||
updatedAt: isUiUpdateOnly ? undefined : new Date().toISOString(),
|
||||
...updates
|
||||
}
|
||||
|
||||
|
||||
@@ -3863,6 +3863,12 @@
|
||||
"usage": "Usage",
|
||||
"version": "Version"
|
||||
},
|
||||
"oauth": {
|
||||
"callback": {
|
||||
"message": "You can close this page and return to Cherry Studio",
|
||||
"title": "Authentication Successful"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"arguments": "Arguments",
|
||||
"availablePrompts": "Available Prompts",
|
||||
|
||||
@@ -3863,6 +3863,12 @@
|
||||
"usage": "用法",
|
||||
"version": "版本"
|
||||
},
|
||||
"oauth": {
|
||||
"callback": {
|
||||
"message": "您可以关闭此页面并返回 Cherry Studio",
|
||||
"title": "认证成功"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"arguments": "参数",
|
||||
"availablePrompts": "可用提示",
|
||||
|
||||
@@ -3863,6 +3863,12 @@
|
||||
"usage": "用法",
|
||||
"version": "版本"
|
||||
},
|
||||
"oauth": {
|
||||
"callback": {
|
||||
"message": "您可以關閉此頁面並返回 Cherry Studio",
|
||||
"title": "認證成功"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"arguments": "參數",
|
||||
"availablePrompts": "可用提示",
|
||||
|
||||
@@ -3863,6 +3863,12 @@
|
||||
"usage": "Verwendung",
|
||||
"version": "Version"
|
||||
},
|
||||
"oauth": {
|
||||
"callback": {
|
||||
"message": "Sie können diese Seite schließen und zu Cherry Studio zurückkehren",
|
||||
"title": "Authentifizierung erfolgreich"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"arguments": "Parameter",
|
||||
"availablePrompts": "Verfügbare Prompts",
|
||||
|
||||
@@ -3863,6 +3863,12 @@
|
||||
"usage": "Χρήση",
|
||||
"version": "Έκδοση"
|
||||
},
|
||||
"oauth": {
|
||||
"callback": {
|
||||
"message": "Μπορείτε να κλείσετε αυτήν τη σελίδα και να επιστρέψετε στο Cherry Studio",
|
||||
"title": "Επιτυχής Ταυτοποίηση"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"arguments": "Ορίσματα",
|
||||
"availablePrompts": "Διαθέσιμες Υποδείξεις",
|
||||
|
||||
@@ -3863,6 +3863,12 @@
|
||||
"usage": "Uso",
|
||||
"version": "Versión"
|
||||
},
|
||||
"oauth": {
|
||||
"callback": {
|
||||
"message": "Puede cerrar esta página y volver a Cherry Studio",
|
||||
"title": "Autenticación Exitosa"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"arguments": "Argumentos",
|
||||
"availablePrompts": "Indicaciones disponibles",
|
||||
|
||||
@@ -3863,6 +3863,12 @@
|
||||
"usage": "Utilisation",
|
||||
"version": "Version"
|
||||
},
|
||||
"oauth": {
|
||||
"callback": {
|
||||
"message": "Vous pouvez fermer cette page et retourner à Cherry Studio",
|
||||
"title": "Authentification Réussie"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"arguments": "Arguments",
|
||||
"availablePrompts": "Invites disponibles",
|
||||
|
||||
@@ -3863,6 +3863,12 @@
|
||||
"usage": "使用法",
|
||||
"version": "バージョン"
|
||||
},
|
||||
"oauth": {
|
||||
"callback": {
|
||||
"message": "このページを閉じてCherry Studioに戻ることができます",
|
||||
"title": "認証成功"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"arguments": "引数",
|
||||
"availablePrompts": "利用可能なプロンプト",
|
||||
|
||||
@@ -3863,6 +3863,12 @@
|
||||
"usage": "Uso",
|
||||
"version": "Versão"
|
||||
},
|
||||
"oauth": {
|
||||
"callback": {
|
||||
"message": "Você pode fechar esta página e retornar ao Cherry Studio",
|
||||
"title": "Autenticação Bem-Sucedida"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"arguments": "Argumentos",
|
||||
"availablePrompts": "Dicas disponíveis",
|
||||
|
||||
@@ -3863,6 +3863,12 @@
|
||||
"usage": "Использование",
|
||||
"version": "Версия"
|
||||
},
|
||||
"oauth": {
|
||||
"callback": {
|
||||
"message": "Вы можете закрыть эту страницу и вернуться в Cherry Studio",
|
||||
"title": "Аутентификация Успешна"
|
||||
}
|
||||
},
|
||||
"prompts": {
|
||||
"arguments": "Аргументы",
|
||||
"availablePrompts": "Доступные подсказки",
|
||||
|
||||
@@ -84,7 +84,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
||||
</Tooltip>
|
||||
)}
|
||||
{isTopNavbar && !showAssistants && (
|
||||
<Tooltip placement="bottom" content={t('navbar.show_sidebar')} delay={800}>
|
||||
<Tooltip placement="bottom" content={t('navbar.show_sidebar')} delay={800} placement="right">
|
||||
<NavbarIcon onClick={() => toggleShowAssistants()} style={{ marginRight: 8 }}>
|
||||
<PanelRightClose size={18} />
|
||||
</NavbarIcon>
|
||||
|
||||
@@ -5,11 +5,13 @@ import { useTopicMessages } from '@renderer/hooks/useMessageOperations'
|
||||
import { getGroupedMessages } from '@renderer/services/MessagesService'
|
||||
import { type Topic, TopicType } from '@renderer/types'
|
||||
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
|
||||
import { Spin } from 'antd'
|
||||
import { memo, useMemo } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import MessageGroup from './MessageGroup'
|
||||
import NarrowLayout from './NarrowLayout'
|
||||
import PermissionModeDisplay from './PermissionModeDisplay'
|
||||
import { MessagesContainer, ScrollContainer } from './shared'
|
||||
|
||||
const logger = loggerService.withContext('AgentSessionMessages')
|
||||
@@ -67,8 +69,12 @@ const AgentSessionMessages: React.FC<Props> = ({ agentId, sessionId }) => {
|
||||
groupedMessages.map(([key, groupMessages]) => (
|
||||
<MessageGroup key={key} messages={groupMessages} topic={derivedTopic} />
|
||||
))
|
||||
) : session ? (
|
||||
<PermissionModeDisplay session={session} agentId={agentId} />
|
||||
) : (
|
||||
<EmptyState>{session ? 'No messages yet.' : 'Loading session...'}</EmptyState>
|
||||
<LoadingState>
|
||||
<Spin size="small" />
|
||||
</LoadingState>
|
||||
)}
|
||||
</ScrollContainer>
|
||||
</ContextMenu>
|
||||
@@ -77,10 +83,10 @@ const AgentSessionMessages: React.FC<Props> = ({ agentId, sessionId }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const EmptyState = styled.div`
|
||||
color: var(--color-text-3);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
const LoadingState = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px 0;
|
||||
`
|
||||
|
||||
|
||||
@@ -301,7 +301,7 @@ const BuiltinError = ({ error }: { error: SerializedError }) => {
|
||||
)
|
||||
}
|
||||
|
||||
// 作为 base,渲染公共字段,应当在 ErrorDetailList 中渲染
|
||||
// Base component to render common fields, should be rendered inside ErrorDetailList
|
||||
const AiSdkErrorBase = ({ error }: { error: SerializedAiSdkError }) => {
|
||||
const { t } = useTranslation()
|
||||
const { highlightCode } = useCodeStyle()
|
||||
@@ -366,6 +366,13 @@ const AiSdkError = ({ error }: { error: SerializedAiSdkErrorUnion }) => {
|
||||
|
||||
{isSerializedAiSdkAPICallError(error) && (
|
||||
<>
|
||||
{error.responseBody && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.responseBody')}:</ErrorDetailLabel>
|
||||
<CodeViewer value={error.responseBody} className="source-view" language="json" expanded />
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
|
||||
{error.requestBodyValues && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.requestBodyValues')}:</ErrorDetailLabel>
|
||||
@@ -390,13 +397,6 @@ const AiSdkError = ({ error }: { error: SerializedAiSdkErrorUnion }) => {
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
|
||||
{error.responseBody && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.responseBody')}:</ErrorDetailLabel>
|
||||
<CodeViewer value={error.responseBody} className="source-view" language="json" expanded />
|
||||
</ErrorDetailItem>
|
||||
)}
|
||||
|
||||
{error.data && (
|
||||
<ErrorDetailItem>
|
||||
<ErrorDetailLabel>{t('error.data')}:</ErrorDetailLabel>
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { permissionModeCards } from '@renderer/config/agent'
|
||||
import SessionSettingsPopup from '@renderer/pages/settings/AgentSettings/SessionSettingsPopup'
|
||||
import type { GetAgentSessionResponse, PermissionMode } from '@renderer/types'
|
||||
import { FileEdit, Lightbulb, Shield, ShieldOff } from 'lucide-react'
|
||||
import type { FC } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface Props {
|
||||
session: GetAgentSessionResponse
|
||||
agentId: string
|
||||
}
|
||||
|
||||
const getPermissionModeConfig = (mode: PermissionMode) => {
|
||||
switch (mode) {
|
||||
case 'default':
|
||||
return {
|
||||
icon: <Shield size={18} color="var(--color-primary)" />
|
||||
}
|
||||
case 'plan':
|
||||
return {
|
||||
icon: <Lightbulb size={18} color="#faad14" />
|
||||
}
|
||||
case 'acceptEdits':
|
||||
return {
|
||||
icon: <FileEdit size={18} color="#52c41a" />
|
||||
}
|
||||
case 'bypassPermissions':
|
||||
return {
|
||||
icon: <ShieldOff size={18} color="var(--color-error)" />
|
||||
}
|
||||
default:
|
||||
return {
|
||||
icon: <Shield size={18} color="var(--color-primary)" />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PermissionModeDisplay: FC<Props> = ({ session, agentId }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const permissionMode = session?.configuration?.permission_mode ?? 'default'
|
||||
|
||||
const modeCard = useMemo(() => {
|
||||
return permissionModeCards.find((card) => card.mode === permissionMode)
|
||||
}, [permissionMode])
|
||||
|
||||
const modeConfig = useMemo(() => getPermissionModeConfig(permissionMode), [permissionMode])
|
||||
|
||||
const handleClick = () => {
|
||||
SessionSettingsPopup.show({
|
||||
agentId,
|
||||
sessionId: session.id,
|
||||
tab: 'tooling'
|
||||
})
|
||||
}
|
||||
|
||||
if (!modeCard) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={handleClick}
|
||||
className="mx-2 cursor-pointer rounded-lg border-[0.5px] border-[var(--color-border)] px-3 py-2">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="flex shrink-0 items-center justify-center">{modeConfig.icon}</div>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||
<div className="overflow-hidden text-ellipsis whitespace-nowrap font-semibold text-[var(--color-text-1)] text-xs">
|
||||
{t(modeCard.titleKey, modeCard.titleFallback)}
|
||||
</div>
|
||||
<div className="overflow-hidden text-ellipsis whitespace-nowrap text-[11px] text-[var(--color-text-2)] leading-[1.4]">
|
||||
{t(modeCard.descriptionKey, modeCard.descriptionFallback)}{' '}
|
||||
{t(modeCard.behaviorKey, modeCard.behaviorFallback)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PermissionModeDisplay
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { CollapseProps } from 'antd'
|
||||
import { Tag } from 'antd'
|
||||
import { Popover, Tag } from 'antd'
|
||||
import { Terminal } from 'lucide-react'
|
||||
|
||||
import { ToolTitle } from './GenericTools'
|
||||
import type { BashToolInput as BashToolInputType, BashToolOutput as BashToolOutputType } from './types'
|
||||
|
||||
const MAX_TAG_LENGTH = 100
|
||||
|
||||
export function BashTool({
|
||||
input,
|
||||
output
|
||||
@@ -15,6 +17,13 @@ export function BashTool({
|
||||
// 如果有输出,计算输出行数
|
||||
const outputLines = output ? output.split('\n').length : 0
|
||||
|
||||
// 处理命令字符串的截断
|
||||
const command = input.command
|
||||
const needsTruncate = command.length > MAX_TAG_LENGTH
|
||||
const displayCommand = needsTruncate ? `${command.slice(0, MAX_TAG_LENGTH)}...` : command
|
||||
|
||||
const tagContent = <Tag className="whitespace-pre-wrap break-all font-mono">{displayCommand}</Tag>
|
||||
|
||||
return {
|
||||
key: 'tool',
|
||||
label: (
|
||||
@@ -26,7 +35,15 @@ export function BashTool({
|
||||
stats={output ? `${outputLines} ${outputLines === 1 ? 'line' : 'lines'}` : undefined}
|
||||
/>
|
||||
<div className="mt-1">
|
||||
<Tag className="whitespace-pre-wrap break-all font-mono">{input.command}</Tag>
|
||||
{needsTruncate ? (
|
||||
<Popover
|
||||
content={<div className="max-w-xl whitespace-pre-wrap break-all font-mono">{command}</div>}
|
||||
trigger="hover">
|
||||
{tagContent}
|
||||
</Popover>
|
||||
) : (
|
||||
tagContent
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
|
||||
@@ -95,7 +95,7 @@ const HeaderNavbar: FC<Props> = ({
|
||||
paddingRight: 0,
|
||||
minWidth: 'auto'
|
||||
}}>
|
||||
<Tooltip placement="bottom" content={t('navbar.show_sidebar')} delay={800}>
|
||||
<Tooltip placement="bottom" content={t('navbar.show_sidebar')} delay={800} placement="right">
|
||||
<NavbarIcon onClick={() => toggleShowAssistants()}>
|
||||
<PanelRightClose size={18} />
|
||||
</NavbarIcon>
|
||||
|
||||
@@ -181,7 +181,7 @@ const HeaderNavbar = ({ notesTree, getCurrentNoteContent, onToggleStar, onExpand
|
||||
</Tooltip>
|
||||
)}
|
||||
{!showWorkspace && (
|
||||
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={0.8}>
|
||||
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={0.8} placement="right">
|
||||
<NavbarIcon onClick={handleToggleShowWorkspace}>
|
||||
<PanelRightClose size={18} />
|
||||
</NavbarIcon>
|
||||
|
||||
@@ -459,11 +459,22 @@ export const ToolingSettings: FC<AgentToolingSettingsProps> = ({ agentBase, upda
|
||||
key={server.id}
|
||||
className="border border-default-200"
|
||||
title={
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 flex-col">
|
||||
<span className="truncate font-medium text-sm">{server.name}</span>
|
||||
<div className="flex items-center justify-between gap-2 py-3">
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
{server.logoUrl && (
|
||||
<img
|
||||
src={server.logoUrl}
|
||||
alt={`${server.name} logo`}
|
||||
className="h-5 w-5 rounded object-cover"
|
||||
/>
|
||||
)}
|
||||
<span className="truncate font-medium text-sm">{server.name}</span>
|
||||
</div>
|
||||
{server.description ? (
|
||||
<span className="line-clamp-2 text-foreground-500 text-xs">{server.description}</span>
|
||||
<span className="line-clamp-2 whitespace-pre-wrap break-all text-foreground-500 text-xs">
|
||||
{server.description}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<Switch
|
||||
|
||||
@@ -263,6 +263,7 @@ export const PluginBrowser: FC<PluginBrowserProps> = ({
|
||||
items={pluginTypeTabItems}
|
||||
className="w-full"
|
||||
size="small"
|
||||
centered
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 171,
|
||||
version: 172,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@@ -2628,132 +2628,6 @@ const migrateConfig = {
|
||||
return state
|
||||
}
|
||||
},
|
||||
'162': (state: RootState) => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
if (state?.agents?.agents) {
|
||||
// @ts-ignore
|
||||
state.assistants.presets = [...state.agents.agents]
|
||||
// @ts-ignore
|
||||
delete state.agents.agents
|
||||
}
|
||||
|
||||
if (state.settings.sidebarIcons) {
|
||||
state.settings.sidebarIcons.visible = state.settings.sidebarIcons.visible.map((icon) => {
|
||||
// @ts-ignore
|
||||
return icon === 'agents' ? 'store' : icon
|
||||
})
|
||||
state.settings.sidebarIcons.disabled = state.settings.sidebarIcons.disabled.map((icon) => {
|
||||
// @ts-ignore
|
||||
return icon === 'agents' ? 'store' : icon
|
||||
})
|
||||
}
|
||||
|
||||
state.llm.providers.forEach((provider) => {
|
||||
if (provider.anthropicApiHost) {
|
||||
return
|
||||
}
|
||||
|
||||
switch (provider.id) {
|
||||
case 'deepseek':
|
||||
provider.anthropicApiHost = 'https://api.deepseek.com/anthropic'
|
||||
break
|
||||
case 'moonshot':
|
||||
provider.anthropicApiHost = 'https://api.moonshot.cn/anthropic'
|
||||
break
|
||||
case 'zhipu':
|
||||
provider.anthropicApiHost = 'https://open.bigmodel.cn/api/anthropic'
|
||||
break
|
||||
case 'dashscope':
|
||||
provider.anthropicApiHost = 'https://dashscope.aliyuncs.com/api/v2/apps/claude-code-proxy'
|
||||
break
|
||||
case 'modelscope':
|
||||
provider.anthropicApiHost = 'https://api-inference.modelscope.cn'
|
||||
break
|
||||
case 'aihubmix':
|
||||
provider.anthropicApiHost = 'https://aihubmix.com'
|
||||
break
|
||||
case 'new-api':
|
||||
provider.anthropicApiHost = 'http://localhost:3000'
|
||||
break
|
||||
case 'grok':
|
||||
provider.anthropicApiHost = 'https://api.x.ai'
|
||||
}
|
||||
})
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 162 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'163': (state: RootState) => {
|
||||
try {
|
||||
addOcrProvider(state, BUILTIN_OCR_PROVIDERS_MAP.ovocr)
|
||||
state.llm.providers.forEach((provider) => {
|
||||
if (provider.id === 'cherryin') {
|
||||
provider.anthropicApiHost = 'https://open.cherryin.net'
|
||||
}
|
||||
})
|
||||
state.paintings.ovms_paintings = []
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 163 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'164': (state: RootState) => {
|
||||
try {
|
||||
addMiniApp(state, 'ling')
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 164 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'165': (state: RootState) => {
|
||||
try {
|
||||
addMiniApp(state, 'huggingchat')
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 165 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'166': (state: RootState) => {
|
||||
try {
|
||||
if (state.assistants.presets === undefined) {
|
||||
state.assistants.presets = []
|
||||
}
|
||||
state.assistants.presets.forEach((preset) => {
|
||||
if (!preset.settings) {
|
||||
preset.settings = DEFAULT_ASSISTANT_SETTINGS
|
||||
} else if (!preset.settings.toolUseMode) {
|
||||
preset.settings.toolUseMode = DEFAULT_ASSISTANT_SETTINGS.toolUseMode
|
||||
}
|
||||
})
|
||||
// 更新阿里云百炼的 Anthropic API 地址
|
||||
const dashscopeProvider = state.llm.providers.find((provider) => provider.id === 'dashscope')
|
||||
if (dashscopeProvider) {
|
||||
dashscopeProvider.anthropicApiHost = 'https://dashscope.aliyuncs.com/apps/anthropic'
|
||||
}
|
||||
|
||||
state.llm.providers.forEach((provider) => {
|
||||
if (provider.id === SystemProviderIds['new-api'] && provider.type !== 'new-api') {
|
||||
provider.type = 'new-api'
|
||||
}
|
||||
if (provider.id === SystemProviderIds.longcat) {
|
||||
// https://longcat.chat/platform/docs/zh/#anthropic-api-%E6%A0%BC%E5%BC%8F
|
||||
if (!provider.anthropicApiHost) {
|
||||
provider.anthropicApiHost = 'https://api.longcat.chat/anthropic'
|
||||
}
|
||||
}
|
||||
})
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 166 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'167': (state: RootState) => {
|
||||
try {
|
||||
addProvider(state, 'huggingface')
|
||||
@@ -2822,6 +2696,98 @@ const migrateConfig = {
|
||||
logger.error('migrate 171 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'172': (state: RootState) => {
|
||||
try {
|
||||
// Add ling and huggingchat mini apps
|
||||
addMiniApp(state, 'ling')
|
||||
addMiniApp(state, 'huggingchat')
|
||||
|
||||
// Add ovocr provider and clear ovms paintings
|
||||
addOcrProvider(state, BUILTIN_OCR_PROVIDERS_MAP.ovocr)
|
||||
if (isEmpty(state.paintings.ovms_paintings)) {
|
||||
state.paintings.ovms_paintings = []
|
||||
}
|
||||
|
||||
// Migrate agents to assistants presets
|
||||
// @ts-ignore
|
||||
if (state?.agents?.agents) {
|
||||
// @ts-ignore
|
||||
state.assistants.presets = [...state.agents.agents]
|
||||
// @ts-ignore
|
||||
delete state.agents.agents
|
||||
}
|
||||
|
||||
// Initialize assistants presets
|
||||
if (state.assistants.presets === undefined) {
|
||||
state.assistants.presets = []
|
||||
}
|
||||
|
||||
// Migrate assistants presets
|
||||
state.assistants.presets.forEach((preset) => {
|
||||
if (!preset.settings) {
|
||||
preset.settings = DEFAULT_ASSISTANT_SETTINGS
|
||||
} else if (!preset.settings.toolUseMode) {
|
||||
preset.settings.toolUseMode = DEFAULT_ASSISTANT_SETTINGS.toolUseMode
|
||||
}
|
||||
})
|
||||
|
||||
// Migrate sidebar icons
|
||||
if (state.settings.sidebarIcons) {
|
||||
state.settings.sidebarIcons.visible = state.settings.sidebarIcons.visible.map((icon) => {
|
||||
// @ts-ignore
|
||||
return icon === 'agents' ? 'store' : icon
|
||||
})
|
||||
state.settings.sidebarIcons.disabled = state.settings.sidebarIcons.disabled.map((icon) => {
|
||||
// @ts-ignore
|
||||
return icon === 'agents' ? 'store' : icon
|
||||
})
|
||||
}
|
||||
|
||||
// Migrate llm providers
|
||||
state.llm.providers.forEach((provider) => {
|
||||
if (provider.id === SystemProviderIds['new-api'] && provider.type !== 'new-api') {
|
||||
provider.type = 'new-api'
|
||||
}
|
||||
|
||||
switch (provider.id) {
|
||||
case 'deepseek':
|
||||
provider.anthropicApiHost = 'https://api.deepseek.com/anthropic'
|
||||
break
|
||||
case 'moonshot':
|
||||
provider.anthropicApiHost = 'https://api.moonshot.cn/anthropic'
|
||||
break
|
||||
case 'zhipu':
|
||||
provider.anthropicApiHost = 'https://open.bigmodel.cn/api/anthropic'
|
||||
break
|
||||
case 'dashscope':
|
||||
provider.anthropicApiHost = 'https://dashscope.aliyuncs.com/apps/anthropic'
|
||||
break
|
||||
case 'modelscope':
|
||||
provider.anthropicApiHost = 'https://api-inference.modelscope.cn'
|
||||
break
|
||||
case 'aihubmix':
|
||||
provider.anthropicApiHost = 'https://aihubmix.com'
|
||||
break
|
||||
case 'new-api':
|
||||
provider.anthropicApiHost = 'http://localhost:3000'
|
||||
break
|
||||
case 'grok':
|
||||
provider.anthropicApiHost = 'https://api.x.ai'
|
||||
break
|
||||
case 'cherryin':
|
||||
provider.anthropicApiHost = 'https://open.cherryin.net'
|
||||
break
|
||||
case 'longcat':
|
||||
provider.anthropicApiHost = 'https://api.longcat.chat/anthropic'
|
||||
break
|
||||
}
|
||||
})
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 172 error', error as Error)
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu'
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'
|
||||
import { cn } from '@renderer/utils'
|
||||
|
||||
function ContextMenu({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
|
||||
return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
|
||||
}
|
||||
|
||||
function ContextMenuTrigger({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
|
||||
return <ContextMenuPrimitive.Trigger data-slot="context-menu-trigger" {...props} />
|
||||
}
|
||||
|
||||
function ContextMenuGroup({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
|
||||
return <ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
|
||||
}
|
||||
|
||||
function ContextMenuPortal({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
|
||||
return <ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
|
||||
}
|
||||
|
||||
function ContextMenuSub({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
|
||||
return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
|
||||
}
|
||||
|
||||
function ContextMenuRadioGroup({ ...props }: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
|
||||
return <ContextMenuPrimitive.RadioGroup data-slot="context-menu-radio-group" {...props} />
|
||||
}
|
||||
|
||||
function ContextMenuSubTrigger({
|
||||
className,
|
||||
inset,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.SubTrigger
|
||||
data-slot="context-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[inset]:pl-8 data-[state=open]:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto" />
|
||||
</ContextMenuPrimitive.SubTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuSubContent({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.SubContent
|
||||
data-slot="context-menu-sub-content"
|
||||
className={cn(
|
||||
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 z-50 min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=closed]:animate-out data-[state=open]:animate-in',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuContent({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Content>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Portal>
|
||||
<ContextMenuPrimitive.Content
|
||||
data-slot="context-menu-content"
|
||||
className={cn(
|
||||
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 z-50 max-h-(--radix-context-menu-content-available-height) min-w-[8rem] origin-(--radix-context-menu-content-transform-origin) overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=closed]:animate-out data-[state=open]:animate-in',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</ContextMenuPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuItem({
|
||||
className,
|
||||
inset,
|
||||
variant = 'default',
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
variant?: 'default' | 'destructive'
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Item
|
||||
data-slot="context-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"data-[variant=destructive]:*:[svg]:!text-destructive relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[disabled]:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuCheckboxItem({
|
||||
className,
|
||||
children,
|
||||
checked,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.CheckboxItem
|
||||
data-slot="context-menu-checkbox-item"
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.CheckboxItem>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuRadioItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.RadioItem
|
||||
data-slot="context-menu-radio-item"
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.RadioItem>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuLabel({
|
||||
className,
|
||||
inset,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Label
|
||||
data-slot="context-menu-label"
|
||||
data-inset={inset}
|
||||
className={cn('px-2 py-1.5 font-medium text-foreground text-sm data-[inset]:pl-8', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuSeparator({ className, ...props }: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
|
||||
return (
|
||||
<ContextMenuPrimitive.Separator
|
||||
data-slot="context-menu-separator"
|
||||
className={cn('-mx-1 my-1 h-px bg-border', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ContextMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {
|
||||
return (
|
||||
<span
|
||||
data-slot="context-menu-shortcut"
|
||||
className={cn('ml-auto text-muted-foreground text-xs tracking-widest', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
ContextMenu,
|
||||
ContextMenuTrigger,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuRadioItem,
|
||||
ContextMenuLabel,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuGroup,
|
||||
ContextMenuPortal,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuRadioGroup
|
||||
}
|
||||
Reference in New Issue
Block a user