mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 02:20:10 +08:00
Merge remote-tracking branch 'origin/main' into feat/proxy-api-server
This commit is contained in:
commit
b3c7a1db91
2
.github/workflows/auto-i18n.yml
vendored
2
.github/workflows/auto-i18n.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 🐈⬛ Checkout
|
- name: 🐈⬛ Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/claude-code-review.yml
vendored
2
.github/workflows/claude-code-review.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/claude-translator.yml
vendored
2
.github/workflows/claude-translator.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/claude.yml
vendored
2
.github/workflows/claude.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
|||||||
actions: read # Required for Claude to read CI results on PRs
|
actions: read # Required for Claude to read CI results on PRs
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/github-issue-tracker.yml
vendored
6
.github/workflows/github-issue-tracker.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Check Beijing Time
|
- name: Check Beijing Time
|
||||||
id: check_time
|
id: check_time
|
||||||
@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Add pending label if in quiet hours
|
- name: Add pending label if in quiet hours
|
||||||
if: steps.check_time.outputs.should_delay == 'true'
|
if: steps.check_time.outputs.should_delay == 'true'
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v8
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.addLabels({
|
github.rest.issues.addLabels({
|
||||||
@ -118,7 +118,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
|
|||||||
2
.github/workflows/nightly-build.yml
vendored
2
.github/workflows/nightly-build.yml
vendored
@ -51,7 +51,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/pr-ci.yml
vendored
2
.github/workflows/pr-ci.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out Git repository
|
- name: Check out Git repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
36
.github/workflows/update-app-upgrade-config.yml
vendored
36
.github/workflows/update-app-upgrade-config.yml
vendored
@ -19,10 +19,9 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
propose-update:
|
update-config:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'release' && github.event.release.draft == false)
|
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'release' && github.event.release.draft == false)
|
||||||
|
|
||||||
@ -135,7 +134,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout default branch
|
- name: Checkout default branch
|
||||||
if: steps.check.outputs.should_run == 'true'
|
if: steps.check.outputs.should_run == 'true'
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.repository.default_branch }}
|
ref: ${{ github.event.repository.default_branch }}
|
||||||
path: main
|
path: main
|
||||||
@ -143,7 +142,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout x-files/app-upgrade-config branch
|
- name: Checkout x-files/app-upgrade-config branch
|
||||||
if: steps.check.outputs.should_run == 'true'
|
if: steps.check.outputs.should_run == 'true'
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: x-files/app-upgrade-config
|
ref: x-files/app-upgrade-config
|
||||||
path: cs
|
path: cs
|
||||||
@ -187,25 +186,20 @@ jobs:
|
|||||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Create pull request
|
- name: Commit and push changes
|
||||||
if: steps.check.outputs.should_run == 'true' && steps.diff.outputs.changed == 'true'
|
if: steps.check.outputs.should_run == 'true' && steps.diff.outputs.changed == 'true'
|
||||||
uses: peter-evans/create-pull-request@v7
|
working-directory: cs
|
||||||
with:
|
run: |
|
||||||
path: cs
|
git config user.name "github-actions[bot]"
|
||||||
base: x-files/app-upgrade-config
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
branch: chore/update-app-upgrade-config/${{ steps.meta.outputs.safe_tag }}
|
git add app-upgrade-config.json
|
||||||
commit-message: "🤖 chore: sync app-upgrade-config for ${{ steps.meta.outputs.tag }}"
|
git commit -m "chore: sync app-upgrade-config for ${{ steps.meta.outputs.tag }}" -m "Automated update triggered by \`${{ steps.meta.outputs.trigger }}\`.
|
||||||
title: "chore: update app-upgrade-config for ${{ steps.meta.outputs.tag }}"
|
|
||||||
body: |
|
|
||||||
Automated update triggered by `${{ steps.meta.outputs.trigger }}`.
|
|
||||||
|
|
||||||
- Source tag: `${{ steps.meta.outputs.tag }}`
|
- Source tag: \`${{ steps.meta.outputs.tag }}\`
|
||||||
- Pre-release: `${{ steps.meta.outputs.prerelease }}`
|
- Pre-release: \`${{ steps.meta.outputs.prerelease }}\`
|
||||||
- Latest: `${{ steps.meta.outputs.latest }}`
|
- Latest: \`${{ steps.meta.outputs.latest }}\`
|
||||||
- Workflow run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
- Workflow run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
labels: |
|
git push origin x-files/app-upgrade-config
|
||||||
automation
|
|
||||||
app-upgrade
|
|
||||||
|
|
||||||
- name: No changes detected
|
- name: No changes detected
|
||||||
if: steps.check.outputs.should_run == 'true' && steps.diff.outputs.changed != 'true'
|
if: steps.check.outputs.should_run == 'true' && steps.diff.outputs.changed != 'true'
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
diff --git a/sdk.mjs b/sdk.mjs
|
diff --git a/sdk.mjs b/sdk.mjs
|
||||||
index bf429a344b7d59f70aead16b639f949b07688a81..f77d50cc5d3fb04292cb3ac7fa7085d02dcc628f 100755
|
index dea7766a3432a1e809f12d6daba4f2834a219689..e0b02ef73da177ba32b903887d7bbbeaa08cc6d3 100755
|
||||||
--- a/sdk.mjs
|
--- a/sdk.mjs
|
||||||
+++ b/sdk.mjs
|
+++ b/sdk.mjs
|
||||||
@@ -6250,7 +6250,7 @@ function createAbortController(maxListeners = DEFAULT_MAX_LISTENERS) {
|
@@ -6250,7 +6250,7 @@ function createAbortController(maxListeners = DEFAULT_MAX_LISTENERS) {
|
||||||
@ -11,7 +11,7 @@ index bf429a344b7d59f70aead16b639f949b07688a81..f77d50cc5d3fb04292cb3ac7fa7085d0
|
|||||||
import { createInterface } from "readline";
|
import { createInterface } from "readline";
|
||||||
|
|
||||||
// ../src/utils/fsOperations.ts
|
// ../src/utils/fsOperations.ts
|
||||||
@@ -6619,18 +6619,11 @@ class ProcessTransport {
|
@@ -6644,18 +6644,11 @@ class ProcessTransport {
|
||||||
const errorMessage = isNativeBinary(pathToClaudeCodeExecutable) ? `Claude Code native binary not found at ${pathToClaudeCodeExecutable}. Please ensure Claude Code is installed via native installer or specify a valid path with options.pathToClaudeCodeExecutable.` : `Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`;
|
const errorMessage = isNativeBinary(pathToClaudeCodeExecutable) ? `Claude Code native binary not found at ${pathToClaudeCodeExecutable}. Please ensure Claude Code is installed via native installer or specify a valid path with options.pathToClaudeCodeExecutable.` : `Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`;
|
||||||
throw new ReferenceError(errorMessage);
|
throw new ReferenceError(errorMessage);
|
||||||
}
|
}
|
||||||
@ -134,108 +134,66 @@ artifactBuildCompleted: scripts/artifact-build-completed.js
|
|||||||
releaseInfo:
|
releaseInfo:
|
||||||
releaseNotes: |
|
releaseNotes: |
|
||||||
<!--LANG:en-->
|
<!--LANG:en-->
|
||||||
A New Era of Intelligence with Cherry Studio 1.7.1
|
Cherry Studio 1.7.2 - Stability & Enhancement Update
|
||||||
|
|
||||||
Today we're releasing Cherry Studio 1.7.1 — our most ambitious update yet, introducing Agent: autonomous AI that thinks, plans, and acts.
|
This release focuses on stability improvements, bug fixes, and quality-of-life enhancements.
|
||||||
|
|
||||||
For years, AI assistants have been reactive — waiting for your commands, responding to your questions. With Agent, we're changing that. Now, AI can truly work alongside you: understanding complex goals, breaking them into steps, and executing them independently.
|
🔧 Improvements
|
||||||
|
- Enhanced update dialog functionality and state management
|
||||||
|
- Improved ImageViewer context menu UX
|
||||||
|
- Better temperature and top_p parameter handling
|
||||||
|
- User-configurable stream options for OpenAI API
|
||||||
|
- Translation feature now supports document files
|
||||||
|
|
||||||
This is what we've been building toward. And it's just the beginning.
|
🤖 AI & Models
|
||||||
|
- Added explicit thinking token support for Gemini 3 Pro Image
|
||||||
|
- Updated DeepSeek logic to match DeepSeek v3.2
|
||||||
|
- Updated AiOnly default models
|
||||||
|
- Updated AI model configurations to latest versions
|
||||||
|
|
||||||
🤖 Meet Agent
|
♿ Accessibility
|
||||||
Imagine having a brilliant colleague who never sleeps. Give Agent a goal — write a report, analyze data, refactor code — and watch it work. It reasons through problems, breaks them into steps, calls the right tools, and adapts when things change.
|
- Improved screen reader (NVDA) support with aria-label attributes
|
||||||
|
- Added Slovak language support for spell check
|
||||||
|
|
||||||
- **Think → Plan → Act**: From goal to execution, fully autonomous
|
🐛 Bug Fixes
|
||||||
- **Deep Reasoning**: Multi-turn thinking that solves real problems
|
- Fixed Quick Assistant shortcut registration issue
|
||||||
- **Tool Mastery**: File operations, web search, code execution, and more
|
- Fixed UI freeze on multi-file selection via batch processing
|
||||||
- **Skill Plugins**: Extend with custom commands and capabilities
|
- Fixed assistant default model update when editing model capabilities
|
||||||
- **You Stay in Control**: Real-time approval for sensitive actions
|
- Fixed provider handling and API key rotation logic
|
||||||
- **Full Visibility**: Every thought, every decision, fully transparent
|
- Fixed OVMS API URL path formation
|
||||||
|
- Fixed custom parameters placement for Vercel AI Gateway
|
||||||
🌐 Expanding Ecosystem
|
- Fixed topic message blocks clearing
|
||||||
- **New Providers**: HuggingFace, Mistral, CherryIN, AI Gateway, Intel OVMS, Didi MCP
|
- Fixed input bar blocking enter send while generating
|
||||||
- **New Models**: Claude 4.5 Haiku, DeepSeek v3.2, GLM-4.6, Doubao, Ling series
|
|
||||||
- **MCP Integration**: Alibaba Cloud, ModelScope, Higress, MCP.so, TokenFlux and more
|
|
||||||
|
|
||||||
📚 Smarter Knowledge Base
|
|
||||||
- **OpenMinerU**: Self-hosted document processing
|
|
||||||
- **Full-Text Search**: Find anything instantly across your notes
|
|
||||||
- **Enhanced Tool Selection**: Smarter configuration for better AI assistance
|
|
||||||
|
|
||||||
📝 Notes, Reimagined
|
|
||||||
- Full-text search with highlighted results
|
|
||||||
- AI-powered smart rename
|
|
||||||
- Export as image
|
|
||||||
- Auto-wrap for tables
|
|
||||||
|
|
||||||
🖼️ Image & OCR
|
|
||||||
- Intel OVMS painting capabilities
|
|
||||||
- Intel OpenVINO NPU-accelerated OCR
|
|
||||||
|
|
||||||
🌍 Now in 10+ Languages
|
|
||||||
- Added German support
|
|
||||||
- Enhanced internationalization
|
|
||||||
|
|
||||||
⚡ Faster & More Polished
|
|
||||||
- Electron 38 upgrade
|
|
||||||
- New MCP management interface
|
|
||||||
- Dozens of UI refinements
|
|
||||||
|
|
||||||
❤️ Fully Open Source
|
|
||||||
Commercial restrictions removed. Cherry Studio now follows standard AGPL v3 — free for teams of any size.
|
|
||||||
|
|
||||||
The Agent Era is here. We can't wait to see what you'll create.
|
|
||||||
|
|
||||||
<!--LANG:zh-CN-->
|
<!--LANG:zh-CN-->
|
||||||
Cherry Studio 1.7.1:开启智能新纪元
|
Cherry Studio 1.7.2 - 稳定性与功能增强更新
|
||||||
|
|
||||||
今天,我们正式发布 Cherry Studio 1.7.1 —— 迄今最具雄心的版本,带来全新的 Agent:能够自主思考、规划和行动的 AI。
|
本次更新专注于稳定性改进、问题修复和用户体验提升。
|
||||||
|
|
||||||
多年来,AI 助手一直是被动的——等待你的指令,回应你的问题。Agent 改变了这一切。现在,AI 能够真正与你并肩工作:理解复杂目标,将其拆解为步骤,并独立执行。
|
🔧 功能改进
|
||||||
|
- 增强更新对话框功能和状态管理
|
||||||
|
- 优化图片查看器右键菜单体验
|
||||||
|
- 改进温度和 top_p 参数处理逻辑
|
||||||
|
- 支持用户自定义 OpenAI API 流式选项
|
||||||
|
- 翻译功能现已支持文档文件
|
||||||
|
|
||||||
这是我们一直在构建的未来。而这,仅仅是开始。
|
🤖 AI 与模型
|
||||||
|
- 为 Gemini 3 Pro Image 添加显式思考 token 支持
|
||||||
|
- 更新 DeepSeek 逻辑以适配 DeepSeek v3.2
|
||||||
|
- 更新 AiOnly 默认模型
|
||||||
|
- 更新 AI 模型配置至最新版本
|
||||||
|
|
||||||
🤖 认识 Agent
|
♿ 无障碍支持
|
||||||
想象一位永不疲倦的得力伙伴。给 Agent 一个目标——撰写报告、分析数据、重构代码——然后看它工作。它会推理问题、拆解步骤、调用工具,并在情况变化时灵活应对。
|
- 改进屏幕阅读器 (NVDA) 支持,添加 aria-label 属性
|
||||||
|
- 新增斯洛伐克语拼写检查支持
|
||||||
|
|
||||||
- **思考 → 规划 → 行动**:从目标到执行,全程自主
|
🐛 问题修复
|
||||||
- **深度推理**:多轮思考,解决真实问题
|
- 修复快捷助手无法注册快捷键的问题
|
||||||
- **工具大师**:文件操作、网络搜索、代码执行,样样精通
|
- 修复多文件选择时 UI 冻结问题(通过批处理优化)
|
||||||
- **技能插件**:自定义命令,无限扩展
|
- 修复编辑模型能力时助手默认模型更新问题
|
||||||
- **你掌控全局**:敏感操作,实时审批
|
- 修复服务商处理和 API 密钥轮换逻辑
|
||||||
- **完全透明**:每一步思考,每一个决策,清晰可见
|
- 修复 OVMS API URL 路径格式问题
|
||||||
|
- 修复 Vercel AI Gateway 自定义参数位置问题
|
||||||
🌐 生态持续壮大
|
- 修复话题消息块清理问题
|
||||||
- **新增服务商**:Hugging Face、Mistral、Perplexity、SophNet、AI Gateway、Cerebras AI
|
- 修复生成时输入框阻止回车发送的问题
|
||||||
- **新增模型**:Gemini 3、Gemini 3 Pro(支持图像预览)、GPT-5.1、Claude Opus 4.5
|
|
||||||
- **MCP 集成**:百炼、魔搭、Higress、MCP.so、TokenFlux 等平台
|
|
||||||
|
|
||||||
📚 更智能的知识库
|
|
||||||
- **OpenMinerU**:本地自部署文档处理
|
|
||||||
- **全文搜索**:笔记内容一搜即达
|
|
||||||
- **增强工具选择**:更智能的配置,更好的 AI 协助
|
|
||||||
|
|
||||||
📝 笔记,焕然一新
|
|
||||||
- 全文搜索,结果高亮
|
|
||||||
- AI 智能重命名
|
|
||||||
- 导出为图片
|
|
||||||
- 表格自动换行
|
|
||||||
|
|
||||||
🖼️ 图像与 OCR
|
|
||||||
- Intel OVMS 绘图能力
|
|
||||||
- Intel OpenVINO NPU 加速 OCR
|
|
||||||
|
|
||||||
🌍 支持 10+ 种语言
|
|
||||||
- 新增德语支持
|
|
||||||
- 全面增强国际化
|
|
||||||
|
|
||||||
⚡ 更快、更精致
|
|
||||||
- 升级 Electron 38
|
|
||||||
- 新的 MCP 管理界面
|
|
||||||
- 数十处 UI 细节打磨
|
|
||||||
|
|
||||||
❤️ 完全开源
|
|
||||||
商用限制已移除。Cherry Studio 现遵循标准 AGPL v3 协议——任意规模团队均可自由使用。
|
|
||||||
|
|
||||||
Agent 纪元已至。期待你的创造。
|
|
||||||
<!--LANG:END-->
|
<!--LANG:END-->
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "CherryStudio",
|
"name": "CherryStudio",
|
||||||
"version": "1.7.1",
|
"version": "1.7.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A powerful AI assistant for producer.",
|
"description": "A powerful AI assistant for producer.",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
@ -50,7 +50,7 @@
|
|||||||
"generate:icons": "electron-icon-builder --input=./build/logo.png --output=build",
|
"generate:icons": "electron-icon-builder --input=./build/logo.png --output=build",
|
||||||
"analyze:renderer": "VISUALIZER_RENDERER=true yarn build",
|
"analyze:renderer": "VISUALIZER_RENDERER=true yarn build",
|
||||||
"analyze:main": "VISUALIZER_MAIN=true yarn build",
|
"analyze:main": "VISUALIZER_MAIN=true yarn build",
|
||||||
"typecheck": "concurrently -n \"node,web\" -c \"cyan,magenta\" \"npm run typecheck:node\" \"npm run typecheck:web\"",
|
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
||||||
"typecheck:node": "tsgo --noEmit -p tsconfig.node.json --composite false",
|
"typecheck:node": "tsgo --noEmit -p tsconfig.node.json --composite false",
|
||||||
"typecheck:web": "tsgo --noEmit -p tsconfig.web.json --composite false",
|
"typecheck:web": "tsgo --noEmit -p tsconfig.web.json --composite false",
|
||||||
"check:i18n": "dotenv -e .env -- tsx scripts/check-i18n.ts",
|
"check:i18n": "dotenv -e .env -- tsx scripts/check-i18n.ts",
|
||||||
@ -81,7 +81,7 @@
|
|||||||
"release:ai-sdk-provider": "yarn workspace @cherrystudio/ai-sdk-provider version patch --immediate && yarn workspace @cherrystudio/ai-sdk-provider build && yarn workspace @cherrystudio/ai-sdk-provider npm publish --access public"
|
"release:ai-sdk-provider": "yarn workspace @cherrystudio/ai-sdk-provider version patch --immediate && yarn workspace @cherrystudio/ai-sdk-provider build && yarn workspace @cherrystudio/ai-sdk-provider npm publish --access public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.53#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.53-4b77f4cf29.patch",
|
"@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.62#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.62-23ae56f8c8.patch",
|
||||||
"@libsql/client": "0.14.0",
|
"@libsql/client": "0.14.0",
|
||||||
"@libsql/win32-x64-msvc": "^0.4.7",
|
"@libsql/win32-x64-msvc": "^0.4.7",
|
||||||
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
|
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
|
||||||
@ -257,7 +257,6 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"code-inspector-plugin": "^0.20.14",
|
"code-inspector-plugin": "^0.20.14",
|
||||||
"color": "^5.0.0",
|
"color": "^5.0.0",
|
||||||
"concurrently": "^9.2.1",
|
|
||||||
"country-flag-emoji-polyfill": "0.1.8",
|
"country-flag-emoji-polyfill": "0.1.8",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
"dexie": "^4.0.8",
|
"dexie": "^4.0.8",
|
||||||
|
|||||||
@ -135,10 +135,8 @@ export class StreamEventManager {
|
|||||||
// 构建新的对话消息
|
// 构建新的对话消息
|
||||||
const newMessages: ModelMessage[] = [
|
const newMessages: ModelMessage[] = [
|
||||||
...(context.originalParams.messages || []),
|
...(context.originalParams.messages || []),
|
||||||
{
|
// 只有当 textBuffer 有内容时才添加 assistant 消息,避免空消息导致 API 错误
|
||||||
role: 'assistant',
|
...(textBuffer ? [{ role: 'assistant' as const, content: textBuffer }] : []),
|
||||||
content: textBuffer
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: toolResultsText
|
content: toolResultsText
|
||||||
|
|||||||
@ -90,6 +90,8 @@ export enum IpcChannel {
|
|||||||
Mcp_AbortTool = 'mcp:abort-tool',
|
Mcp_AbortTool = 'mcp:abort-tool',
|
||||||
Mcp_GetServerVersion = 'mcp:get-server-version',
|
Mcp_GetServerVersion = 'mcp:get-server-version',
|
||||||
Mcp_Progress = 'mcp:progress',
|
Mcp_Progress = 'mcp:progress',
|
||||||
|
Mcp_GetServerLogs = 'mcp:get-server-logs',
|
||||||
|
Mcp_ServerLog = 'mcp:server-log',
|
||||||
// Python
|
// Python
|
||||||
Python_Execute = 'python:execute',
|
Python_Execute = 'python:execute',
|
||||||
|
|
||||||
@ -293,6 +295,8 @@ export enum IpcChannel {
|
|||||||
Selection_ActionWindowClose = 'selection:action-window-close',
|
Selection_ActionWindowClose = 'selection:action-window-close',
|
||||||
Selection_ActionWindowMinimize = 'selection:action-window-minimize',
|
Selection_ActionWindowMinimize = 'selection:action-window-minimize',
|
||||||
Selection_ActionWindowPin = 'selection:action-window-pin',
|
Selection_ActionWindowPin = 'selection:action-window-pin',
|
||||||
|
// [Windows only] Electron bug workaround - can be removed once https://github.com/electron/electron/issues/48554 is fixed
|
||||||
|
Selection_ActionWindowResize = 'selection:action-window-resize',
|
||||||
Selection_ProcessAction = 'selection:process-action',
|
Selection_ProcessAction = 'selection:process-action',
|
||||||
Selection_UpdateActionData = 'selection:update-action-data',
|
Selection_UpdateActionData = 'selection:update-action-data',
|
||||||
|
|
||||||
|
|||||||
@ -106,16 +106,11 @@ export function getSdkClient(
|
|||||||
fetch: customFetch
|
fetch: customFetch
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
let baseURL =
|
const baseURL =
|
||||||
provider.type === 'anthropic'
|
provider.type === 'anthropic'
|
||||||
? provider.apiHost
|
? provider.apiHost
|
||||||
: (provider.anthropicApiHost && provider.anthropicApiHost.trim()) || provider.apiHost
|
: (provider.anthropicApiHost && provider.anthropicApiHost.trim()) || provider.apiHost
|
||||||
|
|
||||||
// Anthropic SDK automatically appends /v1 to all endpoints (like /v1/messages, /v1/models)
|
|
||||||
// We need to strip api version from baseURL to avoid duplication (e.g., /v3/v1/models)
|
|
||||||
// formatProviderApiHost adds /v1 for AI SDK compatibility, but Anthropic SDK needs it removed
|
|
||||||
baseURL = baseURL.replace(/\/v\d+(?:alpha|beta)?(?=\/|$)/i, '')
|
|
||||||
|
|
||||||
logger.debug('Anthropic API baseURL', { baseURL, providerId: provider.id })
|
logger.debug('Anthropic API baseURL', { baseURL, providerId: provider.id })
|
||||||
|
|
||||||
if (provider.id === 'aihubmix') {
|
if (provider.id === 'aihubmix') {
|
||||||
|
|||||||
@ -43,6 +43,23 @@ export function withoutTrailingSharp<T extends string>(url: T): T {
|
|||||||
return url.replace(/#$/, '') as T
|
return url.replace(/#$/, '') as T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a URL string ends with a trailing '#' character.
|
||||||
|
*
|
||||||
|
* @template T - The string type to preserve type safety
|
||||||
|
* @param {T} url - The URL string to check
|
||||||
|
* @returns {boolean} True if the URL ends with '#', false otherwise
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* isWithTrailingSharp('https://example.com#') // true
|
||||||
|
* isWithTrailingSharp('https://example.com') // false
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function isWithTrailingSharp<T extends string>(url: T): boolean {
|
||||||
|
return url.endsWith('#')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches a version segment in a path that starts with `/v<number>` and optionally
|
* Matches a version segment in a path that starts with `/v<number>` and optionally
|
||||||
* continues with `alpha` or `beta`. The segment may be followed by `/` or the end
|
* continues with `alpha` or `beta`. The segment may be followed by `/` or the end
|
||||||
|
|||||||
@ -23,6 +23,14 @@ export type MCPProgressEvent = {
|
|||||||
progress: number // 0-1 range
|
progress: number // 0-1 range
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MCPServerLogEntry = {
|
||||||
|
timestamp: number
|
||||||
|
level: 'debug' | 'info' | 'warn' | 'error' | 'stderr' | 'stdout'
|
||||||
|
message: string
|
||||||
|
data?: any
|
||||||
|
source?: string
|
||||||
|
}
|
||||||
|
|
||||||
export type WebviewKeyEvent = {
|
export type WebviewKeyEvent = {
|
||||||
webviewId: number
|
webviewId: number
|
||||||
key: string
|
key: string
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
formatAzureOpenAIApiHost,
|
formatAzureOpenAIApiHost,
|
||||||
formatOllamaApiHost,
|
formatOllamaApiHost,
|
||||||
formatVertexApiHost,
|
formatVertexApiHost,
|
||||||
|
isWithTrailingSharp,
|
||||||
routeToEndpoint,
|
routeToEndpoint,
|
||||||
withoutTrailingSlash
|
withoutTrailingSlash
|
||||||
} from '../api'
|
} from '../api'
|
||||||
@ -63,17 +64,17 @@ export function defaultFormatAzureOpenAIApiHost(host: string): string {
|
|||||||
*/
|
*/
|
||||||
export function formatProviderApiHost<T extends MinimalProvider>(provider: T, context: ProviderFormatContext): T {
|
export function formatProviderApiHost<T extends MinimalProvider>(provider: T, context: ProviderFormatContext): T {
|
||||||
const formatted = { ...provider }
|
const formatted = { ...provider }
|
||||||
|
const appendApiVersion = !isWithTrailingSharp(provider.apiHost)
|
||||||
// Format anthropicApiHost if present
|
// Format anthropicApiHost if present
|
||||||
if (formatted.anthropicApiHost) {
|
if (formatted.anthropicApiHost) {
|
||||||
formatted.anthropicApiHost = formatApiHost(formatted.anthropicApiHost)
|
formatted.anthropicApiHost = formatApiHost(formatted.anthropicApiHost, appendApiVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format based on provider type
|
// Format based on provider type
|
||||||
if (isAnthropicProvider(provider)) {
|
if (isAnthropicProvider(provider)) {
|
||||||
const baseHost = formatted.anthropicApiHost || formatted.apiHost
|
const baseHost = formatted.anthropicApiHost || formatted.apiHost
|
||||||
// AI SDK needs /v1 in baseURL
|
// AI SDK needs /v1 in baseURL
|
||||||
formatted.apiHost = formatApiHost(baseHost)
|
formatted.apiHost = formatApiHost(baseHost, appendApiVersion)
|
||||||
if (!formatted.anthropicApiHost) {
|
if (!formatted.anthropicApiHost) {
|
||||||
formatted.anthropicApiHost = formatted.apiHost
|
formatted.anthropicApiHost = formatted.apiHost
|
||||||
}
|
}
|
||||||
@ -82,7 +83,7 @@ export function formatProviderApiHost<T extends MinimalProvider>(provider: T, co
|
|||||||
} else if (isOllamaProvider(formatted)) {
|
} else if (isOllamaProvider(formatted)) {
|
||||||
formatted.apiHost = formatOllamaApiHost(formatted.apiHost)
|
formatted.apiHost = formatOllamaApiHost(formatted.apiHost)
|
||||||
} else if (isGeminiProvider(formatted)) {
|
} else if (isGeminiProvider(formatted)) {
|
||||||
formatted.apiHost = formatApiHost(formatted.apiHost, true, 'v1beta')
|
formatted.apiHost = formatApiHost(formatted.apiHost, appendApiVersion, 'v1beta')
|
||||||
} else if (isAzureOpenAIProvider(formatted)) {
|
} else if (isAzureOpenAIProvider(formatted)) {
|
||||||
formatted.apiHost = formatAzureOpenAIApiHost(formatted.apiHost)
|
formatted.apiHost = formatAzureOpenAIApiHost(formatted.apiHost)
|
||||||
} else if (isVertexProvider(formatted)) {
|
} else if (isVertexProvider(formatted)) {
|
||||||
@ -92,7 +93,7 @@ export function formatProviderApiHost<T extends MinimalProvider>(provider: T, co
|
|||||||
} else if (isPerplexityProvider(formatted)) {
|
} else if (isPerplexityProvider(formatted)) {
|
||||||
formatted.apiHost = formatApiHost(formatted.apiHost, false)
|
formatted.apiHost = formatApiHost(formatted.apiHost, false)
|
||||||
} else {
|
} else {
|
||||||
formatted.apiHost = formatApiHost(formatted.apiHost)
|
formatted.apiHost = formatApiHost(formatted.apiHost, appendApiVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatted
|
return formatted
|
||||||
|
|||||||
@ -19,8 +19,8 @@ import { agentService } from './services/agents'
|
|||||||
import { apiServerService } from './services/ApiServerService'
|
import { apiServerService } from './services/ApiServerService'
|
||||||
import { appMenuService } from './services/AppMenuService'
|
import { appMenuService } from './services/AppMenuService'
|
||||||
import { configManager } from './services/ConfigManager'
|
import { configManager } from './services/ConfigManager'
|
||||||
import mcpService from './services/MCPService'
|
|
||||||
import { nodeTraceService } from './services/NodeTraceService'
|
import { nodeTraceService } from './services/NodeTraceService'
|
||||||
|
import mcpService from './services/MCPService'
|
||||||
import powerMonitorService from './services/PowerMonitorService'
|
import powerMonitorService from './services/PowerMonitorService'
|
||||||
import {
|
import {
|
||||||
CHERRY_STUDIO_PROTOCOL,
|
CHERRY_STUDIO_PROTOCOL,
|
||||||
|
|||||||
@ -765,6 +765,8 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
ipcMain.handle(IpcChannel.Mcp_CheckConnectivity, mcpService.checkMcpConnectivity)
|
ipcMain.handle(IpcChannel.Mcp_CheckConnectivity, mcpService.checkMcpConnectivity)
|
||||||
ipcMain.handle(IpcChannel.Mcp_AbortTool, mcpService.abortTool)
|
ipcMain.handle(IpcChannel.Mcp_AbortTool, mcpService.abortTool)
|
||||||
ipcMain.handle(IpcChannel.Mcp_GetServerVersion, mcpService.getServerVersion)
|
ipcMain.handle(IpcChannel.Mcp_GetServerVersion, mcpService.getServerVersion)
|
||||||
|
ipcMain.handle(IpcChannel.Mcp_GetServerLogs, mcpService.getServerLogs)
|
||||||
|
ipcMain.handle(IpcChannel.Mcp_GetServerLogs, mcpService.getServerLogs)
|
||||||
|
|
||||||
// DXT upload handler
|
// DXT upload handler
|
||||||
ipcMain.handle(IpcChannel.Mcp_UploadDxt, async (event, fileBuffer: ArrayBuffer, fileName: string) => {
|
ipcMain.handle(IpcChannel.Mcp_UploadDxt, async (event, fileBuffer: ArrayBuffer, fileName: string) => {
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import {
|
|||||||
import { nanoid } from '@reduxjs/toolkit'
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
import { HOME_CHERRY_DIR } from '@shared/config/constant'
|
||||||
import type { MCPProgressEvent } from '@shared/config/types'
|
import type { MCPProgressEvent } from '@shared/config/types'
|
||||||
|
import type { MCPServerLogEntry } from '@shared/config/types'
|
||||||
import { IpcChannel } from '@shared/IpcChannel'
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
import { defaultAppHeaders } from '@shared/utils'
|
import { defaultAppHeaders } from '@shared/utils'
|
||||||
import {
|
import {
|
||||||
@ -56,6 +57,7 @@ import { CacheService } from './CacheService'
|
|||||||
import DxtService from './DxtService'
|
import DxtService from './DxtService'
|
||||||
import { CallBackServer } from './mcp/oauth/callback'
|
import { CallBackServer } from './mcp/oauth/callback'
|
||||||
import { McpOAuthClientProvider } from './mcp/oauth/provider'
|
import { McpOAuthClientProvider } from './mcp/oauth/provider'
|
||||||
|
import { ServerLogBuffer } from './mcp/ServerLogBuffer'
|
||||||
import { windowService } from './WindowService'
|
import { windowService } from './WindowService'
|
||||||
|
|
||||||
// Generic type for caching wrapped functions
|
// Generic type for caching wrapped functions
|
||||||
@ -142,6 +144,7 @@ class McpService {
|
|||||||
private pendingClients: Map<string, Promise<Client>> = new Map()
|
private pendingClients: Map<string, Promise<Client>> = new Map()
|
||||||
private dxtService = new DxtService()
|
private dxtService = new DxtService()
|
||||||
private activeToolCalls: Map<string, AbortController> = new Map()
|
private activeToolCalls: Map<string, AbortController> = new Map()
|
||||||
|
private serverLogs = new ServerLogBuffer(200)
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.initClient = this.initClient.bind(this)
|
this.initClient = this.initClient.bind(this)
|
||||||
@ -172,6 +175,19 @@ class McpService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private emitServerLog(server: MCPServer, entry: MCPServerLogEntry) {
|
||||||
|
const serverKey = this.getServerKey(server)
|
||||||
|
this.serverLogs.append(serverKey, entry)
|
||||||
|
const mainWindow = windowService.getMainWindow()
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send(IpcChannel.Mcp_ServerLog, { ...entry, serverId: server.id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getServerLogs(_: Electron.IpcMainInvokeEvent, server: MCPServer): MCPServerLogEntry[] {
|
||||||
|
return this.serverLogs.get(this.getServerKey(server))
|
||||||
|
}
|
||||||
|
|
||||||
async initClient(server: MCPServer): Promise<Client> {
|
async initClient(server: MCPServer): Promise<Client> {
|
||||||
const serverKey = this.getServerKey(server)
|
const serverKey = this.getServerKey(server)
|
||||||
|
|
||||||
@ -366,9 +382,25 @@ class McpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const stdioTransport = new StdioClientTransport(transportOptions)
|
const stdioTransport = new StdioClientTransport(transportOptions)
|
||||||
stdioTransport.stderr?.on('data', (data) =>
|
stdioTransport.stderr?.on('data', (data) => {
|
||||||
getServerLogger(server).debug(`Stdio stderr`, { data: data.toString() })
|
const msg = data.toString()
|
||||||
)
|
getServerLogger(server).debug(`Stdio stderr`, { data: msg })
|
||||||
|
this.emitServerLog(server, {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
level: 'stderr',
|
||||||
|
message: msg.trim(),
|
||||||
|
source: 'stdio'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
;(stdioTransport as any).stdout?.on('data', (data: any) => {
|
||||||
|
const msg = data.toString()
|
||||||
|
this.emitServerLog(server, {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
level: 'stdout',
|
||||||
|
message: msg.trim(),
|
||||||
|
source: 'stdio'
|
||||||
|
})
|
||||||
|
})
|
||||||
return stdioTransport
|
return stdioTransport
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Either baseUrl or command must be provided')
|
throw new Error('Either baseUrl or command must be provided')
|
||||||
@ -436,6 +468,13 @@ class McpService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.emitServerLog(server, {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
level: 'info',
|
||||||
|
message: 'Server connected',
|
||||||
|
source: 'client'
|
||||||
|
})
|
||||||
|
|
||||||
// Store the new client in the cache
|
// Store the new client in the cache
|
||||||
this.clients.set(serverKey, client)
|
this.clients.set(serverKey, client)
|
||||||
|
|
||||||
@ -446,9 +485,22 @@ class McpService {
|
|||||||
this.clearServerCache(serverKey)
|
this.clearServerCache(serverKey)
|
||||||
|
|
||||||
logger.debug(`Activated server: ${server.name}`)
|
logger.debug(`Activated server: ${server.name}`)
|
||||||
|
this.emitServerLog(server, {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
level: 'info',
|
||||||
|
message: 'Server activated',
|
||||||
|
source: 'client'
|
||||||
|
})
|
||||||
return client
|
return client
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
getServerLogger(server).error(`Error activating server ${server.name}`, error as Error)
|
getServerLogger(server).error(`Error activating server ${server.name}`, error as Error)
|
||||||
|
this.emitServerLog(server, {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
level: 'error',
|
||||||
|
message: `Error activating server: ${(error as Error)?.message}`,
|
||||||
|
data: redactSensitive(error),
|
||||||
|
source: 'client'
|
||||||
|
})
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -506,6 +558,16 @@ class McpService {
|
|||||||
// Set up logging message notification handler
|
// Set up logging message notification handler
|
||||||
client.setNotificationHandler(LoggingMessageNotificationSchema, async (notification) => {
|
client.setNotificationHandler(LoggingMessageNotificationSchema, async (notification) => {
|
||||||
logger.debug(`Message from server ${server.name}:`, notification.params)
|
logger.debug(`Message from server ${server.name}:`, notification.params)
|
||||||
|
const msg = notification.params?.message
|
||||||
|
if (msg) {
|
||||||
|
this.emitServerLog(server, {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
level: (notification.params?.level as MCPServerLogEntry['level']) || 'info',
|
||||||
|
message: typeof msg === 'string' ? msg : JSON.stringify(msg),
|
||||||
|
data: redactSensitive(notification.params?.data),
|
||||||
|
source: notification.params?.logger || 'server'
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
getServerLogger(server).debug(`Set up notification handlers`)
|
getServerLogger(server).debug(`Set up notification handlers`)
|
||||||
@ -540,6 +602,7 @@ class McpService {
|
|||||||
this.clients.delete(serverKey)
|
this.clients.delete(serverKey)
|
||||||
// Clear all caches for this server
|
// Clear all caches for this server
|
||||||
this.clearServerCache(serverKey)
|
this.clearServerCache(serverKey)
|
||||||
|
this.serverLogs.remove(serverKey)
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`No client found for server`, { serverKey })
|
logger.warn(`No client found for server`, { serverKey })
|
||||||
}
|
}
|
||||||
@ -548,6 +611,12 @@ class McpService {
|
|||||||
async stopServer(_: Electron.IpcMainInvokeEvent, server: MCPServer) {
|
async stopServer(_: Electron.IpcMainInvokeEvent, server: MCPServer) {
|
||||||
const serverKey = this.getServerKey(server)
|
const serverKey = this.getServerKey(server)
|
||||||
getServerLogger(server).debug(`Stopping server`)
|
getServerLogger(server).debug(`Stopping server`)
|
||||||
|
this.emitServerLog(server, {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
level: 'info',
|
||||||
|
message: 'Stopping server',
|
||||||
|
source: 'client'
|
||||||
|
})
|
||||||
await this.closeClient(serverKey)
|
await this.closeClient(serverKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -574,6 +643,12 @@ class McpService {
|
|||||||
async restartServer(_: Electron.IpcMainInvokeEvent, server: MCPServer) {
|
async restartServer(_: Electron.IpcMainInvokeEvent, server: MCPServer) {
|
||||||
getServerLogger(server).debug(`Restarting server`)
|
getServerLogger(server).debug(`Restarting server`)
|
||||||
const serverKey = this.getServerKey(server)
|
const serverKey = this.getServerKey(server)
|
||||||
|
this.emitServerLog(server, {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
level: 'info',
|
||||||
|
message: 'Restarting server',
|
||||||
|
source: 'client'
|
||||||
|
})
|
||||||
await this.closeClient(serverKey)
|
await this.closeClient(serverKey)
|
||||||
// Clear cache before restarting to ensure fresh data
|
// Clear cache before restarting to ensure fresh data
|
||||||
this.clearServerCache(serverKey)
|
this.clearServerCache(serverKey)
|
||||||
@ -606,9 +681,22 @@ class McpService {
|
|||||||
// Attempt to list tools as a way to check connectivity
|
// Attempt to list tools as a way to check connectivity
|
||||||
await client.listTools()
|
await client.listTools()
|
||||||
getServerLogger(server).debug(`Connectivity check successful`)
|
getServerLogger(server).debug(`Connectivity check successful`)
|
||||||
|
this.emitServerLog(server, {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
level: 'info',
|
||||||
|
message: 'Connectivity check successful',
|
||||||
|
source: 'connectivity'
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
getServerLogger(server).error(`Connectivity check failed`, error as Error)
|
getServerLogger(server).error(`Connectivity check failed`, error as Error)
|
||||||
|
this.emitServerLog(server, {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
level: 'error',
|
||||||
|
message: `Connectivity check failed: ${(error as Error).message}`,
|
||||||
|
data: redactSensitive(error),
|
||||||
|
source: 'connectivity'
|
||||||
|
})
|
||||||
// Close the client if connectivity check fails to ensure a clean state for the next attempt
|
// Close the client if connectivity check fails to ensure a clean state for the next attempt
|
||||||
const serverKey = this.getServerKey(server)
|
const serverKey = this.getServerKey(server)
|
||||||
await this.closeClient(serverKey)
|
await this.closeClient(serverKey)
|
||||||
|
|||||||
@ -1393,6 +1393,50 @@ export class SelectionService {
|
|||||||
actionWindow.setAlwaysOnTop(isPinned)
|
actionWindow.setAlwaysOnTop(isPinned)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Windows only] Manual window resize handler
|
||||||
|
*
|
||||||
|
* ELECTRON BUG WORKAROUND:
|
||||||
|
* In Electron, when using `frame: false` + `transparent: true`, the native window
|
||||||
|
* resize functionality is broken on Windows. This is a known Electron bug.
|
||||||
|
* See: https://github.com/electron/electron/issues/48554
|
||||||
|
*
|
||||||
|
* This method can be removed once the Electron bug is fixed.
|
||||||
|
*/
|
||||||
|
public resizeActionWindow(actionWindow: BrowserWindow, deltaX: number, deltaY: number, direction: string): void {
|
||||||
|
const bounds = actionWindow.getBounds()
|
||||||
|
const minWidth = 300
|
||||||
|
const minHeight = 200
|
||||||
|
|
||||||
|
let { x, y, width, height } = bounds
|
||||||
|
|
||||||
|
// Handle horizontal resize
|
||||||
|
if (direction.includes('e')) {
|
||||||
|
width = Math.max(minWidth, width + deltaX)
|
||||||
|
}
|
||||||
|
if (direction.includes('w')) {
|
||||||
|
const newWidth = Math.max(minWidth, width - deltaX)
|
||||||
|
if (newWidth !== width) {
|
||||||
|
x = x + (width - newWidth)
|
||||||
|
width = newWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle vertical resize
|
||||||
|
if (direction.includes('s')) {
|
||||||
|
height = Math.max(minHeight, height + deltaY)
|
||||||
|
}
|
||||||
|
if (direction.includes('n')) {
|
||||||
|
const newHeight = Math.max(minHeight, height - deltaY)
|
||||||
|
if (newHeight !== height) {
|
||||||
|
y = y + (height - newHeight)
|
||||||
|
height = newHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actionWindow.setBounds({ x, y, width, height })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update trigger mode behavior
|
* Update trigger mode behavior
|
||||||
* Switches between selection-based and alt-key based triggering
|
* Switches between selection-based and alt-key based triggering
|
||||||
@ -1510,6 +1554,18 @@ export class SelectionService {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// [Windows only] Electron bug workaround - can be removed once fixed
|
||||||
|
// See: https://github.com/electron/electron/issues/48554
|
||||||
|
ipcMain.handle(
|
||||||
|
IpcChannel.Selection_ActionWindowResize,
|
||||||
|
(event, deltaX: number, deltaY: number, direction: string) => {
|
||||||
|
const actionWindow = BrowserWindow.fromWebContents(event.sender)
|
||||||
|
if (actionWindow) {
|
||||||
|
selectionService?.resizeActionWindow(actionWindow, deltaX, deltaY, direction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
this.isIpcHandlerRegistered = true
|
this.isIpcHandlerRegistered = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,15 @@ function getShortcutHandler(shortcut: Shortcut) {
|
|||||||
}
|
}
|
||||||
case 'mini_window':
|
case 'mini_window':
|
||||||
return () => {
|
return () => {
|
||||||
|
// 在处理器内部检查QuickAssistant状态,而不是在注册时检查
|
||||||
|
const quickAssistantEnabled = configManager.getEnableQuickAssistant()
|
||||||
|
logger.info(`mini_window shortcut triggered, QuickAssistant enabled: ${quickAssistantEnabled}`)
|
||||||
|
|
||||||
|
if (!quickAssistantEnabled) {
|
||||||
|
logger.warn('QuickAssistant is disabled, ignoring mini_window shortcut trigger')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
windowService.toggleMiniWindow()
|
windowService.toggleMiniWindow()
|
||||||
}
|
}
|
||||||
case 'selection_assistant_toggle':
|
case 'selection_assistant_toggle':
|
||||||
@ -190,11 +199,10 @@ export function registerShortcuts(window: BrowserWindow) {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case 'mini_window':
|
case 'mini_window':
|
||||||
//available only when QuickAssistant enabled
|
// 移除注册时的条件检查,在处理器内部进行检查
|
||||||
if (!configManager.getEnableQuickAssistant()) {
|
logger.info(`Processing mini_window shortcut, enabled: ${shortcut.enabled}`)
|
||||||
return
|
|
||||||
}
|
|
||||||
showMiniWindowAccelerator = formatShortcutKey(shortcut.shortcut)
|
showMiniWindowAccelerator = formatShortcutKey(shortcut.shortcut)
|
||||||
|
logger.debug(`Mini window accelerator set to: ${showMiniWindowAccelerator}`)
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'selection_assistant_toggle':
|
case 'selection_assistant_toggle':
|
||||||
|
|||||||
29
src/main/services/__tests__/ServerLogBuffer.test.ts
Normal file
29
src/main/services/__tests__/ServerLogBuffer.test.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { ServerLogBuffer } from '../mcp/ServerLogBuffer'
|
||||||
|
|
||||||
|
describe('ServerLogBuffer', () => {
|
||||||
|
it('keeps a bounded number of entries per server', () => {
|
||||||
|
const buffer = new ServerLogBuffer(3)
|
||||||
|
const key = 'srv'
|
||||||
|
|
||||||
|
buffer.append(key, { timestamp: 1, level: 'info', message: 'a' })
|
||||||
|
buffer.append(key, { timestamp: 2, level: 'info', message: 'b' })
|
||||||
|
buffer.append(key, { timestamp: 3, level: 'info', message: 'c' })
|
||||||
|
buffer.append(key, { timestamp: 4, level: 'info', message: 'd' })
|
||||||
|
|
||||||
|
const logs = buffer.get(key)
|
||||||
|
expect(logs).toHaveLength(3)
|
||||||
|
expect(logs[0].message).toBe('b')
|
||||||
|
expect(logs[2].message).toBe('d')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('isolates entries by server key', () => {
|
||||||
|
const buffer = new ServerLogBuffer(5)
|
||||||
|
buffer.append('one', { timestamp: 1, level: 'info', message: 'a' })
|
||||||
|
buffer.append('two', { timestamp: 2, level: 'info', message: 'b' })
|
||||||
|
|
||||||
|
expect(buffer.get('one')).toHaveLength(1)
|
||||||
|
expect(buffer.get('two')).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -78,7 +78,7 @@ export abstract class BaseService {
|
|||||||
* Get database instance
|
* Get database instance
|
||||||
* Automatically waits for initialization to complete
|
* Automatically waits for initialization to complete
|
||||||
*/
|
*/
|
||||||
protected async getDatabase() {
|
public async getDatabase() {
|
||||||
const dbManager = await DatabaseManager.getInstance()
|
const dbManager = await DatabaseManager.getInstance()
|
||||||
return dbManager.getDatabase()
|
return dbManager.getDatabase()
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/main/services/mcp/ServerLogBuffer.ts
Normal file
36
src/main/services/mcp/ServerLogBuffer.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
export type MCPServerLogEntry = {
|
||||||
|
timestamp: number
|
||||||
|
level: 'debug' | 'info' | 'warn' | 'error' | 'stderr' | 'stdout'
|
||||||
|
message: string
|
||||||
|
data?: any
|
||||||
|
source?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight ring buffer for per-server MCP logs.
|
||||||
|
*/
|
||||||
|
export class ServerLogBuffer {
|
||||||
|
private maxEntries: number
|
||||||
|
private logs: Map<string, MCPServerLogEntry[]> = new Map()
|
||||||
|
|
||||||
|
constructor(maxEntries = 200) {
|
||||||
|
this.maxEntries = maxEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
append(serverKey: string, entry: MCPServerLogEntry) {
|
||||||
|
const list = this.logs.get(serverKey) ?? []
|
||||||
|
list.push(entry)
|
||||||
|
if (list.length > this.maxEntries) {
|
||||||
|
list.splice(0, list.length - this.maxEntries)
|
||||||
|
}
|
||||||
|
this.logs.set(serverKey, list)
|
||||||
|
}
|
||||||
|
|
||||||
|
get(serverKey: string): MCPServerLogEntry[] {
|
||||||
|
return [...(this.logs.get(serverKey) ?? [])]
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(serverKey: string) {
|
||||||
|
this.logs.delete(serverKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import type { SpanContext } from '@opentelemetry/api'
|
|||||||
import type { TerminalConfig, UpgradeChannel } from '@shared/config/constant'
|
import type { TerminalConfig, UpgradeChannel } from '@shared/config/constant'
|
||||||
import type { LogLevel, LogSourceWithContext } from '@shared/config/logger'
|
import type { LogLevel, LogSourceWithContext } from '@shared/config/logger'
|
||||||
import type { FileChangeEvent, WebviewKeyEvent } from '@shared/config/types'
|
import type { FileChangeEvent, WebviewKeyEvent } from '@shared/config/types'
|
||||||
|
import type { MCPServerLogEntry } from '@shared/config/types'
|
||||||
import { IpcChannel } from '@shared/IpcChannel'
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
import type { Notification } from '@types'
|
import type { Notification } from '@types'
|
||||||
import type {
|
import type {
|
||||||
@ -372,7 +373,16 @@ const api = {
|
|||||||
},
|
},
|
||||||
abortTool: (callId: string) => ipcRenderer.invoke(IpcChannel.Mcp_AbortTool, callId),
|
abortTool: (callId: string) => ipcRenderer.invoke(IpcChannel.Mcp_AbortTool, callId),
|
||||||
getServerVersion: (server: MCPServer): Promise<string | null> =>
|
getServerVersion: (server: MCPServer): Promise<string | null> =>
|
||||||
ipcRenderer.invoke(IpcChannel.Mcp_GetServerVersion, server)
|
ipcRenderer.invoke(IpcChannel.Mcp_GetServerVersion, server),
|
||||||
|
getServerLogs: (server: MCPServer): Promise<MCPServerLogEntry[]> =>
|
||||||
|
ipcRenderer.invoke(IpcChannel.Mcp_GetServerLogs, server),
|
||||||
|
onServerLog: (callback: (log: MCPServerLogEntry & { serverId?: string }) => void) => {
|
||||||
|
const listener = (_event: Electron.IpcRendererEvent, log: MCPServerLogEntry & { serverId?: string }) => {
|
||||||
|
callback(log)
|
||||||
|
}
|
||||||
|
ipcRenderer.on(IpcChannel.Mcp_ServerLog, listener)
|
||||||
|
return () => ipcRenderer.off(IpcChannel.Mcp_ServerLog, listener)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
python: {
|
python: {
|
||||||
execute: (script: string, context?: Record<string, any>, timeout?: number) =>
|
execute: (script: string, context?: Record<string, any>, timeout?: number) =>
|
||||||
@ -456,7 +466,10 @@ const api = {
|
|||||||
ipcRenderer.invoke(IpcChannel.Selection_ProcessAction, actionItem, isFullScreen),
|
ipcRenderer.invoke(IpcChannel.Selection_ProcessAction, actionItem, isFullScreen),
|
||||||
closeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowClose),
|
closeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowClose),
|
||||||
minimizeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowMinimize),
|
minimizeActionWindow: () => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowMinimize),
|
||||||
pinActionWindow: (isPinned: boolean) => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowPin, isPinned)
|
pinActionWindow: (isPinned: boolean) => ipcRenderer.invoke(IpcChannel.Selection_ActionWindowPin, isPinned),
|
||||||
|
// [Windows only] Electron bug workaround - can be removed once https://github.com/electron/electron/issues/48554 is fixed
|
||||||
|
resizeActionWindow: (deltaX: number, deltaY: number, direction: string) =>
|
||||||
|
ipcRenderer.invoke(IpcChannel.Selection_ActionWindowResize, deltaX, deltaY, direction)
|
||||||
},
|
},
|
||||||
agentTools: {
|
agentTools: {
|
||||||
respondToPermission: (payload: {
|
respondToPermission: (payload: {
|
||||||
|
|||||||
@ -91,7 +91,9 @@ export default class ModernAiProvider {
|
|||||||
if (this.isModel(modelOrProvider)) {
|
if (this.isModel(modelOrProvider)) {
|
||||||
// 传入的是 Model
|
// 传入的是 Model
|
||||||
this.model = modelOrProvider
|
this.model = modelOrProvider
|
||||||
this.actualProvider = provider ? adaptProvider({ provider }) : getActualProvider(modelOrProvider)
|
this.actualProvider = provider
|
||||||
|
? adaptProvider({ provider, model: modelOrProvider })
|
||||||
|
: getActualProvider(modelOrProvider)
|
||||||
// 只保存配置,不预先创建executor
|
// 只保存配置,不预先创建executor
|
||||||
this.config = providerToAiSdkConfig(this.actualProvider, modelOrProvider)
|
this.config = providerToAiSdkConfig(this.actualProvider, modelOrProvider)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -2,9 +2,10 @@ import { loggerService } from '@logger'
|
|||||||
import {
|
import {
|
||||||
getModelSupportedVerbosity,
|
getModelSupportedVerbosity,
|
||||||
isFunctionCallingModel,
|
isFunctionCallingModel,
|
||||||
isNotSupportTemperatureAndTopP,
|
|
||||||
isOpenAIModel,
|
isOpenAIModel,
|
||||||
isSupportFlexServiceTierModel
|
isSupportFlexServiceTierModel,
|
||||||
|
isSupportTemperatureModel,
|
||||||
|
isSupportTopPModel
|
||||||
} from '@renderer/config/models'
|
} from '@renderer/config/models'
|
||||||
import { REFERENCE_PROMPT } from '@renderer/config/prompts'
|
import { REFERENCE_PROMPT } from '@renderer/config/prompts'
|
||||||
import { getLMStudioKeepAliveTime } from '@renderer/hooks/useLMStudio'
|
import { getLMStudioKeepAliveTime } from '@renderer/hooks/useLMStudio'
|
||||||
@ -200,7 +201,7 @@ export abstract class BaseApiClient<
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getTemperature(assistant: Assistant, model: Model): number | undefined {
|
public getTemperature(assistant: Assistant, model: Model): number | undefined {
|
||||||
if (isNotSupportTemperatureAndTopP(model)) {
|
if (!isSupportTemperatureModel(model)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const assistantSettings = getAssistantSettings(assistant)
|
const assistantSettings = getAssistantSettings(assistant)
|
||||||
@ -208,7 +209,7 @@ export abstract class BaseApiClient<
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getTopP(assistant: Assistant, model: Model): number | undefined {
|
public getTopP(assistant: Assistant, model: Model): number | undefined {
|
||||||
if (isNotSupportTemperatureAndTopP(model)) {
|
if (!isSupportTopPModel(model)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const assistantSettings = getAssistantSettings(assistant)
|
const assistantSettings = getAssistantSettings(assistant)
|
||||||
|
|||||||
@ -124,7 +124,8 @@ export class AnthropicAPIClient extends BaseApiClient<
|
|||||||
|
|
||||||
override async listModels(): Promise<Anthropic.ModelInfo[]> {
|
override async listModels(): Promise<Anthropic.ModelInfo[]> {
|
||||||
const sdk = (await this.getSdkInstance()) as Anthropic
|
const sdk = (await this.getSdkInstance()) as Anthropic
|
||||||
const response = await sdk.models.list()
|
// prevent auto appended /v1. It's included in baseUrl.
|
||||||
|
const response = await sdk.models.list({ path: '/models' })
|
||||||
return response.data
|
return response.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -173,13 +173,15 @@ export class GeminiAPIClient extends BaseApiClient<
|
|||||||
return this.sdkInstance
|
return this.sdkInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const apiVersion = this.getApiVersion()
|
||||||
|
|
||||||
this.sdkInstance = new GoogleGenAI({
|
this.sdkInstance = new GoogleGenAI({
|
||||||
vertexai: false,
|
vertexai: false,
|
||||||
apiKey: this.apiKey,
|
apiKey: this.apiKey,
|
||||||
apiVersion: this.getApiVersion(),
|
apiVersion,
|
||||||
httpOptions: {
|
httpOptions: {
|
||||||
baseUrl: this.getBaseURL(),
|
baseUrl: this.getBaseURL(),
|
||||||
apiVersion: this.getApiVersion(),
|
apiVersion,
|
||||||
headers: {
|
headers: {
|
||||||
...this.provider.extra_headers
|
...this.provider.extra_headers
|
||||||
}
|
}
|
||||||
@ -200,7 +202,7 @@ export class GeminiAPIClient extends BaseApiClient<
|
|||||||
return trailingVersion
|
return trailingVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'v1beta'
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import type {
|
|||||||
OpenAISdkRawOutput,
|
OpenAISdkRawOutput,
|
||||||
ReasoningEffortOptionalParams
|
ReasoningEffortOptionalParams
|
||||||
} from '@renderer/types/sdk'
|
} from '@renderer/types/sdk'
|
||||||
import { formatApiHost, withoutTrailingSlash } from '@renderer/utils/api'
|
import { withoutTrailingSlash } from '@renderer/utils/api'
|
||||||
import { isOllamaProvider } from '@renderer/utils/provider'
|
import { isOllamaProvider } from '@renderer/utils/provider'
|
||||||
|
|
||||||
import { BaseApiClient } from '../BaseApiClient'
|
import { BaseApiClient } from '../BaseApiClient'
|
||||||
@ -49,8 +49,9 @@ export abstract class OpenAIBaseClient<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 仅适用于openai
|
// 仅适用于openai
|
||||||
override getBaseURL(isSupportedAPIVerion: boolean = true): string {
|
override getBaseURL(): string {
|
||||||
return formatApiHost(this.provider.apiHost, isSupportedAPIVerion)
|
// apiHost is formatted when called by AiProvider
|
||||||
|
return this.provider.apiHost
|
||||||
}
|
}
|
||||||
|
|
||||||
override async generateImage({
|
override async generateImage({
|
||||||
@ -100,6 +101,17 @@ export abstract class OpenAIBaseClient<
|
|||||||
override async listModels(): Promise<OpenAI.Models.Model[]> {
|
override async listModels(): Promise<OpenAI.Models.Model[]> {
|
||||||
try {
|
try {
|
||||||
const sdk = await this.getSdkInstance()
|
const sdk = await this.getSdkInstance()
|
||||||
|
if (this.provider.id === 'openrouter') {
|
||||||
|
// https://openrouter.ai/docs/api/api-reference/embeddings/list-embeddings-models
|
||||||
|
const embedBaseUrl = 'https://openrouter.ai/api/v1/embeddings'
|
||||||
|
const embedSdk = sdk.withOptions({ baseURL: embedBaseUrl })
|
||||||
|
const modelPromise = sdk.models.list()
|
||||||
|
const embedModelPromise = embedSdk.models.list()
|
||||||
|
const [modelResponse, embedModelResponse] = await Promise.all([modelPromise, embedModelPromise])
|
||||||
|
const models = [...modelResponse.data, ...embedModelResponse.data]
|
||||||
|
const uniqueModels = Array.from(new Map(models.map((model) => [model.id, model])).values())
|
||||||
|
return uniqueModels.filter(isSupportedModel)
|
||||||
|
}
|
||||||
if (this.provider.id === 'github') {
|
if (this.provider.id === 'github') {
|
||||||
// GitHub Models 其 models 和 chat completions 两个接口的 baseUrl 不一样
|
// GitHub Models 其 models 和 chat completions 两个接口的 baseUrl 不一样
|
||||||
const baseUrl = 'https://models.github.ai/catalog/'
|
const baseUrl = 'https://models.github.ai/catalog/'
|
||||||
@ -118,7 +130,7 @@ export abstract class OpenAIBaseClient<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isOllamaProvider(this.provider)) {
|
if (isOllamaProvider(this.provider)) {
|
||||||
const baseUrl = withoutTrailingSlash(this.getBaseURL(false))
|
const baseUrl = withoutTrailingSlash(this.getBaseURL())
|
||||||
.replace(/\/v1$/, '')
|
.replace(/\/v1$/, '')
|
||||||
.replace(/\/api$/, '')
|
.replace(/\/api$/, '')
|
||||||
const response = await fetch(`${baseUrl}/api/tags`, {
|
const response = await fetch(`${baseUrl}/api/tags`, {
|
||||||
@ -173,6 +185,7 @@ export abstract class OpenAIBaseClient<
|
|||||||
|
|
||||||
let apiKeyForSdkInstance = this.apiKey
|
let apiKeyForSdkInstance = this.apiKey
|
||||||
let baseURLForSdkInstance = this.getBaseURL()
|
let baseURLForSdkInstance = this.getBaseURL()
|
||||||
|
logger.debug('baseURLForSdkInstance', { baseURLForSdkInstance })
|
||||||
let headersForSdkInstance = {
|
let headersForSdkInstance = {
|
||||||
...this.defaultHeaders(),
|
...this.defaultHeaders(),
|
||||||
...this.provider.extra_headers
|
...this.provider.extra_headers
|
||||||
@ -184,7 +197,7 @@ export abstract class OpenAIBaseClient<
|
|||||||
// this.provider.apiKey不允许修改
|
// this.provider.apiKey不允许修改
|
||||||
// this.provider.apiKey = token
|
// this.provider.apiKey = token
|
||||||
apiKeyForSdkInstance = token
|
apiKeyForSdkInstance = token
|
||||||
baseURLForSdkInstance = this.getBaseURL(false)
|
baseURLForSdkInstance = this.getBaseURL()
|
||||||
headersForSdkInstance = {
|
headersForSdkInstance = {
|
||||||
...headersForSdkInstance,
|
...headersForSdkInstance,
|
||||||
...COPILOT_DEFAULT_HEADERS
|
...COPILOT_DEFAULT_HEADERS
|
||||||
|
|||||||
@ -122,6 +122,7 @@ export class OpenAIResponseAPIClient extends OpenAIBaseClient<
|
|||||||
if (this.sdkInstance) {
|
if (this.sdkInstance) {
|
||||||
return this.sdkInstance
|
return this.sdkInstance
|
||||||
}
|
}
|
||||||
|
const baseUrl = this.getBaseURL()
|
||||||
|
|
||||||
if (this.provider.id === 'azure-openai' || this.provider.type === 'azure-openai') {
|
if (this.provider.id === 'azure-openai' || this.provider.type === 'azure-openai') {
|
||||||
return new AzureOpenAI({
|
return new AzureOpenAI({
|
||||||
@ -134,7 +135,7 @@ export class OpenAIResponseAPIClient extends OpenAIBaseClient<
|
|||||||
return new OpenAI({
|
return new OpenAI({
|
||||||
dangerouslyAllowBrowser: true,
|
dangerouslyAllowBrowser: true,
|
||||||
apiKey: this.apiKey,
|
apiKey: this.apiKey,
|
||||||
baseURL: this.getBaseURL(),
|
baseURL: baseUrl,
|
||||||
defaultHeaders: {
|
defaultHeaders: {
|
||||||
...this.defaultHeaders(),
|
...this.defaultHeaders(),
|
||||||
...this.provider.extra_headers
|
...this.provider.extra_headers
|
||||||
|
|||||||
@ -4,60 +4,81 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isClaude45ReasoningModel,
|
|
||||||
isClaudeReasoningModel,
|
isClaudeReasoningModel,
|
||||||
isMaxTemperatureOneModel,
|
isMaxTemperatureOneModel,
|
||||||
isNotSupportTemperatureAndTopP,
|
|
||||||
isSupportedFlexServiceTier,
|
isSupportedFlexServiceTier,
|
||||||
isSupportedThinkingTokenClaudeModel
|
isSupportedThinkingTokenClaudeModel,
|
||||||
|
isSupportTemperatureModel,
|
||||||
|
isSupportTopPModel,
|
||||||
|
isTemperatureTopPMutuallyExclusiveModel
|
||||||
} from '@renderer/config/models'
|
} from '@renderer/config/models'
|
||||||
import { getAssistantSettings, getProviderByModel } from '@renderer/services/AssistantService'
|
import {
|
||||||
|
DEFAULT_ASSISTANT_SETTINGS,
|
||||||
|
getAssistantSettings,
|
||||||
|
getProviderByModel
|
||||||
|
} from '@renderer/services/AssistantService'
|
||||||
import type { Assistant, Model } from '@renderer/types'
|
import type { Assistant, Model } from '@renderer/types'
|
||||||
import { defaultTimeout } from '@shared/config/constant'
|
import { defaultTimeout } from '@shared/config/constant'
|
||||||
|
|
||||||
import { getAnthropicThinkingBudget } from '../utils/reasoning'
|
import { getAnthropicThinkingBudget } from '../utils/reasoning'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Claude 4.5 推理模型:
|
* Retrieves the temperature parameter, adapting it based on assistant.settings and model capabilities.
|
||||||
* - 只启用 temperature → 使用 temperature
|
* - Disabled for Claude reasoning models when reasoning effort is set.
|
||||||
* - 只启用 top_p → 使用 top_p
|
* - Disabled for models that do not support temperature.
|
||||||
* - 同时启用 → temperature 生效,top_p 被忽略
|
* - Disabled for Claude 4.5 reasoning models when TopP is enabled and temperature is disabled.
|
||||||
* - 都不启用 → 都不使用
|
* Otherwise, returns the temperature value if the assistant has temperature enabled.
|
||||||
* 获取温度参数
|
|
||||||
*/
|
*/
|
||||||
export function getTemperature(assistant: Assistant, model: Model): number | undefined {
|
export function getTemperature(assistant: Assistant, model: Model): number | undefined {
|
||||||
if (assistant.settings?.reasoning_effort && isClaudeReasoningModel(model)) {
|
if (assistant.settings?.reasoning_effort && isClaudeReasoningModel(model)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isSupportTemperatureModel(model)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isNotSupportTemperatureAndTopP(model) ||
|
isTemperatureTopPMutuallyExclusiveModel(model) &&
|
||||||
(isClaude45ReasoningModel(model) && assistant.settings?.enableTopP && !assistant.settings?.enableTemperature)
|
assistant.settings?.enableTopP &&
|
||||||
|
!assistant.settings?.enableTemperature
|
||||||
) {
|
) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
const assistantSettings = getAssistantSettings(assistant)
|
const assistantSettings = getAssistantSettings(assistant)
|
||||||
let temperature = assistantSettings?.temperature
|
let temperature = assistantSettings?.temperature
|
||||||
if (temperature && isMaxTemperatureOneModel(model)) {
|
if (temperature && isMaxTemperatureOneModel(model)) {
|
||||||
temperature = Math.min(1, temperature)
|
temperature = Math.min(1, temperature)
|
||||||
}
|
}
|
||||||
return assistantSettings?.enableTemperature ? temperature : undefined
|
|
||||||
|
// FIXME: assistant.settings.enableTemperature should be always a boolean value.
|
||||||
|
const enableTemperature = assistantSettings?.enableTemperature ?? DEFAULT_ASSISTANT_SETTINGS.enableTemperature
|
||||||
|
return enableTemperature ? temperature : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 TopP 参数
|
* Retrieves the TopP parameter, adapting it based on assistant.settings and model capabilities.
|
||||||
|
* - Disabled for Claude reasoning models when reasoning effort is set.
|
||||||
|
* - Disabled for models that do not support TopP.
|
||||||
|
* - Disabled for Claude 4.5 reasoning models when temperature is explicitly enabled.
|
||||||
|
* Otherwise, returns the TopP value if the assistant has TopP enabled.
|
||||||
*/
|
*/
|
||||||
export function getTopP(assistant: Assistant, model: Model): number | undefined {
|
export function getTopP(assistant: Assistant, model: Model): number | undefined {
|
||||||
if (assistant.settings?.reasoning_effort && isClaudeReasoningModel(model)) {
|
if (assistant.settings?.reasoning_effort && isClaudeReasoningModel(model)) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
if (
|
if (!isSupportTopPModel(model)) {
|
||||||
isNotSupportTemperatureAndTopP(model) ||
|
|
||||||
(isClaude45ReasoningModel(model) && assistant.settings?.enableTemperature)
|
|
||||||
) {
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
if (isTemperatureTopPMutuallyExclusiveModel(model) && assistant.settings?.enableTemperature) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
const assistantSettings = getAssistantSettings(assistant)
|
const assistantSettings = getAssistantSettings(assistant)
|
||||||
return assistantSettings?.enableTopP ? assistantSettings?.topP : undefined
|
// FIXME: assistant.settings.enableTopP should be always a boolean value.
|
||||||
|
const enableTopP = assistantSettings.enableTopP ?? DEFAULT_ASSISTANT_SETTINGS.enableTopP
|
||||||
|
return enableTopP ? assistantSettings?.topP : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -42,7 +42,8 @@ vi.mock('@renderer/utils/api', () => ({
|
|||||||
routeToEndpoint: vi.fn((host) => ({
|
routeToEndpoint: vi.fn((host) => ({
|
||||||
baseURL: host,
|
baseURL: host,
|
||||||
endpoint: '/chat/completions'
|
endpoint: '/chat/completions'
|
||||||
}))
|
})),
|
||||||
|
isWithTrailingSharp: vi.fn((host) => host?.endsWith('#') || false)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Also mock @shared/api since formatProviderApiHost uses it directly
|
// Also mock @shared/api since formatProviderApiHost uses it directly
|
||||||
@ -241,12 +242,19 @@ describe('CherryAI provider configuration', () => {
|
|||||||
// Mock the functions to simulate non-CherryAI provider
|
// Mock the functions to simulate non-CherryAI provider
|
||||||
vi.mocked(isCherryAIProvider).mockReturnValue(false)
|
vi.mocked(isCherryAIProvider).mockReturnValue(false)
|
||||||
vi.mocked(getProviderByModel).mockReturnValue(provider)
|
vi.mocked(getProviderByModel).mockReturnValue(provider)
|
||||||
|
// Mock isWithTrailingSharp to return false for this test
|
||||||
|
vi.mocked(formatApiHost as any).mockImplementation((host, isSupportedAPIVersion = true) => {
|
||||||
|
if (isSupportedAPIVersion === false) {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
return `${host}/v1`
|
||||||
|
})
|
||||||
|
|
||||||
// Call getActualProvider
|
// Call getActualProvider
|
||||||
const actualProvider = getActualProvider(model)
|
const actualProvider = getActualProvider(model)
|
||||||
|
|
||||||
// Verify that formatApiHost was called with default parameters (true)
|
// Verify that formatApiHost was called with appendApiVersion parameter
|
||||||
expect(formatApiHost).toHaveBeenCalledWith('https://api.openai.com')
|
expect(formatApiHost).toHaveBeenCalledWith('https://api.openai.com', true)
|
||||||
expect(actualProvider.apiHost).toBe('https://api.openai.com/v1')
|
expect(actualProvider.apiHost).toBe('https://api.openai.com/v1')
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -317,12 +325,19 @@ describe('Perplexity provider configuration', () => {
|
|||||||
vi.mocked(isCherryAIProvider).mockReturnValue(false)
|
vi.mocked(isCherryAIProvider).mockReturnValue(false)
|
||||||
vi.mocked(isPerplexityProvider).mockReturnValue(false)
|
vi.mocked(isPerplexityProvider).mockReturnValue(false)
|
||||||
vi.mocked(getProviderByModel).mockReturnValue(provider)
|
vi.mocked(getProviderByModel).mockReturnValue(provider)
|
||||||
|
// Mock isWithTrailingSharp to return false for this test
|
||||||
|
vi.mocked(formatApiHost as any).mockImplementation((host, isSupportedAPIVersion = true) => {
|
||||||
|
if (isSupportedAPIVersion === false) {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
return `${host}/v1`
|
||||||
|
})
|
||||||
|
|
||||||
// Call getActualProvider
|
// Call getActualProvider
|
||||||
const actualProvider = getActualProvider(model)
|
const actualProvider = getActualProvider(model)
|
||||||
|
|
||||||
// Verify that formatApiHost was called with default parameters (true)
|
// Verify that formatApiHost was called with appendApiVersion parameter
|
||||||
expect(formatApiHost).toHaveBeenCalledWith('https://api.openai.com')
|
expect(formatApiHost).toHaveBeenCalledWith('https://api.openai.com', true)
|
||||||
expect(actualProvider.apiHost).toBe('https://api.openai.com/v1')
|
expect(actualProvider.apiHost).toBe('https://api.openai.com/v1')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { convertImageToPng } from '@renderer/utils/image'
|
|||||||
import type { ImageProps as AntImageProps } from 'antd'
|
import type { ImageProps as AntImageProps } from 'antd'
|
||||||
import { Dropdown, Image as AntImage, Space } from 'antd'
|
import { Dropdown, Image as AntImage, Space } from 'antd'
|
||||||
import { Base64 } from 'js-base64'
|
import { Base64 } from 'js-base64'
|
||||||
import { DownloadIcon, ImageIcon } from 'lucide-react'
|
import { DownloadIcon } from 'lucide-react'
|
||||||
import mime from 'mime'
|
import mime from 'mime'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -73,9 +73,15 @@ const ImageViewer: React.FC<ImageViewerProps> = ({ src, style, ...props }) => {
|
|||||||
const getContextMenuItems = (src: string, size: number = 14) => {
|
const getContextMenuItems = (src: string, size: number = 14) => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
key: 'copy-url',
|
key: 'copy-image',
|
||||||
label: t('common.copy'),
|
label: t('common.copy'),
|
||||||
icon: <CopyIcon size={size} />,
|
icon: <CopyIcon size={size} />,
|
||||||
|
onClick: () => handleCopyImage(src)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'copy-url',
|
||||||
|
label: t('preview.copy.src'),
|
||||||
|
icon: <CopyIcon size={size} />,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
navigator.clipboard.writeText(src)
|
navigator.clipboard.writeText(src)
|
||||||
window.toast.success(t('message.copy.success'))
|
window.toast.success(t('message.copy.success'))
|
||||||
@ -86,12 +92,6 @@ const ImageViewer: React.FC<ImageViewerProps> = ({ src, style, ...props }) => {
|
|||||||
label: t('common.download'),
|
label: t('common.download'),
|
||||||
icon: <DownloadIcon size={size} />,
|
icon: <DownloadIcon size={size} />,
|
||||||
onClick: () => download(src)
|
onClick: () => download(src)
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'copy-image',
|
|
||||||
label: t('preview.copy.image'),
|
|
||||||
icon: <ImageIcon size={size} />,
|
|
||||||
onClick: () => handleCopyImage(src)
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { TopView } from '@renderer/components/TopView'
|
import { TopView } from '@renderer/components/TopView'
|
||||||
import { handleSaveData } from '@renderer/store'
|
import { handleSaveData, useAppDispatch } from '@renderer/store'
|
||||||
|
import { setUpdateState } from '@renderer/store/runtime'
|
||||||
import { Button, Modal } from 'antd'
|
import { Button, Modal } from 'antd'
|
||||||
import type { ReleaseNoteInfo, UpdateInfo } from 'builder-util-runtime'
|
import type { ReleaseNoteInfo, UpdateInfo } from 'builder-util-runtime'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
@ -22,6 +23,7 @@ const PopupContainer: React.FC<Props> = ({ releaseInfo, resolve }) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [open, setOpen] = useState(true)
|
const [open, setOpen] = useState(true)
|
||||||
const [isInstalling, setIsInstalling] = useState(false)
|
const [isInstalling, setIsInstalling] = useState(false)
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (releaseInfo) {
|
if (releaseInfo) {
|
||||||
@ -50,6 +52,11 @@ const PopupContainer: React.FC<Props> = ({ releaseInfo, resolve }) => {
|
|||||||
resolve({})
|
resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onIgnore = () => {
|
||||||
|
dispatch(setUpdateState({ ignore: true }))
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
UpdateDialogPopup.hide = onCancel
|
UpdateDialogPopup.hide = onCancel
|
||||||
|
|
||||||
const releaseNotes = releaseInfo?.releaseNotes
|
const releaseNotes = releaseInfo?.releaseNotes
|
||||||
@ -69,7 +76,7 @@ const PopupContainer: React.FC<Props> = ({ releaseInfo, resolve }) => {
|
|||||||
centered
|
centered
|
||||||
width={720}
|
width={720}
|
||||||
footer={[
|
footer={[
|
||||||
<Button key="later" onClick={onCancel} disabled={isInstalling}>
|
<Button key="later" onClick={onIgnore} disabled={isInstalling}>
|
||||||
{t('update.later')}
|
{t('update.later')}
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button key="install" type="primary" onClick={handleInstall} loading={isInstalling}>
|
<Button key="install" type="primary" onClick={handleInstall} loading={isInstalling}>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { loggerService } from '@logger'
|
import { loggerService } from '@logger'
|
||||||
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
||||||
|
import { HelpTooltip } from '@renderer/components/TooltipIcons'
|
||||||
import { TopView } from '@renderer/components/TopView'
|
import { TopView } from '@renderer/components/TopView'
|
||||||
import { permissionModeCards } from '@renderer/config/agent'
|
import { permissionModeCards } from '@renderer/config/agent'
|
||||||
import { useAgents } from '@renderer/hooks/agents/useAgents'
|
import { useAgents } from '@renderer/hooks/agents/useAgents'
|
||||||
@ -340,9 +341,12 @@ const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
|
|||||||
</FormRow>
|
</FormRow>
|
||||||
|
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<Label>
|
<div className="flex items-center gap-2">
|
||||||
{t('common.model')} <RequiredMark>*</RequiredMark>
|
<Label>
|
||||||
</Label>
|
{t('common.model')} <RequiredMark>*</RequiredMark>
|
||||||
|
</Label>
|
||||||
|
<HelpTooltip title={t('agent.add.model.tooltip')} />
|
||||||
|
</div>
|
||||||
<SelectAgentBaseModelButton
|
<SelectAgentBaseModelButton
|
||||||
agentBase={tempAgentBase}
|
agentBase={tempAgentBase}
|
||||||
onSelect={handleModelSelect}
|
onSelect={handleModelSelect}
|
||||||
|
|||||||
@ -1016,7 +1016,7 @@ describe('Gemini Models', () => {
|
|||||||
provider: '',
|
provider: '',
|
||||||
group: ''
|
group: ''
|
||||||
})
|
})
|
||||||
).toBe(false)
|
).toBe(true)
|
||||||
expect(
|
expect(
|
||||||
isSupportedThinkingTokenGeminiModel({
|
isSupportedThinkingTokenGeminiModel({
|
||||||
id: 'gemini-3.0-flash-image-preview',
|
id: 'gemini-3.0-flash-image-preview',
|
||||||
@ -1224,7 +1224,7 @@ describe('Gemini Models', () => {
|
|||||||
provider: '',
|
provider: '',
|
||||||
group: ''
|
group: ''
|
||||||
})
|
})
|
||||||
).toBe(false)
|
).toBe(true)
|
||||||
expect(
|
expect(
|
||||||
isGeminiReasoningModel({
|
isGeminiReasoningModel({
|
||||||
id: 'gemini-3.5-flash-image-preview',
|
id: 'gemini-3.5-flash-image-preview',
|
||||||
|
|||||||
@ -26,11 +26,13 @@ import {
|
|||||||
isGenerateImageModels,
|
isGenerateImageModels,
|
||||||
isMaxTemperatureOneModel,
|
isMaxTemperatureOneModel,
|
||||||
isNotSupportSystemMessageModel,
|
isNotSupportSystemMessageModel,
|
||||||
isNotSupportTemperatureAndTopP,
|
|
||||||
isNotSupportTextDeltaModel,
|
isNotSupportTextDeltaModel,
|
||||||
isSupportedFlexServiceTier,
|
isSupportedFlexServiceTier,
|
||||||
isSupportedModel,
|
isSupportedModel,
|
||||||
isSupportFlexServiceTierModel,
|
isSupportFlexServiceTierModel,
|
||||||
|
isSupportTemperatureModel,
|
||||||
|
isSupportTopPModel,
|
||||||
|
isTemperatureTopPMutuallyExclusiveModel,
|
||||||
isVisionModels,
|
isVisionModels,
|
||||||
isZhipuModel
|
isZhipuModel
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
@ -303,27 +305,104 @@ describe('model utils', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Temperature and top-p support', () => {
|
describe('Temperature and top-p support', () => {
|
||||||
describe('isNotSupportTemperatureAndTopP', () => {
|
describe('isSupportTemperatureModel', () => {
|
||||||
it('returns true for reasoning models', () => {
|
it('returns false for reasoning models (non-open weight)', () => {
|
||||||
const model = createModel({ id: 'o1' })
|
const model = createModel({ id: 'o1' })
|
||||||
reasoningMock.mockReturnValue(true)
|
reasoningMock.mockReturnValue(true)
|
||||||
expect(isNotSupportTemperatureAndTopP(model)).toBe(true)
|
expect(isSupportTemperatureModel(model)).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns false for open weight models', () => {
|
it('returns true for open weight models', () => {
|
||||||
const openWeight = createModel({ id: 'gpt-oss-debug' })
|
const openWeight = createModel({ id: 'gpt-oss-debug' })
|
||||||
expect(isNotSupportTemperatureAndTopP(openWeight)).toBe(false)
|
expect(isSupportTemperatureModel(openWeight)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns true for chat-only models without reasoning', () => {
|
it('returns false for chat-only models', () => {
|
||||||
const chatOnly = createModel({ id: 'o1-preview' })
|
const chatOnly = createModel({ id: 'o1-preview' })
|
||||||
reasoningMock.mockReturnValue(false)
|
expect(isSupportTemperatureModel(chatOnly)).toBe(false)
|
||||||
expect(isNotSupportTemperatureAndTopP(chatOnly)).toBe(true)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns true for Qwen MT models', () => {
|
it('returns false for Qwen MT models', () => {
|
||||||
const qwenMt = createModel({ id: 'qwen-mt-large', provider: 'aliyun' })
|
const qwenMt = createModel({ id: 'qwen-mt-large', provider: 'aliyun' })
|
||||||
expect(isNotSupportTemperatureAndTopP(qwenMt)).toBe(true)
|
expect(isSupportTemperatureModel(qwenMt)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns false for null/undefined models', () => {
|
||||||
|
expect(isSupportTemperatureModel(null)).toBe(false)
|
||||||
|
expect(isSupportTemperatureModel(undefined)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns true for regular GPT models', () => {
|
||||||
|
const model = createModel({ id: 'gpt-4' })
|
||||||
|
expect(isSupportTemperatureModel(model)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('isSupportTopPModel', () => {
|
||||||
|
it('returns false for reasoning models (non-open weight)', () => {
|
||||||
|
const model = createModel({ id: 'o1' })
|
||||||
|
reasoningMock.mockReturnValue(true)
|
||||||
|
expect(isSupportTopPModel(model)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns true for open weight models', () => {
|
||||||
|
const openWeight = createModel({ id: 'gpt-oss-debug' })
|
||||||
|
expect(isSupportTopPModel(openWeight)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns false for chat-only models', () => {
|
||||||
|
const chatOnly = createModel({ id: 'o1-preview' })
|
||||||
|
expect(isSupportTopPModel(chatOnly)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns false for Qwen MT models', () => {
|
||||||
|
const qwenMt = createModel({ id: 'qwen-mt-large', provider: 'aliyun' })
|
||||||
|
expect(isSupportTopPModel(qwenMt)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns false for null/undefined models', () => {
|
||||||
|
expect(isSupportTopPModel(null)).toBe(false)
|
||||||
|
expect(isSupportTopPModel(undefined)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns true for regular GPT models', () => {
|
||||||
|
const model = createModel({ id: 'gpt-4' })
|
||||||
|
expect(isSupportTopPModel(model)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('isTemperatureTopPMutuallyExclusiveModel', () => {
|
||||||
|
it('returns true for Claude 4.5 reasoning models', () => {
|
||||||
|
const claude45Sonnet = createModel({ id: 'claude-sonnet-4.5-20250514' })
|
||||||
|
expect(isTemperatureTopPMutuallyExclusiveModel(claude45Sonnet)).toBe(true)
|
||||||
|
|
||||||
|
const claude45Opus = createModel({ id: 'claude-opus-4.5-20250514' })
|
||||||
|
expect(isTemperatureTopPMutuallyExclusiveModel(claude45Opus)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns false for Claude 4 models', () => {
|
||||||
|
const claude4Sonnet = createModel({ id: 'claude-sonnet-4-20250514' })
|
||||||
|
expect(isTemperatureTopPMutuallyExclusiveModel(claude4Sonnet)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns false for Claude 3.x models', () => {
|
||||||
|
const claude35Sonnet = createModel({ id: 'claude-3-5-sonnet-20241022' })
|
||||||
|
expect(isTemperatureTopPMutuallyExclusiveModel(claude35Sonnet)).toBe(false)
|
||||||
|
|
||||||
|
const claude3Opus = createModel({ id: 'claude-3-opus-20240229' })
|
||||||
|
expect(isTemperatureTopPMutuallyExclusiveModel(claude3Opus)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns false for other AI models', () => {
|
||||||
|
expect(isTemperatureTopPMutuallyExclusiveModel(createModel({ id: 'gpt-4o' }))).toBe(false)
|
||||||
|
expect(isTemperatureTopPMutuallyExclusiveModel(createModel({ id: 'o1' }))).toBe(false)
|
||||||
|
expect(isTemperatureTopPMutuallyExclusiveModel(createModel({ id: 'gemini-2.0-flash' }))).toBe(false)
|
||||||
|
expect(isTemperatureTopPMutuallyExclusiveModel(createModel({ id: 'qwen-max' }))).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns false for null/undefined models', () => {
|
||||||
|
expect(isTemperatureTopPMutuallyExclusiveModel(null)).toBe(false)
|
||||||
|
expect(isTemperatureTopPMutuallyExclusiveModel(undefined)).toBe(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -240,47 +240,35 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
|||||||
],
|
],
|
||||||
|
|
||||||
burncloud: [
|
burncloud: [
|
||||||
{ id: 'claude-3-7-sonnet-20250219-thinking', provider: 'burncloud', name: 'Claude 3.7 thinking', group: 'Claude' },
|
{ id: 'claude-opus-4-5-20251101', provider: 'burncloud', name: 'Claude 4.5 Opus', group: 'Claude 4.5' },
|
||||||
{ id: 'claude-3-7-sonnet-20250219', provider: 'burncloud', name: 'Claude 3.7 Sonnet', group: 'Claude 3.7' },
|
{ id: 'claude-sonnet-4-5-20250929', provider: 'burncloud', name: 'Claude 4.5 Sonnet', group: 'Claude 4.5' },
|
||||||
{ id: 'claude-3-5-sonnet-20241022', provider: 'burncloud', name: 'Claude 3.5 Sonnet', group: 'Claude 3.5' },
|
{ id: 'claude-haiku-4-5-20251001', provider: 'burncloud', name: 'Claude 4.5 Haiku', group: 'Claude 4.5' },
|
||||||
{ id: 'claude-3-5-haiku-20241022', provider: 'burncloud', name: 'Claude 3.5 Haiku', group: 'Claude 3.5' },
|
|
||||||
|
|
||||||
{ id: 'gpt-4.5-preview', provider: 'burncloud', name: 'gpt-4.5-preview', group: 'gpt-4.5' },
|
{ id: 'gpt-5', provider: 'burncloud', name: 'GPT 5', group: 'GPT 5' },
|
||||||
{ id: 'gpt-4o', provider: 'burncloud', name: 'GPT-4o', group: 'GPT 4o' },
|
{ id: 'gpt-5.1', provider: 'burncloud', name: 'GPT 5.1', group: 'GPT 5.1' },
|
||||||
{ id: 'gpt-4o-mini', provider: 'burncloud', name: 'GPT-4o-mini', group: 'GPT 4o' },
|
|
||||||
{ id: 'o3', provider: 'burncloud', name: 'GPT-o1-mini', group: 'o1' },
|
|
||||||
{ id: 'o3-mini', provider: 'burncloud', name: 'GPT-o1-preview', group: 'o1' },
|
|
||||||
{ id: 'o1-mini', provider: 'burncloud', name: 'GPT-o1-mini', group: 'o1' },
|
|
||||||
|
|
||||||
{ id: 'gemini-2.5-pro-preview-03-25', provider: 'burncloud', name: 'Gemini 2.5 Preview', group: 'Geminit 2.5' },
|
{ id: 'gemini-2.5-flash', provider: 'burncloud', name: 'Gemini 2.5 Flash', group: 'Gemini 2.5' },
|
||||||
{ id: 'gemini-2.5-pro-exp-03-25', provider: 'burncloud', name: 'Gemini 2.5 Pro Exp', group: 'Geminit 2.5' },
|
{ id: 'gemini-2.5-flash-image', provider: 'burncloud', name: 'Gemini 2.5 Flash Image', group: 'Gemini 2.5' },
|
||||||
{ id: 'gemini-2.0-flash-lite', provider: 'burncloud', name: 'Gemini 2.0 Flash Lite', group: 'Geminit 2.0' },
|
{ id: 'gemini-2.5-pro', provider: 'burncloud', name: 'Gemini 2.5 Pro', group: 'Gemini 2.5' },
|
||||||
{ id: 'gemini-2.0-flash-exp', provider: 'burncloud', name: 'Gemini 2.0 Flash Exp', group: 'Geminit 2.0' },
|
{ id: 'gemini-3-pro-preview', provider: 'burncloud', name: 'Gemini 3 Pro Preview', group: 'Gemini 3' },
|
||||||
{ id: 'gemini-2.0-flash', provider: 'burncloud', name: 'Gemini 2.0 Flash', group: 'Geminit 2.0' },
|
|
||||||
|
|
||||||
{ id: 'deepseek-r1', name: 'DeepSeek-R1', provider: 'burncloud', group: 'deepseek-ai' },
|
{ id: 'deepseek-reasoner', name: 'DeepSeek Reasoner', provider: 'burncloud', group: 'deepseek-ai' },
|
||||||
{ id: 'deepseek-v3', name: 'DeepSeek-V3', provider: 'burncloud', group: 'deepseek-ai' }
|
{ id: 'deepseek-chat', name: 'DeepSeek Chat', provider: 'burncloud', group: 'deepseek-ai' }
|
||||||
],
|
],
|
||||||
ovms: [],
|
ovms: [],
|
||||||
ollama: [],
|
ollama: [],
|
||||||
lmstudio: [],
|
lmstudio: [],
|
||||||
silicon: [
|
silicon: [
|
||||||
{
|
{
|
||||||
id: 'deepseek-ai/DeepSeek-R1',
|
id: 'deepseek-ai/DeepSeek-V3.2',
|
||||||
name: 'deepseek-ai/DeepSeek-R1',
|
name: 'deepseek-ai/DeepSeek-V3.2',
|
||||||
provider: 'silicon',
|
provider: 'silicon',
|
||||||
group: 'deepseek-ai'
|
group: 'deepseek-ai'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'deepseek-ai/DeepSeek-V3',
|
id: 'Qwen/Qwen3-8B',
|
||||||
name: 'deepseek-ai/DeepSeek-V3',
|
name: 'Qwen/Qwen3-8B',
|
||||||
provider: 'silicon',
|
provider: 'silicon',
|
||||||
group: 'deepseek-ai'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Qwen/Qwen2.5-7B-Instruct',
|
|
||||||
provider: 'silicon',
|
|
||||||
name: 'Qwen2.5-7B-Instruct',
|
|
||||||
group: 'Qwen'
|
group: 'Qwen'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -288,79 +276,31 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
|||||||
name: 'BAAI/bge-m3',
|
name: 'BAAI/bge-m3',
|
||||||
provider: 'silicon',
|
provider: 'silicon',
|
||||||
group: 'BAAI'
|
group: 'BAAI'
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'Qwen/Qwen3-8B',
|
|
||||||
name: 'Qwen/Qwen3-8B',
|
|
||||||
provider: 'silicon',
|
|
||||||
group: 'Qwen'
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
ppio: [
|
ppio: [
|
||||||
{
|
{
|
||||||
id: 'deepseek/deepseek-r1-0528',
|
id: 'deepseek/deepseek-v3.2',
|
||||||
provider: 'ppio',
|
provider: 'ppio',
|
||||||
name: 'DeepSeek R1-0528',
|
name: 'DeepSeek V3.2',
|
||||||
group: 'deepseek'
|
group: 'deepseek'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'deepseek/deepseek-v3-0324',
|
id: 'minimax/minimax-m2',
|
||||||
provider: 'ppio',
|
provider: 'ppio',
|
||||||
name: 'DeepSeek V3-0324',
|
name: 'MiniMax M2',
|
||||||
group: 'deepseek'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'deepseek/deepseek-r1-turbo',
|
|
||||||
provider: 'ppio',
|
|
||||||
name: 'DeepSeek R1 Turbo',
|
|
||||||
group: 'deepseek'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'deepseek/deepseek-v3-turbo',
|
|
||||||
provider: 'ppio',
|
|
||||||
name: 'DeepSeek V3 Turbo',
|
|
||||||
group: 'deepseek'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'deepseek/deepseek-r1/community',
|
|
||||||
name: 'DeepSeek: DeepSeek R1 (Community)',
|
|
||||||
provider: 'ppio',
|
|
||||||
group: 'deepseek'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'deepseek/deepseek-v3/community',
|
|
||||||
name: 'DeepSeek: DeepSeek V3 (Community)',
|
|
||||||
provider: 'ppio',
|
|
||||||
group: 'deepseek'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'minimaxai/minimax-m1-80k',
|
|
||||||
provider: 'ppio',
|
|
||||||
name: 'MiniMax M1-80K',
|
|
||||||
group: 'minimaxai'
|
group: 'minimaxai'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'qwen/qwen3-235b-a22b-fp8',
|
id: 'qwen/qwen3-235b-a22b-instruct-2507',
|
||||||
provider: 'ppio',
|
provider: 'ppio',
|
||||||
name: 'Qwen3 235B',
|
name: 'Qwen3-235b-a22b-instruct-2507',
|
||||||
group: 'qwen'
|
group: 'qwen'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'qwen/qwen3-32b-fp8',
|
id: 'qwen/qwen3-vl-235b-a22b-instruct',
|
||||||
provider: 'ppio',
|
provider: 'ppio',
|
||||||
name: 'Qwen3 32B',
|
name: 'Qwen3-vl-235b-a22b-instruct',
|
||||||
group: 'qwen'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'qwen/qwen3-30b-a3b-fp8',
|
|
||||||
provider: 'ppio',
|
|
||||||
name: 'Qwen3 30B',
|
|
||||||
group: 'qwen'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'qwen/qwen2.5-vl-72b-instruct',
|
|
||||||
provider: 'ppio',
|
|
||||||
name: 'Qwen2.5 VL 72B',
|
|
||||||
group: 'qwen'
|
group: 'qwen'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -378,11 +318,13 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
|||||||
],
|
],
|
||||||
alayanew: [],
|
alayanew: [],
|
||||||
openai: [
|
openai: [
|
||||||
{ id: 'gpt-4.5-preview', provider: 'openai', name: ' gpt-4.5-preview', group: 'gpt-4.5' },
|
{ id: 'gpt-5.1', provider: 'openai', name: ' GPT 5.1', group: 'GPT 5.1' },
|
||||||
{ id: 'gpt-4o', provider: 'openai', name: ' GPT-4o', group: 'GPT 4o' },
|
{ id: 'gpt-5', provider: 'openai', name: ' GPT 5', group: 'GPT 5' },
|
||||||
{ id: 'gpt-4o-mini', provider: 'openai', name: ' GPT-4o-mini', group: 'GPT 4o' },
|
{ id: 'gpt-5-mini', provider: 'openai', name: ' GPT 5 Mini', group: 'GPT 5' },
|
||||||
{ id: 'o1-mini', provider: 'openai', name: ' o1-mini', group: 'o1' },
|
{ id: 'gpt-5-nano', provider: 'openai', name: ' GPT 5 Nano', group: 'GPT 5' },
|
||||||
{ id: 'o1-preview', provider: 'openai', name: ' o1-preview', group: 'o1' }
|
{ id: 'gpt-5-pro', provider: 'openai', name: ' GPT 5 Pro', group: 'GPT 5' },
|
||||||
|
{ id: 'gpt-5-chat', provider: 'openai', name: ' GPT 5 Chat', group: 'GPT 5' },
|
||||||
|
{ id: 'gpt-image-1', provider: 'openai', name: ' GPT Image 1', group: 'GPT Image' }
|
||||||
],
|
],
|
||||||
'azure-openai': [
|
'azure-openai': [
|
||||||
{
|
{
|
||||||
@ -400,96 +342,54 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
|||||||
],
|
],
|
||||||
gemini: [
|
gemini: [
|
||||||
{
|
{
|
||||||
id: 'gemini-1.5-flash',
|
id: 'gemini-2.5-flash',
|
||||||
provider: 'gemini',
|
provider: 'gemini',
|
||||||
name: 'Gemini 1.5 Flash',
|
name: 'Gemini 2.5 Flash',
|
||||||
group: 'Gemini 1.5'
|
group: 'Gemini 2.5'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'gemini-1.5-flash-8b',
|
id: 'gemini-2.5-pro',
|
||||||
provider: 'gemini',
|
provider: 'gemini',
|
||||||
name: 'Gemini 1.5 Flash (8B)',
|
name: 'Gemini 2.5 Pro',
|
||||||
group: 'Gemini 1.5'
|
group: 'Gemini 2.5'
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'gemini-1.5-pro',
|
|
||||||
name: 'Gemini 1.5 Pro',
|
|
||||||
provider: 'gemini',
|
|
||||||
group: 'Gemini 1.5'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'gemini-2.0-flash',
|
|
||||||
provider: 'gemini',
|
|
||||||
name: 'Gemini 2.0 Flash',
|
|
||||||
group: 'Gemini 2.0'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'gemini-2.5-flash-image-preview',
|
id: 'gemini-2.5-flash-image-preview',
|
||||||
provider: 'gemini',
|
provider: 'gemini',
|
||||||
name: 'Gemini 2.5 Flash Image',
|
name: 'Gemini 2.5 Flash Image',
|
||||||
group: 'Gemini 2.5'
|
group: 'Gemini 2.5'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gemini-3-pro-image-preview',
|
||||||
|
provider: 'gemini',
|
||||||
|
name: 'Gemini 3 Pro Image Privew',
|
||||||
|
group: 'Gemini 3'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gemini-3-pro-preview',
|
||||||
|
provider: 'gemini',
|
||||||
|
name: 'Gemini 3 Pro Preview',
|
||||||
|
group: 'Gemini 3'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
anthropic: [
|
anthropic: [
|
||||||
{
|
{
|
||||||
id: 'claude-haiku-4-5-20251001',
|
id: 'claude-sonnet-4-5',
|
||||||
provider: 'anthropic',
|
|
||||||
name: 'Claude Haiku 4.5',
|
|
||||||
group: 'Claude 4.5'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'claude-sonnet-4-5-20250929',
|
|
||||||
provider: 'anthropic',
|
provider: 'anthropic',
|
||||||
name: 'Claude Sonnet 4.5',
|
name: 'Claude Sonnet 4.5',
|
||||||
group: 'Claude 4.5'
|
group: 'Claude 4.5'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'claude-sonnet-4-20250514',
|
id: 'claude-haiku-4-5',
|
||||||
provider: 'anthropic',
|
provider: 'anthropic',
|
||||||
name: 'Claude Sonnet 4',
|
name: 'Claude Haiku 4.5',
|
||||||
group: 'Claude 4'
|
group: 'Claude 4.5'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'claude-opus-4-20250514',
|
id: 'claude-opus-4-5',
|
||||||
provider: 'anthropic',
|
provider: 'anthropic',
|
||||||
name: 'Claude Opus 4',
|
name: 'Claude Opus 4.5',
|
||||||
group: 'Claude 4'
|
group: 'Claude 4.5'
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'claude-3-7-sonnet-20250219',
|
|
||||||
provider: 'anthropic',
|
|
||||||
name: 'Claude 3.7 Sonnet',
|
|
||||||
group: 'Claude 3.7'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'claude-3-5-sonnet-20241022',
|
|
||||||
provider: 'anthropic',
|
|
||||||
name: 'Claude 3.5 Sonnet',
|
|
||||||
group: 'Claude 3.5'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'claude-3-5-haiku-20241022',
|
|
||||||
provider: 'anthropic',
|
|
||||||
name: 'Claude 3.5 Haiku',
|
|
||||||
group: 'Claude 3.5'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'claude-3-5-sonnet-20240620',
|
|
||||||
provider: 'anthropic',
|
|
||||||
name: 'Claude 3.5 Sonnet (Legacy)',
|
|
||||||
group: 'Claude 3.5'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'claude-3-opus-20240229',
|
|
||||||
provider: 'anthropic',
|
|
||||||
name: 'Claude 3 Opus',
|
|
||||||
group: 'Claude 3'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'claude-3-haiku-20240307',
|
|
||||||
provider: 'anthropic',
|
|
||||||
name: 'Claude 3 Haiku',
|
|
||||||
group: 'Claude 3'
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
deepseek: [
|
deepseek: [
|
||||||
@ -1073,18 +973,6 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
|||||||
provider: 'grok',
|
provider: 'grok',
|
||||||
name: 'Grok 3 Mini Fast',
|
name: 'Grok 3 Mini Fast',
|
||||||
group: 'Grok'
|
group: 'Grok'
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'grok-2-vision-1212',
|
|
||||||
provider: 'grok',
|
|
||||||
name: 'Grok 2 Vision 1212',
|
|
||||||
group: 'Grok'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'grok-2-1212',
|
|
||||||
provider: 'grok',
|
|
||||||
name: 'Grok 2 1212',
|
|
||||||
group: 'Grok'
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
mistral: [
|
mistral: [
|
||||||
@ -1808,34 +1696,58 @@ export const SYSTEM_MODELS: Record<SystemProviderId | 'defaultModel', Model[]> =
|
|||||||
],
|
],
|
||||||
aionly: [
|
aionly: [
|
||||||
{
|
{
|
||||||
id: 'claude-opus-4.1',
|
id: 'claude-opus-4-5-20251101',
|
||||||
name: 'claude-opus-4.1',
|
name: 'Claude Opus 4.5',
|
||||||
provider: 'aionly',
|
provider: 'aionly',
|
||||||
group: 'claude'
|
group: 'Anthropic'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'claude-sonnet4',
|
id: 'claude-haiku-4-5-20251001',
|
||||||
name: 'claude-sonnet4',
|
name: 'Claude Haiku 4.5',
|
||||||
provider: 'aionly',
|
provider: 'aionly',
|
||||||
group: 'claude'
|
group: 'Anthropic'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'claude-3.5-sonnet-v2',
|
id: 'claude-sonnet-4-5-20250929',
|
||||||
name: 'claude-3.5-sonnet-v2',
|
name: 'Claude Sonnet 4.5',
|
||||||
provider: 'aionly',
|
provider: 'aionly',
|
||||||
group: 'claude'
|
group: 'Anthropic'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'gpt-4.1',
|
id: 'gpt-5.1',
|
||||||
name: 'gpt-4.1',
|
name: 'GPT-5.1',
|
||||||
provider: 'aionly',
|
provider: 'aionly',
|
||||||
group: 'gpt'
|
group: 'OpenAI'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gpt-5.1-chat',
|
||||||
|
name: 'GPT-5.1 Chat',
|
||||||
|
provider: 'aionly',
|
||||||
|
group: 'OpenAI'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gpt-5-pro',
|
||||||
|
name: 'GPT 5 Pro',
|
||||||
|
provider: 'aionly',
|
||||||
|
group: 'OpenAI'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gemini-3-pro-preview',
|
||||||
|
name: 'Gemini 3 Pro Preview',
|
||||||
|
provider: 'aionly',
|
||||||
|
group: 'Google'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gemini-2.5-pro',
|
||||||
|
name: 'Gemini 2.5 Pro',
|
||||||
|
provider: 'aionly',
|
||||||
|
group: 'Google'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'gemini-2.5-flash',
|
id: 'gemini-2.5-flash',
|
||||||
name: 'gemini-2.5-flash',
|
name: 'Gemini 2.5 Flash',
|
||||||
provider: 'aionly',
|
provider: 'aionly',
|
||||||
group: 'gemini'
|
group: 'Google'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
longcat: [
|
longcat: [
|
||||||
|
|||||||
@ -277,6 +277,10 @@ export const GEMINI_THINKING_MODEL_REGEX =
|
|||||||
export const isSupportedThinkingTokenGeminiModel = (model: Model): boolean => {
|
export const isSupportedThinkingTokenGeminiModel = (model: Model): boolean => {
|
||||||
const modelId = getLowerBaseModelName(model.id, '/')
|
const modelId = getLowerBaseModelName(model.id, '/')
|
||||||
if (GEMINI_THINKING_MODEL_REGEX.test(modelId)) {
|
if (GEMINI_THINKING_MODEL_REGEX.test(modelId)) {
|
||||||
|
// ref: https://docs.cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/3-pro-image
|
||||||
|
if (modelId.includes('gemini-3-pro-image')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (modelId.includes('image') || modelId.includes('tts')) {
|
if (modelId.includes('image') || modelId.includes('tts')) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
isSupportVerbosityModel
|
isSupportVerbosityModel
|
||||||
} from './openai'
|
} from './openai'
|
||||||
import { isQwenMTModel } from './qwen'
|
import { isQwenMTModel } from './qwen'
|
||||||
|
import { isClaude45ReasoningModel } from './reasoning'
|
||||||
import { isFunctionCallingModel } from './tooluse'
|
import { isFunctionCallingModel } from './tooluse'
|
||||||
import { isGenerateImageModel, isTextToImageModel, isVisionModel } from './vision'
|
import { isGenerateImageModel, isTextToImageModel, isVisionModel } from './vision'
|
||||||
export const NOT_SUPPORTED_REGEX = /(?:^tts|whisper|speech)/i
|
export const NOT_SUPPORTED_REGEX = /(?:^tts|whisper|speech)/i
|
||||||
@ -44,20 +45,71 @@ export function isSupportedModel(model: OpenAI.Models.Model): boolean {
|
|||||||
return !NOT_SUPPORTED_REGEX.test(modelId)
|
return !NOT_SUPPORTED_REGEX.test(modelId)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNotSupportTemperatureAndTopP(model: Model): boolean {
|
/**
|
||||||
|
* Check if the model supports temperature parameter
|
||||||
|
* @param model - The model to check
|
||||||
|
* @returns true if the model supports temperature parameter
|
||||||
|
*/
|
||||||
|
export function isSupportTemperatureModel(model: Model | undefined | null): boolean {
|
||||||
if (!model) {
|
if (!model) {
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
// OpenAI reasoning models (except open weight) don't support temperature
|
||||||
(isOpenAIReasoningModel(model) && !isOpenAIOpenWeightModel(model)) ||
|
if (isOpenAIReasoningModel(model) && !isOpenAIOpenWeightModel(model)) {
|
||||||
isOpenAIChatCompletionOnlyModel(model) ||
|
return false
|
||||||
isQwenMTModel(model)
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
// OpenAI chat completion only models don't support temperature
|
||||||
|
if (isOpenAIChatCompletionOnlyModel(model)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Qwen MT models don't support temperature
|
||||||
|
if (isQwenMTModel(model)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the model supports top_p parameter
|
||||||
|
* @param model - The model to check
|
||||||
|
* @returns true if the model supports top_p parameter
|
||||||
|
*/
|
||||||
|
export function isSupportTopPModel(model: Model | undefined | null): boolean {
|
||||||
|
if (!model) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenAI reasoning models (except open weight) don't support top_p
|
||||||
|
if (isOpenAIReasoningModel(model) && !isOpenAIOpenWeightModel(model)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenAI chat completion only models don't support top_p
|
||||||
|
if (isOpenAIChatCompletionOnlyModel(model)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Qwen MT models don't support top_p
|
||||||
|
if (isQwenMTModel(model)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the model enforces mutual exclusivity between temperature and top_p parameters.
|
||||||
|
* Currently only Claude 4.5 reasoning models require this constraint.
|
||||||
|
* @param model - The model to check
|
||||||
|
* @returns true if temperature and top_p are mutually exclusive for this model
|
||||||
|
*/
|
||||||
|
export function isTemperatureTopPMutuallyExclusiveModel(model: Model | undefined | null): boolean {
|
||||||
|
if (!model) return false
|
||||||
|
return isClaude45ReasoningModel(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isGemmaModel(model?: Model): boolean {
|
export function isGemmaModel(model?: Model): boolean {
|
||||||
|
|||||||
@ -306,7 +306,7 @@ export const SEARCH_SUMMARY_PROMPT_KNOWLEDGE_ONLY = `
|
|||||||
**Use user's language to rephrase the question.**
|
**Use user's language to rephrase the question.**
|
||||||
Follow these guidelines:
|
Follow these guidelines:
|
||||||
1. If the question is a simple writing task, greeting (e.g., Hi, Hello, How are you), or does not require searching for information (unless the greeting contains a follow-up question), return 'not_needed' in the 'question' XML block. This indicates that no search is required.
|
1. If the question is a simple writing task, greeting (e.g., Hi, Hello, How are you), or does not require searching for information (unless the greeting contains a follow-up question), return 'not_needed' in the 'question' XML block. This indicates that no search is required.
|
||||||
2. For knowledge, You need rewrite user query into 'rewrite' XML block with one alternative version while preserving the original intent and meaning. Also include the original question in the 'question' block.
|
2. For knowledge, You need rewrite user query into 'rewrite' XML block with one alternative version while preserving the original intent and meaning. Also include the rephrased or decomposed question(s) in the 'question' block.
|
||||||
3. Always return the rephrased question inside the 'question' XML block.
|
3. Always return the rephrased question inside the 'question' XML block.
|
||||||
4. Always wrap the rephrased question in the appropriate XML blocks: use <knowledge></knowledge> for queries that can be answered from a pre-existing knowledge base. Ensure that the rephrased question is always contained within a <question></question> block inside the wrapper.
|
4. Always wrap the rephrased question in the appropriate XML blocks: use <knowledge></knowledge> for queries that can be answered from a pre-existing knowledge base. Ensure that the rephrased question is always contained within a <question></question> block inside the wrapper.
|
||||||
5. *use knowledge to rephrase the question*
|
5. *use knowledge to rephrase the question*
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
"failed": "Failed to add a agent",
|
"failed": "Failed to add a agent",
|
||||||
"invalid_agent": "Invalid Agent"
|
"invalid_agent": "Invalid Agent"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"tooltip": "Currently, only models that support Anthropic endpoints are available for the Agent feature."
|
||||||
|
},
|
||||||
"title": "Add Agent",
|
"title": "Add Agent",
|
||||||
"type": {
|
"type": {
|
||||||
"placeholder": "Select an agent type"
|
"placeholder": "Select an agent type"
|
||||||
@ -2514,7 +2517,8 @@
|
|||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"copy": {
|
"copy": {
|
||||||
"image": "Copy as image"
|
"image": "Copy as image",
|
||||||
|
"src": "Copy Image Source"
|
||||||
},
|
},
|
||||||
"dialog": "Open Dialog",
|
"dialog": "Open Dialog",
|
||||||
"label": "Preview",
|
"label": "Preview",
|
||||||
@ -3908,6 +3912,7 @@
|
|||||||
"jsonSaveError": "Failed to save JSON configuration.",
|
"jsonSaveError": "Failed to save JSON configuration.",
|
||||||
"jsonSaveSuccess": "JSON configuration has been saved.",
|
"jsonSaveSuccess": "JSON configuration has been saved.",
|
||||||
"logoUrl": "Logo URL",
|
"logoUrl": "Logo URL",
|
||||||
|
"logs": "Logs",
|
||||||
"longRunning": "Long Running Mode",
|
"longRunning": "Long Running Mode",
|
||||||
"longRunningTooltip": "When enabled, the server supports long-running tasks. When receiving progress notifications, the timeout will be reset and the maximum execution time will be extended to 10 minutes.",
|
"longRunningTooltip": "When enabled, the server supports long-running tasks. When receiving progress notifications, the timeout will be reset and the maximum execution time will be extended to 10 minutes.",
|
||||||
"marketplaces": "Marketplaces",
|
"marketplaces": "Marketplaces",
|
||||||
@ -3927,6 +3932,7 @@
|
|||||||
"name": "Name",
|
"name": "Name",
|
||||||
"newServer": "MCP Server",
|
"newServer": "MCP Server",
|
||||||
"noDescriptionAvailable": "No description available",
|
"noDescriptionAvailable": "No description available",
|
||||||
|
"noLogs": "No logs yet",
|
||||||
"noServers": "No servers configured",
|
"noServers": "No servers configured",
|
||||||
"not_support": "Model not supported",
|
"not_support": "Model not supported",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
"failed": "添加 Agent 失败",
|
"failed": "添加 Agent 失败",
|
||||||
"invalid_agent": "无效的 Agent"
|
"invalid_agent": "无效的 Agent"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"tooltip": "目前,只有支持 Anthropic 端点的模型可用于 Agent 功能。"
|
||||||
|
},
|
||||||
"title": "添加 Agent",
|
"title": "添加 Agent",
|
||||||
"type": {
|
"type": {
|
||||||
"placeholder": "选择 Agent 类型"
|
"placeholder": "选择 Agent 类型"
|
||||||
@ -2514,7 +2517,8 @@
|
|||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"copy": {
|
"copy": {
|
||||||
"image": "复制为图片"
|
"image": "复制为图片",
|
||||||
|
"src": "复制图片源"
|
||||||
},
|
},
|
||||||
"dialog": "打开预览窗口",
|
"dialog": "打开预览窗口",
|
||||||
"label": "预览",
|
"label": "预览",
|
||||||
@ -3908,6 +3912,7 @@
|
|||||||
"jsonSaveError": "保存 JSON 配置失败",
|
"jsonSaveError": "保存 JSON 配置失败",
|
||||||
"jsonSaveSuccess": "JSON 配置已保存",
|
"jsonSaveSuccess": "JSON 配置已保存",
|
||||||
"logoUrl": "标志网址",
|
"logoUrl": "标志网址",
|
||||||
|
"logs": "日志",
|
||||||
"longRunning": "长时间运行模式",
|
"longRunning": "长时间运行模式",
|
||||||
"longRunningTooltip": "启用后,服务器支持长时间任务,接收到进度通知时会重置超时计时器,并延长最大超时时间至10分钟",
|
"longRunningTooltip": "启用后,服务器支持长时间任务,接收到进度通知时会重置超时计时器,并延长最大超时时间至10分钟",
|
||||||
"marketplaces": "市场",
|
"marketplaces": "市场",
|
||||||
@ -3927,6 +3932,7 @@
|
|||||||
"name": "名称",
|
"name": "名称",
|
||||||
"newServer": "MCP 服务器",
|
"newServer": "MCP 服务器",
|
||||||
"noDescriptionAvailable": "暂无描述",
|
"noDescriptionAvailable": "暂无描述",
|
||||||
|
"noLogs": "暂无日志",
|
||||||
"noServers": "未配置服务器",
|
"noServers": "未配置服务器",
|
||||||
"not_support": "模型不支持",
|
"not_support": "模型不支持",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
"failed": "無法新增代理人",
|
"failed": "無法新增代理人",
|
||||||
"invalid_agent": "無效的 Agent"
|
"invalid_agent": "無效的 Agent"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"tooltip": "目前,僅支援 Anthropic 端點的模型可供代理功能使用。"
|
||||||
|
},
|
||||||
"title": "新增代理",
|
"title": "新增代理",
|
||||||
"type": {
|
"type": {
|
||||||
"placeholder": "選擇 Agent 類型"
|
"placeholder": "選擇 Agent 類型"
|
||||||
@ -2220,8 +2223,8 @@
|
|||||||
"untitled_folder": "新資料夾",
|
"untitled_folder": "新資料夾",
|
||||||
"untitled_note": "無標題筆記",
|
"untitled_note": "無標題筆記",
|
||||||
"upload_failed": "筆記上傳失敗",
|
"upload_failed": "筆記上傳失敗",
|
||||||
"upload_files": "[to be translated]:Upload Files",
|
"upload_files": "上傳檔案",
|
||||||
"upload_folder": "[to be translated]:Upload Folder",
|
"upload_folder": "上傳資料夾",
|
||||||
"upload_success": "筆記上傳成功",
|
"upload_success": "筆記上傳成功",
|
||||||
"uploading_files": "正在上傳 {{count}} 個檔案..."
|
"uploading_files": "正在上傳 {{count}} 個檔案..."
|
||||||
},
|
},
|
||||||
@ -2514,7 +2517,8 @@
|
|||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"copy": {
|
"copy": {
|
||||||
"image": "複製為圖片"
|
"image": "複製為圖片",
|
||||||
|
"src": "複製圖片來源"
|
||||||
},
|
},
|
||||||
"dialog": "開啟預覽窗口",
|
"dialog": "開啟預覽窗口",
|
||||||
"label": "預覽",
|
"label": "預覽",
|
||||||
@ -3908,6 +3912,7 @@
|
|||||||
"jsonSaveError": "保存 JSON 配置失敗",
|
"jsonSaveError": "保存 JSON 配置失敗",
|
||||||
"jsonSaveSuccess": "JSON 配置已儲存",
|
"jsonSaveSuccess": "JSON 配置已儲存",
|
||||||
"logoUrl": "標誌網址",
|
"logoUrl": "標誌網址",
|
||||||
|
"logs": "日誌",
|
||||||
"longRunning": "長時間運行模式",
|
"longRunning": "長時間運行模式",
|
||||||
"longRunningTooltip": "啟用後,伺服器支援長時間任務,接收到進度通知時會重置超時計時器,並延長最大超時時間至10分鐘",
|
"longRunningTooltip": "啟用後,伺服器支援長時間任務,接收到進度通知時會重置超時計時器,並延長最大超時時間至10分鐘",
|
||||||
"marketplaces": "市場",
|
"marketplaces": "市場",
|
||||||
@ -3927,6 +3932,7 @@
|
|||||||
"name": "名稱",
|
"name": "名稱",
|
||||||
"newServer": "MCP 伺服器",
|
"newServer": "MCP 伺服器",
|
||||||
"noDescriptionAvailable": "描述不存在",
|
"noDescriptionAvailable": "描述不存在",
|
||||||
|
"noLogs": "暫無日誌",
|
||||||
"noServers": "未設定伺服器",
|
"noServers": "未設定伺服器",
|
||||||
"not_support": "不支援此模型",
|
"not_support": "不支援此模型",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
"failed": "Agent hinzufügen fehlgeschlagen",
|
"failed": "Agent hinzufügen fehlgeschlagen",
|
||||||
"invalid_agent": "Ungültiger Agent"
|
"invalid_agent": "Ungültiger Agent"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"tooltip": "Derzeit sind für die Agent-Funktion nur Modelle verfügbar, die Anthropic-Endpunkte unterstützen."
|
||||||
|
},
|
||||||
"title": "Agent hinzufügen",
|
"title": "Agent hinzufügen",
|
||||||
"type": {
|
"type": {
|
||||||
"placeholder": "Agent-Typ auswählen"
|
"placeholder": "Agent-Typ auswählen"
|
||||||
@ -2220,10 +2223,10 @@
|
|||||||
"untitled_folder": "Neuer Ordner",
|
"untitled_folder": "Neuer Ordner",
|
||||||
"untitled_note": "Unbenannte Notiz",
|
"untitled_note": "Unbenannte Notiz",
|
||||||
"upload_failed": "Notizen-Upload fehlgeschlagen",
|
"upload_failed": "Notizen-Upload fehlgeschlagen",
|
||||||
"upload_files": "[to be translated]:Upload Files",
|
"upload_files": "Dateien hochladen",
|
||||||
"upload_folder": "[to be translated]:Upload Folder",
|
"upload_folder": "Ordner hochladen",
|
||||||
"upload_success": "Notizen erfolgreich hochgeladen",
|
"upload_success": "Notizen erfolgreich hochgeladen",
|
||||||
"uploading_files": "[to be translated]:Uploading {{count}} files..."
|
"uploading_files": "Lade {{count}} Dateien hoch..."
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"assistant": "Assistenten-Antwort",
|
"assistant": "Assistenten-Antwort",
|
||||||
@ -2514,7 +2517,8 @@
|
|||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"copy": {
|
"copy": {
|
||||||
"image": "Als Bild kopieren"
|
"image": "Als Bild kopieren",
|
||||||
|
"src": "Bildquelle kopieren"
|
||||||
},
|
},
|
||||||
"dialog": "Vorschaufenster öffnen",
|
"dialog": "Vorschaufenster öffnen",
|
||||||
"label": "Vorschau",
|
"label": "Vorschau",
|
||||||
@ -3908,6 +3912,7 @@
|
|||||||
"jsonSaveError": "JSON-Konfiguration speichern fehlgeschlagen",
|
"jsonSaveError": "JSON-Konfiguration speichern fehlgeschlagen",
|
||||||
"jsonSaveSuccess": "JSON-Konfiguration erfolgreich gespeichert",
|
"jsonSaveSuccess": "JSON-Konfiguration erfolgreich gespeichert",
|
||||||
"logoUrl": "Logo-URL",
|
"logoUrl": "Logo-URL",
|
||||||
|
"logs": "Protokolle",
|
||||||
"longRunning": "Lang laufender Modus",
|
"longRunning": "Lang laufender Modus",
|
||||||
"longRunningTooltip": "Nach Aktivierung unterstützt der Server lange Aufgaben. Wenn ein Fortschrittsbenachrichtigung empfangen wird, wird der Timeout-Timer zurückgesetzt und die maximale Timeout-Zeit auf 10 Minuten verlängert",
|
"longRunningTooltip": "Nach Aktivierung unterstützt der Server lange Aufgaben. Wenn ein Fortschrittsbenachrichtigung empfangen wird, wird der Timeout-Timer zurückgesetzt und die maximale Timeout-Zeit auf 10 Minuten verlängert",
|
||||||
"marketplaces": "Marktplätze",
|
"marketplaces": "Marktplätze",
|
||||||
@ -3927,6 +3932,7 @@
|
|||||||
"name": "Name",
|
"name": "Name",
|
||||||
"newServer": "MCP-Server",
|
"newServer": "MCP-Server",
|
||||||
"noDescriptionAvailable": "Keine Beschreibung",
|
"noDescriptionAvailable": "Keine Beschreibung",
|
||||||
|
"noLogs": "Noch keine Protokolle",
|
||||||
"noServers": "Server nicht konfiguriert",
|
"noServers": "Server nicht konfiguriert",
|
||||||
"not_support": "Modell nicht unterstützt",
|
"not_support": "Modell nicht unterstützt",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
"failed": "Αποτυχία προσθήκης πράκτορα",
|
"failed": "Αποτυχία προσθήκης πράκτορα",
|
||||||
"invalid_agent": "Μη έγκυρος Agent"
|
"invalid_agent": "Μη έγκυρος Agent"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"tooltip": "Προς το παρόν, μόνο μοντέλα που υποστηρίζουν τελικά σημεία Anthropic είναι διαθέσιμα για τη λειτουργία Agent."
|
||||||
|
},
|
||||||
"title": "Προσθήκη Agent",
|
"title": "Προσθήκη Agent",
|
||||||
"type": {
|
"type": {
|
||||||
"placeholder": "Επιλέξτε τύπο Agent"
|
"placeholder": "Επιλέξτε τύπο Agent"
|
||||||
@ -2220,10 +2223,10 @@
|
|||||||
"untitled_folder": "Νέος φάκελος",
|
"untitled_folder": "Νέος φάκελος",
|
||||||
"untitled_note": "σημείωση χωρίς τίτλο",
|
"untitled_note": "σημείωση χωρίς τίτλο",
|
||||||
"upload_failed": "Η σημείωση δεν ανέβηκε",
|
"upload_failed": "Η σημείωση δεν ανέβηκε",
|
||||||
"upload_files": "[to be translated]:Upload Files",
|
"upload_files": "Ανέβασμα Αρχείων",
|
||||||
"upload_folder": "[to be translated]:Upload Folder",
|
"upload_folder": "Ανέβασμα Φακέλου",
|
||||||
"upload_success": "Οι σημειώσεις μεταφορτώθηκαν με επιτυχία",
|
"upload_success": "Οι σημειώσεις μεταφορτώθηκαν με επιτυχία",
|
||||||
"uploading_files": "[to be translated]:Uploading {{count}} files..."
|
"uploading_files": "Ανεβάζονται {{count}} αρχεία..."
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"assistant": "Απάντηση Βοηθού",
|
"assistant": "Απάντηση Βοηθού",
|
||||||
@ -2514,7 +2517,8 @@
|
|||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"copy": {
|
"copy": {
|
||||||
"image": "Αντιγραφή ως εικόνα"
|
"image": "Αντιγραφή ως εικόνα",
|
||||||
|
"src": "Αντιγραφή πηγής εικόνας"
|
||||||
},
|
},
|
||||||
"dialog": "Άνοιγμα παραθύρου προεπισκόπησης",
|
"dialog": "Άνοιγμα παραθύρου προεπισκόπησης",
|
||||||
"label": "Προεπισκόπηση",
|
"label": "Προεπισκόπηση",
|
||||||
@ -3908,6 +3912,7 @@
|
|||||||
"jsonSaveError": "Αποτυχία αποθήκευσης της διαμορφωτικής ρύθμισης JSON",
|
"jsonSaveError": "Αποτυχία αποθήκευσης της διαμορφωτικής ρύθμισης JSON",
|
||||||
"jsonSaveSuccess": "Η διαμορφωτική ρύθμιση JSON αποθηκεύτηκε επιτυχώς",
|
"jsonSaveSuccess": "Η διαμορφωτική ρύθμιση JSON αποθηκεύτηκε επιτυχώς",
|
||||||
"logoUrl": "URL Λογότυπου",
|
"logoUrl": "URL Λογότυπου",
|
||||||
|
"logs": "Αρχεία καταγραφής",
|
||||||
"longRunning": "Μακροχρόνια λειτουργία",
|
"longRunning": "Μακροχρόνια λειτουργία",
|
||||||
"longRunningTooltip": "Όταν ενεργοποιηθεί, ο διακομιστής υποστηρίζει μακροχρόνιες εργασίες, επαναφέρει το χρονικό όριο μετά από λήψη ειδοποίησης προόδου και επεκτείνει το μέγιστο χρονικό όριο σε 10 λεπτά.",
|
"longRunningTooltip": "Όταν ενεργοποιηθεί, ο διακομιστής υποστηρίζει μακροχρόνιες εργασίες, επαναφέρει το χρονικό όριο μετά από λήψη ειδοποίησης προόδου και επεκτείνει το μέγιστο χρονικό όριο σε 10 λεπτά.",
|
||||||
"marketplaces": "Αγορές",
|
"marketplaces": "Αγορές",
|
||||||
@ -3927,6 +3932,7 @@
|
|||||||
"name": "Όνομα",
|
"name": "Όνομα",
|
||||||
"newServer": "Διακομιστής MCP",
|
"newServer": "Διακομιστής MCP",
|
||||||
"noDescriptionAvailable": "Δεν υπάρχει διαθέσιμη περιγραφή",
|
"noDescriptionAvailable": "Δεν υπάρχει διαθέσιμη περιγραφή",
|
||||||
|
"noLogs": "Δεν υπάρχουν αρχεία καταγραφής ακόμα",
|
||||||
"noServers": "Δεν έχουν ρυθμιστεί διακομιστές",
|
"noServers": "Δεν έχουν ρυθμιστεί διακομιστές",
|
||||||
"not_support": "Το μοντέλο δεν υποστηρίζεται",
|
"not_support": "Το μοντέλο δεν υποστηρίζεται",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
"failed": "Error al añadir agente",
|
"failed": "Error al añadir agente",
|
||||||
"invalid_agent": "Agent inválido"
|
"invalid_agent": "Agent inválido"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"tooltip": "Actualmente, solo los modelos que admiten puntos finales de Anthropic están disponibles para la función Agente."
|
||||||
|
},
|
||||||
"title": "Agregar Agente",
|
"title": "Agregar Agente",
|
||||||
"type": {
|
"type": {
|
||||||
"placeholder": "Seleccionar tipo de Agente"
|
"placeholder": "Seleccionar tipo de Agente"
|
||||||
@ -2220,10 +2223,10 @@
|
|||||||
"untitled_folder": "Nueva carpeta",
|
"untitled_folder": "Nueva carpeta",
|
||||||
"untitled_note": "Nota sin título",
|
"untitled_note": "Nota sin título",
|
||||||
"upload_failed": "Error al cargar la nota",
|
"upload_failed": "Error al cargar la nota",
|
||||||
"upload_files": "[to be translated]:Upload Files",
|
"upload_files": "Subir archivos",
|
||||||
"upload_folder": "[to be translated]:Upload Folder",
|
"upload_folder": "Carpeta de subida",
|
||||||
"upload_success": "Nota cargada con éxito",
|
"upload_success": "Nota cargada con éxito",
|
||||||
"uploading_files": "[to be translated]:Uploading {{count}} files..."
|
"uploading_files": "Subiendo {{count}} archivos..."
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"assistant": "Respuesta del asistente",
|
"assistant": "Respuesta del asistente",
|
||||||
@ -2514,7 +2517,8 @@
|
|||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"copy": {
|
"copy": {
|
||||||
"image": "Copiar como imagen"
|
"image": "Copiar como imagen",
|
||||||
|
"src": "Copia la fuente de la imagen"
|
||||||
},
|
},
|
||||||
"dialog": "Abrir la ventana de vista previa",
|
"dialog": "Abrir la ventana de vista previa",
|
||||||
"label": "Vista previa",
|
"label": "Vista previa",
|
||||||
@ -3908,6 +3912,7 @@
|
|||||||
"jsonSaveError": "Fallo al guardar la configuración JSON",
|
"jsonSaveError": "Fallo al guardar la configuración JSON",
|
||||||
"jsonSaveSuccess": "Configuración JSON guardada exitosamente",
|
"jsonSaveSuccess": "Configuración JSON guardada exitosamente",
|
||||||
"logoUrl": "URL del logotipo",
|
"logoUrl": "URL del logotipo",
|
||||||
|
"logs": "Registros",
|
||||||
"longRunning": "Modo de ejecución prolongada",
|
"longRunning": "Modo de ejecución prolongada",
|
||||||
"longRunningTooltip": "Una vez habilitado, el servidor admite tareas de larga duración, reinicia el temporizador de tiempo de espera al recibir notificaciones de progreso y amplía el tiempo máximo de espera hasta 10 minutos.",
|
"longRunningTooltip": "Una vez habilitado, el servidor admite tareas de larga duración, reinicia el temporizador de tiempo de espera al recibir notificaciones de progreso y amplía el tiempo máximo de espera hasta 10 minutos.",
|
||||||
"marketplaces": "Mercados",
|
"marketplaces": "Mercados",
|
||||||
@ -3927,6 +3932,7 @@
|
|||||||
"name": "Nombre",
|
"name": "Nombre",
|
||||||
"newServer": "Servidor MCP",
|
"newServer": "Servidor MCP",
|
||||||
"noDescriptionAvailable": "Sin descripción disponible por ahora",
|
"noDescriptionAvailable": "Sin descripción disponible por ahora",
|
||||||
|
"noLogs": "Aún no hay registros",
|
||||||
"noServers": "No se han configurado servidores",
|
"noServers": "No se han configurado servidores",
|
||||||
"not_support": "El modelo no es compatible",
|
"not_support": "El modelo no es compatible",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
"failed": "Échec de l'ajout de l'agent",
|
"failed": "Échec de l'ajout de l'agent",
|
||||||
"invalid_agent": "Agent invalide"
|
"invalid_agent": "Agent invalide"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"tooltip": "Actuellement, seuls les modèles qui prennent en charge les points de terminaison Anthropic sont disponibles pour la fonctionnalité Agent."
|
||||||
|
},
|
||||||
"title": "Ajouter un agent",
|
"title": "Ajouter un agent",
|
||||||
"type": {
|
"type": {
|
||||||
"placeholder": "Sélectionner le type d'Agent"
|
"placeholder": "Sélectionner le type d'Agent"
|
||||||
@ -2220,10 +2223,10 @@
|
|||||||
"untitled_folder": "nouveau dossier",
|
"untitled_folder": "nouveau dossier",
|
||||||
"untitled_note": "Note sans titre",
|
"untitled_note": "Note sans titre",
|
||||||
"upload_failed": "Échec du téléchargement de la note",
|
"upload_failed": "Échec du téléchargement de la note",
|
||||||
"upload_files": "[to be translated]:Upload Files",
|
"upload_files": "Télécharger des fichiers",
|
||||||
"upload_folder": "[to be translated]:Upload Folder",
|
"upload_folder": "Puis dossier de téléchargement",
|
||||||
"upload_success": "Note téléchargée avec succès",
|
"upload_success": "Note téléchargée avec succès",
|
||||||
"uploading_files": "[to be translated]:Uploading {{count}} files..."
|
"uploading_files": "Téléchargement de {{count}} fichiers..."
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"assistant": "Réponse de l'assistant",
|
"assistant": "Réponse de l'assistant",
|
||||||
@ -2514,7 +2517,8 @@
|
|||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"copy": {
|
"copy": {
|
||||||
"image": "Copier en tant qu'image"
|
"image": "Copier en tant qu'image",
|
||||||
|
"src": "Copier la source de l'image"
|
||||||
},
|
},
|
||||||
"dialog": "Ouvrir la fenêtre d'aperçu",
|
"dialog": "Ouvrir la fenêtre d'aperçu",
|
||||||
"label": "Aperçu",
|
"label": "Aperçu",
|
||||||
@ -3908,6 +3912,7 @@
|
|||||||
"jsonSaveError": "Échec de la sauvegarde de la configuration JSON",
|
"jsonSaveError": "Échec de la sauvegarde de la configuration JSON",
|
||||||
"jsonSaveSuccess": "Configuration JSON sauvegardée",
|
"jsonSaveSuccess": "Configuration JSON sauvegardée",
|
||||||
"logoUrl": "Адрес логотипа",
|
"logoUrl": "Адрес логотипа",
|
||||||
|
"logs": "Journaux",
|
||||||
"longRunning": "Mode d'exécution prolongée",
|
"longRunning": "Mode d'exécution prolongée",
|
||||||
"longRunningTooltip": "Une fois activé, le serveur prend en charge les tâches de longue durée, réinitialise le minuteur de temporisation à la réception des notifications de progression, et prolonge le délai d'expiration maximal à 10 minutes.",
|
"longRunningTooltip": "Une fois activé, le serveur prend en charge les tâches de longue durée, réinitialise le minuteur de temporisation à la réception des notifications de progression, et prolonge le délai d'expiration maximal à 10 minutes.",
|
||||||
"marketplaces": "Places de marché",
|
"marketplaces": "Places de marché",
|
||||||
@ -3927,6 +3932,7 @@
|
|||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"newServer": "Сервер MCP",
|
"newServer": "Сервер MCP",
|
||||||
"noDescriptionAvailable": "Aucune description disponible pour le moment",
|
"noDescriptionAvailable": "Aucune description disponible pour le moment",
|
||||||
|
"noLogs": "Aucun journal pour le moment",
|
||||||
"noServers": "Aucun serveur configuré",
|
"noServers": "Aucun serveur configuré",
|
||||||
"not_support": "Модель не поддерживается",
|
"not_support": "Модель не поддерживается",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
"failed": "エージェントの追加に失敗しました",
|
"failed": "エージェントの追加に失敗しました",
|
||||||
"invalid_agent": "無効なエージェント"
|
"invalid_agent": "無効なエージェント"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"tooltip": "現在、エージェント機能では、Anthropicエンドポイントをサポートするモデルのみが利用可能です。"
|
||||||
|
},
|
||||||
"title": "エージェントを追加",
|
"title": "エージェントを追加",
|
||||||
"type": {
|
"type": {
|
||||||
"placeholder": "エージェントタイプを選択"
|
"placeholder": "エージェントタイプを選択"
|
||||||
@ -2220,10 +2223,10 @@
|
|||||||
"untitled_folder": "新ファイル夹",
|
"untitled_folder": "新ファイル夹",
|
||||||
"untitled_note": "無題のメモ",
|
"untitled_note": "無題のメモ",
|
||||||
"upload_failed": "ノートのアップロードに失敗しました",
|
"upload_failed": "ノートのアップロードに失敗しました",
|
||||||
"upload_files": "[to be translated]:Upload Files",
|
"upload_files": "ファイルをアップロード",
|
||||||
"upload_folder": "[to be translated]:Upload Folder",
|
"upload_folder": "アップロードフォルダ",
|
||||||
"upload_success": "ノートのアップロードが成功しました",
|
"upload_success": "ノートのアップロードが成功しました",
|
||||||
"uploading_files": "[to be translated]:Uploading {{count}} files..."
|
"uploading_files": "{{count}} 個のファイルをアップロード中..."
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"assistant": "助手回應",
|
"assistant": "助手回應",
|
||||||
@ -2514,7 +2517,8 @@
|
|||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"copy": {
|
"copy": {
|
||||||
"image": "画像としてコピー"
|
"image": "画像としてコピー",
|
||||||
|
"src": "画像ソースをコピー"
|
||||||
},
|
},
|
||||||
"dialog": "ダイアログを開く",
|
"dialog": "ダイアログを開く",
|
||||||
"label": "プレビュー",
|
"label": "プレビュー",
|
||||||
@ -3908,6 +3912,7 @@
|
|||||||
"jsonSaveError": "JSON設定の保存に失敗しました",
|
"jsonSaveError": "JSON設定の保存に失敗しました",
|
||||||
"jsonSaveSuccess": "JSON設定が保存されました。",
|
"jsonSaveSuccess": "JSON設定が保存されました。",
|
||||||
"logoUrl": "ロゴURL",
|
"logoUrl": "ロゴURL",
|
||||||
|
"logs": "ログ",
|
||||||
"longRunning": "長時間運行モード",
|
"longRunning": "長時間運行モード",
|
||||||
"longRunningTooltip": "このオプションを有効にすると、サーバーは長時間のタスクをサポートします。進行状況通知を受信すると、タイムアウトがリセットされ、最大実行時間が10分に延長されます。",
|
"longRunningTooltip": "このオプションを有効にすると、サーバーは長時間のタスクをサポートします。進行状況通知を受信すると、タイムアウトがリセットされ、最大実行時間が10分に延長されます。",
|
||||||
"marketplaces": "マーケットプレイス",
|
"marketplaces": "マーケットプレイス",
|
||||||
@ -3927,6 +3932,7 @@
|
|||||||
"name": "名前",
|
"name": "名前",
|
||||||
"newServer": "MCP サーバー",
|
"newServer": "MCP サーバー",
|
||||||
"noDescriptionAvailable": "説明がありません",
|
"noDescriptionAvailable": "説明がありません",
|
||||||
|
"noLogs": "ログはまだありません",
|
||||||
"noServers": "サーバーが設定されていません",
|
"noServers": "サーバーが設定されていません",
|
||||||
"not_support": "モデルはサポートされていません",
|
"not_support": "モデルはサポートされていません",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
"failed": "Falha ao adicionar agente",
|
"failed": "Falha ao adicionar agente",
|
||||||
"invalid_agent": "Agent inválido"
|
"invalid_agent": "Agent inválido"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"tooltip": "Atualmente, apenas modelos que suportam endpoints da Anthropic estão disponíveis para o recurso Agente."
|
||||||
|
},
|
||||||
"title": "Adicionar Agente",
|
"title": "Adicionar Agente",
|
||||||
"type": {
|
"type": {
|
||||||
"placeholder": "Selecionar tipo de Agente"
|
"placeholder": "Selecionar tipo de Agente"
|
||||||
@ -2220,10 +2223,10 @@
|
|||||||
"untitled_folder": "Nova pasta",
|
"untitled_folder": "Nova pasta",
|
||||||
"untitled_note": "Nota sem título",
|
"untitled_note": "Nota sem título",
|
||||||
"upload_failed": "Falha ao carregar a nota",
|
"upload_failed": "Falha ao carregar a nota",
|
||||||
"upload_files": "[to be translated]:Upload Files",
|
"upload_files": "Carregar Ficheiros",
|
||||||
"upload_folder": "[to be translated]:Upload Folder",
|
"upload_folder": "Carregar Pasta",
|
||||||
"upload_success": "Nota carregada com sucesso",
|
"upload_success": "Nota carregada com sucesso",
|
||||||
"uploading_files": "[to be translated]:Uploading {{count}} files..."
|
"uploading_files": "A enviar {{count}} ficheiros..."
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"assistant": "Resposta do assistente",
|
"assistant": "Resposta do assistente",
|
||||||
@ -2514,7 +2517,8 @@
|
|||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"copy": {
|
"copy": {
|
||||||
"image": "Copiar como imagem"
|
"image": "Copiar como imagem",
|
||||||
|
"src": "Copiar Origem da Imagem"
|
||||||
},
|
},
|
||||||
"dialog": "Abrir janela de pré-visualização",
|
"dialog": "Abrir janela de pré-visualização",
|
||||||
"label": "Pré-visualização",
|
"label": "Pré-visualização",
|
||||||
@ -3908,6 +3912,7 @@
|
|||||||
"jsonSaveError": "Falha ao salvar configuração JSON",
|
"jsonSaveError": "Falha ao salvar configuração JSON",
|
||||||
"jsonSaveSuccess": "Configuração JSON salva com sucesso",
|
"jsonSaveSuccess": "Configuração JSON salva com sucesso",
|
||||||
"logoUrl": "URL do Logotipo",
|
"logoUrl": "URL do Logotipo",
|
||||||
|
"logs": "Registros",
|
||||||
"longRunning": "Modo de execução prolongada",
|
"longRunning": "Modo de execução prolongada",
|
||||||
"longRunningTooltip": "Quando ativado, o servidor suporta tarefas de longa duração, redefinindo o temporizador de tempo limite ao receber notificações de progresso e estendendo o tempo máximo de tempo limite para 10 minutos.",
|
"longRunningTooltip": "Quando ativado, o servidor suporta tarefas de longa duração, redefinindo o temporizador de tempo limite ao receber notificações de progresso e estendendo o tempo máximo de tempo limite para 10 minutos.",
|
||||||
"marketplaces": "Mercados",
|
"marketplaces": "Mercados",
|
||||||
@ -3927,6 +3932,7 @@
|
|||||||
"name": "Nome",
|
"name": "Nome",
|
||||||
"newServer": "Servidor MCP",
|
"newServer": "Servidor MCP",
|
||||||
"noDescriptionAvailable": "Nenhuma descrição disponível no momento",
|
"noDescriptionAvailable": "Nenhuma descrição disponível no momento",
|
||||||
|
"noLogs": "Ainda sem registos",
|
||||||
"noServers": "Nenhum servidor configurado",
|
"noServers": "Nenhum servidor configurado",
|
||||||
"not_support": "Modelo Não Suportado",
|
"not_support": "Modelo Não Suportado",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
"failed": "Не удалось добавить агента",
|
"failed": "Не удалось добавить агента",
|
||||||
"invalid_agent": "Недействительный агент"
|
"invalid_agent": "Недействительный агент"
|
||||||
},
|
},
|
||||||
|
"model": {
|
||||||
|
"tooltip": "В настоящее время для функции агента доступны только модели, поддерживающие конечные точки Anthropic."
|
||||||
|
},
|
||||||
"title": "Добавить агента",
|
"title": "Добавить агента",
|
||||||
"type": {
|
"type": {
|
||||||
"placeholder": "Выбор типа агента"
|
"placeholder": "Выбор типа агента"
|
||||||
@ -2220,10 +2223,10 @@
|
|||||||
"untitled_folder": "Новая папка",
|
"untitled_folder": "Новая папка",
|
||||||
"untitled_note": "Незаглавленная заметка",
|
"untitled_note": "Незаглавленная заметка",
|
||||||
"upload_failed": "Не удалось загрузить заметку",
|
"upload_failed": "Не удалось загрузить заметку",
|
||||||
"upload_files": "[to be translated]:Upload Files",
|
"upload_files": "Загрузить файлы",
|
||||||
"upload_folder": "[to be translated]:Upload Folder",
|
"upload_folder": "Загрузить папку",
|
||||||
"upload_success": "Заметка успешно загружена",
|
"upload_success": "Заметка успешно загружена",
|
||||||
"uploading_files": "[to be translated]:Uploading {{count}} files..."
|
"uploading_files": "Загрузка {{count}} файлов..."
|
||||||
},
|
},
|
||||||
"notification": {
|
"notification": {
|
||||||
"assistant": "Ответ ассистента",
|
"assistant": "Ответ ассистента",
|
||||||
@ -2514,7 +2517,8 @@
|
|||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"copy": {
|
"copy": {
|
||||||
"image": "Скопировать как изображение"
|
"image": "Скопировать как изображение",
|
||||||
|
"src": "Копировать источник изображения"
|
||||||
},
|
},
|
||||||
"dialog": "Открыть диалог",
|
"dialog": "Открыть диалог",
|
||||||
"label": "Предварительный просмотр",
|
"label": "Предварительный просмотр",
|
||||||
@ -3908,6 +3912,7 @@
|
|||||||
"jsonSaveError": "Не удалось сохранить конфигурацию JSON",
|
"jsonSaveError": "Не удалось сохранить конфигурацию JSON",
|
||||||
"jsonSaveSuccess": "JSON конфигурация сохранена",
|
"jsonSaveSuccess": "JSON конфигурация сохранена",
|
||||||
"logoUrl": "URL логотипа",
|
"logoUrl": "URL логотипа",
|
||||||
|
"logs": "Журналы",
|
||||||
"longRunning": "Длительный режим работы",
|
"longRunning": "Длительный режим работы",
|
||||||
"longRunningTooltip": "Включив эту опцию, сервер будет поддерживать длительные задачи. При получении уведомлений о ходе выполнения будет сброшен тайм-аут и максимальное время выполнения будет увеличено до 10 минут.",
|
"longRunningTooltip": "Включив эту опцию, сервер будет поддерживать длительные задачи. При получении уведомлений о ходе выполнения будет сброшен тайм-аут и максимальное время выполнения будет увеличено до 10 минут.",
|
||||||
"marketplaces": "Торговые площадки",
|
"marketplaces": "Торговые площадки",
|
||||||
@ -3927,6 +3932,7 @@
|
|||||||
"name": "Имя",
|
"name": "Имя",
|
||||||
"newServer": "MCP сервер",
|
"newServer": "MCP сервер",
|
||||||
"noDescriptionAvailable": "Описание отсутствует",
|
"noDescriptionAvailable": "Описание отсутствует",
|
||||||
|
"noLogs": "Логов пока нет",
|
||||||
"noServers": "Серверы не настроены",
|
"noServers": "Серверы не настроены",
|
||||||
"not_support": "Модель не поддерживается",
|
"not_support": "Модель не поддерживается",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
|
|||||||
@ -93,7 +93,7 @@ const ThinkingButton: FC<Props> = ({ quickPanel, model, assistantId }): ReactEle
|
|||||||
level: option,
|
level: option,
|
||||||
label: getReasoningEffortOptionsLabel(option),
|
label: getReasoningEffortOptionsLabel(option),
|
||||||
description: '',
|
description: '',
|
||||||
icon: ThinkingIcon(option),
|
icon: ThinkingIcon({ option }),
|
||||||
isSelected: currentReasoningEffort === option,
|
isSelected: currentReasoningEffort === option,
|
||||||
action: () => onThinkingChange(option)
|
action: () => onThinkingChange(option)
|
||||||
}))
|
}))
|
||||||
@ -135,7 +135,7 @@ const ThinkingButton: FC<Props> = ({ quickPanel, model, assistantId }): ReactEle
|
|||||||
{
|
{
|
||||||
label: t('assistants.settings.reasoning_effort.label'),
|
label: t('assistants.settings.reasoning_effort.label'),
|
||||||
description: '',
|
description: '',
|
||||||
icon: ThinkingIcon(currentReasoningEffort),
|
icon: ThinkingIcon({ option: currentReasoningEffort }),
|
||||||
isMenu: true,
|
isMenu: true,
|
||||||
action: () => openQuickPanel()
|
action: () => openQuickPanel()
|
||||||
}
|
}
|
||||||
@ -163,37 +163,40 @@ const ThinkingButton: FC<Props> = ({ quickPanel, model, assistantId }): ReactEle
|
|||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
aria-pressed={currentReasoningEffort !== 'none'}
|
aria-pressed={currentReasoningEffort !== 'none'}
|
||||||
style={isFixedReasoning ? { cursor: 'default' } : undefined}>
|
style={isFixedReasoning ? { cursor: 'default' } : undefined}>
|
||||||
{ThinkingIcon(currentReasoningEffort)}
|
{ThinkingIcon({ option: currentReasoningEffort, isFixedReasoning })}
|
||||||
</ActionIconButton>
|
</ActionIconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ThinkingIcon = (option?: ThinkingOption) => {
|
const ThinkingIcon = (props: { option?: ThinkingOption; isFixedReasoning?: boolean }) => {
|
||||||
let IconComponent: React.FC<React.SVGProps<SVGSVGElement>> | null = null
|
let IconComponent: React.FC<React.SVGProps<SVGSVGElement>> | null = null
|
||||||
|
if (props.isFixedReasoning) {
|
||||||
switch (option) {
|
IconComponent = MdiLightbulbAutoOutline
|
||||||
case 'minimal':
|
} else {
|
||||||
IconComponent = MdiLightbulbOn30
|
switch (props.option) {
|
||||||
break
|
case 'minimal':
|
||||||
case 'low':
|
IconComponent = MdiLightbulbOn30
|
||||||
IconComponent = MdiLightbulbOn50
|
break
|
||||||
break
|
case 'low':
|
||||||
case 'medium':
|
IconComponent = MdiLightbulbOn50
|
||||||
IconComponent = MdiLightbulbOn80
|
break
|
||||||
break
|
case 'medium':
|
||||||
case 'high':
|
IconComponent = MdiLightbulbOn80
|
||||||
IconComponent = MdiLightbulbOn
|
break
|
||||||
break
|
case 'high':
|
||||||
case 'auto':
|
IconComponent = MdiLightbulbOn
|
||||||
IconComponent = MdiLightbulbAutoOutline
|
break
|
||||||
break
|
case 'auto':
|
||||||
case 'none':
|
IconComponent = MdiLightbulbAutoOutline
|
||||||
IconComponent = MdiLightbulbOffOutline
|
break
|
||||||
break
|
case 'none':
|
||||||
default:
|
IconComponent = MdiLightbulbOffOutline
|
||||||
IconComponent = MdiLightbulbOffOutline
|
break
|
||||||
break
|
default:
|
||||||
|
IconComponent = MdiLightbulbOffOutline
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <IconComponent className="icon" width={18} height={18} style={{ marginTop: -2 }} />
|
return <IconComponent className="icon" width={18} height={18} style={{ marginTop: -2 }} />
|
||||||
|
|||||||
@ -20,6 +20,10 @@ const UpdateAppButton: FC = () => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (update.ignore) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
const handleOpenUpdateDialog = () => {
|
const handleOpenUpdateDialog = () => {
|
||||||
UpdateDialogPopup.show({ releaseInfo: update.info || null })
|
UpdateDialogPopup.show({ releaseInfo: update.info || null })
|
||||||
}
|
}
|
||||||
@ -30,7 +34,7 @@ const UpdateAppButton: FC = () => {
|
|||||||
className="nodrag"
|
className="nodrag"
|
||||||
onClick={handleOpenUpdateDialog}
|
onClick={handleOpenUpdateDialog}
|
||||||
icon={<SyncOutlined />}
|
icon={<SyncOutlined />}
|
||||||
color="orange"
|
color="primary"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small">
|
size="small">
|
||||||
{t('button.update_available')}
|
{t('button.update_available')}
|
||||||
|
|||||||
@ -31,6 +31,8 @@ import { getErrorMessage, uuid } from '@renderer/utils'
|
|||||||
import { isNewApiProvider } from '@renderer/utils/provider'
|
import { isNewApiProvider } from '@renderer/utils/provider'
|
||||||
import { Avatar, Button, Empty, InputNumber, Segmented, Select, Upload } from 'antd'
|
import { Avatar, Button, Empty, InputNumber, Segmented, Select, Upload } from 'antd'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
|
import type { RcFile } from 'antd/es/upload'
|
||||||
|
import type { UploadFile } from 'antd/es/upload/interface'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
@ -553,7 +555,31 @@ const NewApiPage: FC<{ Options: string[] }> = ({ Options }) => {
|
|||||||
maxCount={16}
|
maxCount={16}
|
||||||
showUploadList={true}
|
showUploadList={true}
|
||||||
listType="picture"
|
listType="picture"
|
||||||
beforeUpload={handleImageUpload}>
|
beforeUpload={handleImageUpload}
|
||||||
|
fileList={editImageFiles.map((file, idx): UploadFile<any> => {
|
||||||
|
const rcFile: RcFile = {
|
||||||
|
...file,
|
||||||
|
uid: String(idx),
|
||||||
|
lastModifiedDate: file.lastModified ? new Date(file.lastModified) : new Date()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
uid: rcFile.uid,
|
||||||
|
name: rcFile.name || `image_${idx + 1}.png`,
|
||||||
|
status: 'done',
|
||||||
|
url: URL.createObjectURL(file),
|
||||||
|
originFileObj: rcFile,
|
||||||
|
lastModifiedDate: rcFile.lastModifiedDate
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
onRemove={(file) => {
|
||||||
|
setEditImageFiles((prev) =>
|
||||||
|
prev.filter((f) => {
|
||||||
|
const idx = prev.indexOf(f)
|
||||||
|
return String(idx) !== file.uid
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}}>
|
||||||
<ImagePlaceholder>
|
<ImagePlaceholder>
|
||||||
<ImageSizeImage src={IcImageUp} theme={theme} />
|
<ImageSizeImage src={IcImageUp} theme={theme} />
|
||||||
</ImagePlaceholder>
|
</ImagePlaceholder>
|
||||||
|
|||||||
@ -64,7 +64,7 @@ export const AccessibleDirsSetting = ({ base, update }: AccessibleDirsSettingPro
|
|||||||
return (
|
return (
|
||||||
<SettingsItem>
|
<SettingsItem>
|
||||||
<SettingsTitle
|
<SettingsTitle
|
||||||
actions={
|
contentAfter={
|
||||||
<Tooltip title={t('agent.session.accessible_paths.add')}>
|
<Tooltip title={t('agent.session.accessible_paths.add')}>
|
||||||
<Button type="text" icon={<Plus size={16} />} shape="circle" onClick={addAccessiblePath} />
|
<Button type="text" icon={<Plus size={16} />} shape="circle" onClick={addAccessiblePath} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@ -69,7 +69,7 @@ export const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({ agentBase, u
|
|||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
<SettingsItem divider={false}>
|
<SettingsItem divider={false}>
|
||||||
<SettingsTitle
|
<SettingsTitle
|
||||||
actions={
|
contentAfter={
|
||||||
<Tooltip title={t('agent.settings.advance.maxTurns.description')} placement="left">
|
<Tooltip title={t('agent.settings.advance.maxTurns.description')} placement="left">
|
||||||
<Info size={16} className="text-foreground-400" />
|
<Info size={16} className="text-foreground-400" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { HelpTooltip } from '@renderer/components/TooltipIcons'
|
||||||
import SelectAgentBaseModelButton from '@renderer/pages/home/components/SelectAgentBaseModelButton'
|
import SelectAgentBaseModelButton from '@renderer/pages/home/components/SelectAgentBaseModelButton'
|
||||||
import type { AgentBaseWithId, ApiModel, UpdateAgentFunctionUnion } from '@renderer/types'
|
import type { AgentBaseWithId, ApiModel, UpdateAgentFunctionUnion } from '@renderer/types'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -22,7 +23,9 @@ export const ModelSetting = ({ base, update, isDisabled }: ModelSettingProps) =>
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsItem inline>
|
<SettingsItem inline>
|
||||||
<SettingsTitle id="model">{t('common.model')}</SettingsTitle>
|
<SettingsTitle id="model" contentAfter={<HelpTooltip title={t('agent.add.model.tooltip')} />}>
|
||||||
|
{t('common.model')}
|
||||||
|
</SettingsTitle>
|
||||||
<SelectAgentBaseModelButton
|
<SelectAgentBaseModelButton
|
||||||
agentBase={base}
|
agentBase={base}
|
||||||
onSelect={async (model) => {
|
onSelect={async (model) => {
|
||||||
|
|||||||
@ -10,14 +10,14 @@ import styled from 'styled-components'
|
|||||||
import { SettingDivider } from '..'
|
import { SettingDivider } from '..'
|
||||||
|
|
||||||
export interface SettingsTitleProps extends React.ComponentPropsWithRef<'div'> {
|
export interface SettingsTitleProps extends React.ComponentPropsWithRef<'div'> {
|
||||||
actions?: ReactNode
|
contentAfter?: ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SettingsTitle: React.FC<SettingsTitleProps> = ({ children, actions }) => {
|
export const SettingsTitle: React.FC<SettingsTitleProps> = ({ children, contentAfter }) => {
|
||||||
return (
|
return (
|
||||||
<div className={cn(actions ? 'justify-between' : undefined, 'mb-1 flex items-center gap-2')}>
|
<div className={cn(contentAfter ? 'justify-between' : undefined, 'mb-1 flex items-center gap-2')}>
|
||||||
<span className="flex items-center gap-1 font-bold">{children}</span>
|
<span className="flex items-center gap-1 font-bold">{children}</span>
|
||||||
{actions !== undefined && actions}
|
{contentAfter !== undefined && contentAfter}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,9 @@ import MCPDescription from '@renderer/pages/settings/MCPSettings/McpDescription'
|
|||||||
import type { MCPPrompt, MCPResource, MCPServer, MCPTool } from '@renderer/types'
|
import type { MCPPrompt, MCPResource, MCPServer, MCPTool } from '@renderer/types'
|
||||||
import { parseKeyValueString } from '@renderer/utils/env'
|
import { parseKeyValueString } from '@renderer/utils/env'
|
||||||
import { formatMcpError } from '@renderer/utils/error'
|
import { formatMcpError } from '@renderer/utils/error'
|
||||||
|
import type { MCPServerLogEntry } from '@shared/config/types'
|
||||||
import type { TabsProps } from 'antd'
|
import type { TabsProps } from 'antd'
|
||||||
import { Badge, Button, Flex, Form, Input, Radio, Select, Switch, Tabs } from 'antd'
|
import { Badge, Button, Flex, Form, Input, Modal, Radio, Select, Switch, Tabs, Tag, Typography } from 'antd'
|
||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import { ChevronDown, SaveIcon } from 'lucide-react'
|
import { ChevronDown, SaveIcon } from 'lucide-react'
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
@ -88,8 +89,11 @@ const McpSettings: React.FC = () => {
|
|||||||
|
|
||||||
const [showAdvanced, setShowAdvanced] = useState(false)
|
const [showAdvanced, setShowAdvanced] = useState(false)
|
||||||
const [serverVersion, setServerVersion] = useState<string | null>(null)
|
const [serverVersion, setServerVersion] = useState<string | null>(null)
|
||||||
|
const [logModalOpen, setLogModalOpen] = useState(false)
|
||||||
|
const [logs, setLogs] = useState<(MCPServerLogEntry & { serverId?: string })[]>([])
|
||||||
|
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
const { Text } = Typography
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
@ -234,12 +238,43 @@ const McpSettings: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchServerLogs = async () => {
|
||||||
|
try {
|
||||||
|
const history = await window.api.mcp.getServerLogs(server)
|
||||||
|
setLogs(history)
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('Failed to load server logs', error as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = window.api.mcp.onServerLog((log) => {
|
||||||
|
if (log.serverId && log.serverId !== server.id) return
|
||||||
|
setLogs((prev) => {
|
||||||
|
const merged = [...prev, log]
|
||||||
|
if (merged.length > 200) {
|
||||||
|
return merged.slice(merged.length - 200)
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribe?.()
|
||||||
|
}
|
||||||
|
}, [server.id])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLogs([])
|
||||||
|
}, [server.id])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (server.isActive) {
|
if (server.isActive) {
|
||||||
fetchTools()
|
fetchTools()
|
||||||
fetchPrompts()
|
fetchPrompts()
|
||||||
fetchResources()
|
fetchResources()
|
||||||
fetchServerVersion()
|
fetchServerVersion()
|
||||||
|
fetchServerLogs()
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [server.id, server.isActive])
|
}, [server.id, server.isActive])
|
||||||
@ -736,6 +771,9 @@ const McpSettings: React.FC = () => {
|
|||||||
<ServerName className="text-nowrap">{server?.name}</ServerName>
|
<ServerName className="text-nowrap">{server?.name}</ServerName>
|
||||||
{serverVersion && <VersionBadge count={serverVersion} color="blue" />}
|
{serverVersion && <VersionBadge count={serverVersion} color="blue" />}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Button size="small" onClick={() => setLogModalOpen(true)}>
|
||||||
|
{t('settings.mcp.logs', 'View Logs')}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
danger
|
danger
|
||||||
icon={<DeleteIcon size={14} className="lucide-custom" />}
|
icon={<DeleteIcon size={14} className="lucide-custom" />}
|
||||||
@ -770,6 +808,37 @@ const McpSettings: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title={t('settings.mcp.logs', 'Server Logs')}
|
||||||
|
open={logModalOpen}
|
||||||
|
onCancel={() => setLogModalOpen(false)}
|
||||||
|
footer={null}
|
||||||
|
width={720}
|
||||||
|
centered
|
||||||
|
transitionName="animation-move-down"
|
||||||
|
bodyStyle={{ maxHeight: '60vh', minHeight: '40vh', overflowY: 'auto' }}
|
||||||
|
afterOpenChange={(open) => {
|
||||||
|
if (open) {
|
||||||
|
fetchServerLogs()
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<LogList>
|
||||||
|
{logs.length === 0 && <Text type="secondary">{t('settings.mcp.noLogs', 'No logs yet')}</Text>}
|
||||||
|
{logs.map((log, idx) => (
|
||||||
|
<LogItem key={`${log.timestamp}-${idx}`}>
|
||||||
|
<Flex gap={8} align="baseline">
|
||||||
|
<Timestamp>{new Date(log.timestamp).toLocaleTimeString()}</Timestamp>
|
||||||
|
<Tag color={mapLogLevelColor(log.level)}>{log.level}</Tag>
|
||||||
|
<Text>{log.message}</Text>
|
||||||
|
</Flex>
|
||||||
|
{log.data && (
|
||||||
|
<PreBlock>{typeof log.data === 'string' ? log.data : JSON.stringify(log.data, null, 2)}</PreBlock>
|
||||||
|
)}
|
||||||
|
</LogItem>
|
||||||
|
))}
|
||||||
|
</LogList>
|
||||||
|
</Modal>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -792,6 +861,52 @@ const AdvancedSettingsButton = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const LogList = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const LogItem = styled.div`
|
||||||
|
background: var(--color-bg-2, #1f1f1f);
|
||||||
|
color: var(--color-text-1, #e6e6e6);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--color-border, rgba(255, 255, 255, 0.08));
|
||||||
|
`
|
||||||
|
|
||||||
|
const Timestamp = styled.span`
|
||||||
|
color: var(--color-text-3, #9aa2b1);
|
||||||
|
font-size: 12px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const PreBlock = styled.pre`
|
||||||
|
margin: 6px 0 0;
|
||||||
|
padding: 8px;
|
||||||
|
background: var(--color-bg-3, #111418);
|
||||||
|
color: var(--color-text-1, #e6e6e6);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
border: 1px solid var(--color-border, rgba(255, 255, 255, 0.08));
|
||||||
|
`
|
||||||
|
|
||||||
|
function mapLogLevelColor(level: MCPServerLogEntry['level']) {
|
||||||
|
switch (level) {
|
||||||
|
case 'error':
|
||||||
|
case 'stderr':
|
||||||
|
return 'red'
|
||||||
|
case 'warn':
|
||||||
|
return 'orange'
|
||||||
|
case 'info':
|
||||||
|
case 'stdout':
|
||||||
|
return 'blue'
|
||||||
|
default:
|
||||||
|
return 'default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const VersionBadge = styled(Badge)`
|
const VersionBadge = styled(Badge)`
|
||||||
.ant-badge-count {
|
.ant-badge-count {
|
||||||
background-color: var(--color-primary);
|
background-color: var(--color-primary);
|
||||||
|
|||||||
@ -0,0 +1,74 @@
|
|||||||
|
import { useProvider } from '@renderer/hooks/useProvider'
|
||||||
|
import { Select } from 'antd'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface CherryINSettingsProps {
|
||||||
|
providerId: string
|
||||||
|
apiHost: string
|
||||||
|
setApiHost: (host: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const API_HOST_OPTIONS = [
|
||||||
|
{
|
||||||
|
value: 'https://open.cherryin.cc',
|
||||||
|
labelKey: '加速域名',
|
||||||
|
description: 'open.cherryin.cc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'https://open.cherryin.net',
|
||||||
|
labelKey: '国际域名',
|
||||||
|
description: 'open.cherryin.net'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'https://open.cherryin.ai',
|
||||||
|
labelKey: '备用域名',
|
||||||
|
description: 'open.cherryin.ai'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const CherryINSettings: FC<CherryINSettingsProps> = ({ providerId, apiHost, setApiHost }) => {
|
||||||
|
const { updateProvider } = useProvider(providerId)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const getCurrentHost = useMemo(() => {
|
||||||
|
const matchedOption = API_HOST_OPTIONS.find((option) => apiHost?.includes(option.value.replace('https://', '')))
|
||||||
|
return matchedOption?.value ?? API_HOST_OPTIONS[0].value
|
||||||
|
}, [apiHost])
|
||||||
|
|
||||||
|
const handleHostChange = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
setApiHost(value)
|
||||||
|
updateProvider({ apiHost: value, anthropicApiHost: value })
|
||||||
|
},
|
||||||
|
[setApiHost, updateProvider]
|
||||||
|
)
|
||||||
|
|
||||||
|
const options = useMemo(
|
||||||
|
() =>
|
||||||
|
API_HOST_OPTIONS.map((option) => ({
|
||||||
|
value: option.value,
|
||||||
|
label: (
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
<span>{t(option.labelKey)}</span>
|
||||||
|
<span className="text-[var(--color-text-3)] text-xs">{t(option.description)}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})),
|
||||||
|
[t]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
value={getCurrentHost}
|
||||||
|
onChange={handleHostChange}
|
||||||
|
options={options}
|
||||||
|
style={{ width: '100%', marginTop: 5 }}
|
||||||
|
optionLabelProp="label"
|
||||||
|
labelRender={(option) => option.value}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CherryINSettings
|
||||||
@ -10,7 +10,6 @@ import { PROVIDER_URLS } from '@renderer/config/providers'
|
|||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useAllProviders, useProvider, useProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders, useProvider, useProviders } from '@renderer/hooks/useProvider'
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import i18n from '@renderer/i18n'
|
|
||||||
import AnthropicSettings from '@renderer/pages/settings/ProviderSettings/AnthropicSettings'
|
import AnthropicSettings from '@renderer/pages/settings/ProviderSettings/AnthropicSettings'
|
||||||
import { ModelList } from '@renderer/pages/settings/ProviderSettings/ModelList'
|
import { ModelList } from '@renderer/pages/settings/ProviderSettings/ModelList'
|
||||||
import { checkApi } from '@renderer/services/ApiService'
|
import { checkApi } from '@renderer/services/ApiService'
|
||||||
@ -53,6 +52,7 @@ import {
|
|||||||
} from '..'
|
} from '..'
|
||||||
import ApiOptionsSettingsPopup from './ApiOptionsSettings/ApiOptionsSettingsPopup'
|
import ApiOptionsSettingsPopup from './ApiOptionsSettings/ApiOptionsSettingsPopup'
|
||||||
import AwsBedrockSettings from './AwsBedrockSettings'
|
import AwsBedrockSettings from './AwsBedrockSettings'
|
||||||
|
import CherryINSettings from './CherryINSettings'
|
||||||
import CustomHeaderPopup from './CustomHeaderPopup'
|
import CustomHeaderPopup from './CustomHeaderPopup'
|
||||||
import DMXAPISettings from './DMXAPISettings'
|
import DMXAPISettings from './DMXAPISettings'
|
||||||
import GithubCopilotSettings from './GithubCopilotSettings'
|
import GithubCopilotSettings from './GithubCopilotSettings'
|
||||||
@ -100,13 +100,15 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
|||||||
const [anthropicApiHost, setAnthropicHost] = useState<string | undefined>(provider.anthropicApiHost)
|
const [anthropicApiHost, setAnthropicHost] = useState<string | undefined>(provider.anthropicApiHost)
|
||||||
const [apiVersion, setApiVersion] = useState(provider.apiVersion)
|
const [apiVersion, setApiVersion] = useState(provider.apiVersion)
|
||||||
const [activeHostField, setActiveHostField] = useState<HostField>('apiHost')
|
const [activeHostField, setActiveHostField] = useState<HostField>('apiHost')
|
||||||
const { t } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { setTimeoutTimer } = useTimer()
|
const { setTimeoutTimer } = useTimer()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
const isAzureOpenAI = isAzureOpenAIProvider(provider)
|
const isAzureOpenAI = isAzureOpenAIProvider(provider)
|
||||||
const isDmxapi = provider.id === 'dmxapi'
|
const isDmxapi = provider.id === 'dmxapi'
|
||||||
|
const isCherryIN = provider.id === 'cherryin'
|
||||||
|
const isChineseUser = i18n.language.startsWith('zh')
|
||||||
const noAPIInputProviders = ['aws-bedrock'] as const satisfies SystemProviderId[]
|
const noAPIInputProviders = ['aws-bedrock'] as const satisfies SystemProviderId[]
|
||||||
const hideApiInput = noAPIInputProviders.some((id) => id === provider.id)
|
const hideApiInput = noAPIInputProviders.some((id) => id === provider.id)
|
||||||
const noAPIKeyInputProviders = ['copilot', 'vertexai'] as const satisfies SystemProviderId[]
|
const noAPIKeyInputProviders = ['copilot', 'vertexai'] as const satisfies SystemProviderId[]
|
||||||
@ -339,13 +341,16 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
|||||||
}, [provider.anthropicApiHost])
|
}, [provider.anthropicApiHost])
|
||||||
|
|
||||||
const canConfigureAnthropicHost = useMemo(() => {
|
const canConfigureAnthropicHost = useMemo(() => {
|
||||||
|
if (isCherryIN) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if (isNewApiProvider(provider)) {
|
if (isNewApiProvider(provider)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
provider.type !== 'anthropic' && isSystemProviderId(provider.id) && isAnthropicCompatibleProviderId(provider.id)
|
provider.type !== 'anthropic' && isSystemProviderId(provider.id) && isAnthropicCompatibleProviderId(provider.id)
|
||||||
)
|
)
|
||||||
}, [provider])
|
}, [isCherryIN, provider])
|
||||||
|
|
||||||
const anthropicHostPreview = useMemo(() => {
|
const anthropicHostPreview = useMemo(() => {
|
||||||
const rawHost = anthropicApiHost ?? provider.anthropicApiHost
|
const rawHost = anthropicApiHost ?? provider.anthropicApiHost
|
||||||
@ -514,19 +519,23 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
|||||||
</SettingSubtitle>
|
</SettingSubtitle>
|
||||||
{activeHostField === 'apiHost' && (
|
{activeHostField === 'apiHost' && (
|
||||||
<>
|
<>
|
||||||
<Space.Compact style={{ width: '100%', marginTop: 5 }}>
|
{isCherryIN && isChineseUser ? (
|
||||||
<Input
|
<CherryINSettings providerId={provider.id} apiHost={apiHost} setApiHost={setApiHost} />
|
||||||
value={apiHost}
|
) : (
|
||||||
placeholder={t('settings.provider.api_host')}
|
<Space.Compact style={{ width: '100%', marginTop: 5 }}>
|
||||||
onChange={(e) => setApiHost(e.target.value)}
|
<Input
|
||||||
onBlur={onUpdateApiHost}
|
value={apiHost}
|
||||||
/>
|
placeholder={t('settings.provider.api_host')}
|
||||||
{isApiHostResettable && (
|
onChange={(e) => setApiHost(e.target.value)}
|
||||||
<Button danger onClick={onReset}>
|
onBlur={onUpdateApiHost}
|
||||||
{t('settings.provider.api.url.reset')}
|
/>
|
||||||
</Button>
|
{isApiHostResettable && (
|
||||||
)}
|
<Button danger onClick={onReset}>
|
||||||
</Space.Compact>
|
{t('settings.provider.api.url.reset')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Space.Compact>
|
||||||
|
)}
|
||||||
{isVertexProvider(provider) && (
|
{isVertexProvider(provider) && (
|
||||||
<SettingHelpTextRow>
|
<SettingHelpTextRow>
|
||||||
<SettingHelpText>{t('settings.provider.vertex_ai.api_host_help')}</SettingHelpText>
|
<SettingHelpText>{t('settings.provider.vertex_ai.api_host_help')}</SettingHelpText>
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import type { AssistantPreset } from '@renderer/types'
|
|||||||
import { getLeadingEmoji } from '@renderer/utils'
|
import { getLeadingEmoji } from '@renderer/utils'
|
||||||
import { Button, Dropdown } from 'antd'
|
import { Button, Dropdown } from 'antd'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
|
import { isArray } from 'lodash'
|
||||||
import { ArrowDownAZ, Ellipsis, PlusIcon, SquareArrowOutUpRight } from 'lucide-react'
|
import { ArrowDownAZ, Ellipsis, PlusIcon, SquareArrowOutUpRight } from 'lucide-react'
|
||||||
import { type FC, memo, useCallback, useEffect, useRef, useState } from 'react'
|
import { type FC, memo, useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -142,7 +143,7 @@ const AssistantPresetCard: FC<Props> = ({ preset, onClick, activegroup, getLocal
|
|||||||
{getLocalizedGroupName('我的')}
|
{getLocalizedGroupName('我的')}
|
||||||
</CustomTag>
|
</CustomTag>
|
||||||
)}
|
)}
|
||||||
{!!preset.group?.length &&
|
{isArray(preset.group) &&
|
||||||
preset.group.map((group) => (
|
preset.group.map((group) => (
|
||||||
<CustomTag key={group} color="#A0A0A0" size={11}>
|
<CustomTag key={group} color="#A0A0A0" size={11}>
|
||||||
{getLocalizedGroupName(group)}
|
{getLocalizedGroupName(group)}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import {
|
|||||||
UNLIMITED_CONTEXT_COUNT
|
UNLIMITED_CONTEXT_COUNT
|
||||||
} from '@renderer/config/constant'
|
} from '@renderer/config/constant'
|
||||||
import { isQwenMTModel } from '@renderer/config/models/qwen'
|
import { isQwenMTModel } from '@renderer/config/models/qwen'
|
||||||
import { CHERRYAI_PROVIDER } from '@renderer/config/providers'
|
|
||||||
import { UNKNOWN } from '@renderer/config/translate'
|
import { UNKNOWN } from '@renderer/config/translate'
|
||||||
import { getStoreProviders } from '@renderer/hooks/useStore'
|
import { getStoreProviders } from '@renderer/hooks/useStore'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
@ -27,7 +26,7 @@ import { uuid } from '@renderer/utils'
|
|||||||
|
|
||||||
const logger = loggerService.withContext('AssistantService')
|
const logger = loggerService.withContext('AssistantService')
|
||||||
|
|
||||||
export const DEFAULT_ASSISTANT_SETTINGS: AssistantSettings = {
|
export const DEFAULT_ASSISTANT_SETTINGS = {
|
||||||
temperature: DEFAULT_TEMPERATURE,
|
temperature: DEFAULT_TEMPERATURE,
|
||||||
enableTemperature: true,
|
enableTemperature: true,
|
||||||
contextCount: DEFAULT_CONTEXTCOUNT,
|
contextCount: DEFAULT_CONTEXTCOUNT,
|
||||||
@ -39,7 +38,7 @@ export const DEFAULT_ASSISTANT_SETTINGS: AssistantSettings = {
|
|||||||
// It would gracefully fallback to prompt if not supported by model.
|
// It would gracefully fallback to prompt if not supported by model.
|
||||||
toolUseMode: 'function',
|
toolUseMode: 'function',
|
||||||
customParameters: []
|
customParameters: []
|
||||||
} as const
|
} as const satisfies AssistantSettings
|
||||||
|
|
||||||
export function getDefaultAssistant(): Assistant {
|
export function getDefaultAssistant(): Assistant {
|
||||||
return {
|
return {
|
||||||
@ -142,7 +141,7 @@ export function getProviderByModel(model?: Model): Provider {
|
|||||||
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
const defaultProvider = providers.find((p) => p.id === getDefaultModel()?.provider)
|
const defaultProvider = providers.find((p) => p.id === getDefaultModel()?.provider)
|
||||||
return defaultProvider || CHERRYAI_PROVIDER || providers[0]
|
return defaultProvider || providers[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider
|
return provider
|
||||||
|
|||||||
@ -162,7 +162,7 @@ export const searchKnowledgeBase = async (
|
|||||||
|
|
||||||
const searchResults: KnowledgeSearchResult[] = await window.api.knowledgeBase.search(
|
const searchResults: KnowledgeSearchResult[] = await window.api.knowledgeBase.search(
|
||||||
{
|
{
|
||||||
search: rewrite || query,
|
search: query || rewrite || '',
|
||||||
base: baseParams
|
base: baseParams
|
||||||
},
|
},
|
||||||
currentSpan?.spanContext()
|
currentSpan?.spanContext()
|
||||||
|
|||||||
@ -45,7 +45,7 @@ function normalizeModels<T>(models: T[], transformer: (entry: T) => Model | null
|
|||||||
}
|
}
|
||||||
|
|
||||||
function adaptSdkModel(provider: Provider, model: SdkModel): Model | null {
|
function adaptSdkModel(provider: Provider, model: SdkModel): Model | null {
|
||||||
const id = pickPreferredString([(model as any)?.id, (model as any)?.modelId])
|
const id = pickPreferredString([(model as any)?.id, (model as any)?.modelId, (model as any)?.name])
|
||||||
const name = pickPreferredString([
|
const name = pickPreferredString([
|
||||||
(model as any)?.display_name,
|
(model as any)?.display_name,
|
||||||
(model as any)?.displayName,
|
(model as any)?.displayName,
|
||||||
|
|||||||
@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
|
|||||||
{
|
{
|
||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 182,
|
version: 183,
|
||||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'],
|
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'],
|
||||||
migrate
|
migrate
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2981,6 +2981,22 @@ const migrateConfig = {
|
|||||||
logger.error('migrate 182 error', error as Error)
|
logger.error('migrate 182 error', error as Error)
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'183': (state: RootState) => {
|
||||||
|
try {
|
||||||
|
state.llm.providers.forEach((provider) => {
|
||||||
|
if (provider.id === SystemProviderIds.cherryin) {
|
||||||
|
provider.apiHost = 'https://open.cherryin.cc'
|
||||||
|
provider.anthropicApiHost = 'https://open.cherryin.cc'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
state.llm.providers = moveProvider(state.llm.providers, SystemProviderIds.poe, 10)
|
||||||
|
logger.info('migrate 183 success')
|
||||||
|
return state
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('migrate 183 error', error as Error)
|
||||||
|
return state
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,7 @@ export interface UpdateState {
|
|||||||
downloaded: boolean
|
downloaded: boolean
|
||||||
downloadProgress: number
|
downloadProgress: number
|
||||||
available: boolean
|
available: boolean
|
||||||
|
ignore: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RuntimeState {
|
export interface RuntimeState {
|
||||||
@ -79,7 +80,8 @@ const initialState: RuntimeState = {
|
|||||||
downloading: false,
|
downloading: false,
|
||||||
downloaded: false,
|
downloaded: false,
|
||||||
downloadProgress: 0,
|
downloadProgress: 0,
|
||||||
available: false
|
available: false,
|
||||||
|
ignore: false
|
||||||
},
|
},
|
||||||
export: {
|
export: {
|
||||||
isExporting: false
|
isExporting: false
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
formatVertexApiHost,
|
formatVertexApiHost,
|
||||||
getTrailingApiVersion,
|
getTrailingApiVersion,
|
||||||
hasAPIVersion,
|
hasAPIVersion,
|
||||||
|
isWithTrailingSharp,
|
||||||
maskApiKey,
|
maskApiKey,
|
||||||
routeToEndpoint,
|
routeToEndpoint,
|
||||||
splitApiKeyString,
|
splitApiKeyString,
|
||||||
@ -439,6 +440,43 @@ describe('api', () => {
|
|||||||
it('returns undefined for empty string', () => {
|
it('returns undefined for empty string', () => {
|
||||||
expect(getTrailingApiVersion('')).toBeUndefined()
|
expect(getTrailingApiVersion('')).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('returns undefined when URL ends with # regardless of version', () => {
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/v1#')).toBeUndefined()
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/v2beta#')).toBeUndefined()
|
||||||
|
expect(getTrailingApiVersion('https://gateway.ai.cloudflare.com/v1#')).toBeUndefined()
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/service/v1#')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles URLs with # and trailing slash correctly', () => {
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/v1/#')).toBeUndefined()
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/v2beta/#')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles URLs with version followed by # and additional path', () => {
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/v1#endpoint')).toBeUndefined()
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/v2beta#chat/completions')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles complex URLs with multiple # characters', () => {
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/v1#path#')).toBeUndefined()
|
||||||
|
expect(getTrailingApiVersion('https://gateway.ai.cloudflare.com/v1/xxx/v2beta#')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles URLs ending with # when version is not at the end', () => {
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/v1/service#')).toBeUndefined()
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/v1/api/chat#')).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('distinguishes between URLs with and without trailing #', () => {
|
||||||
|
// Without # - should extract version
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/v1')).toBe('v1')
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/v2beta')).toBe('v2beta')
|
||||||
|
|
||||||
|
// With # - should return undefined
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/v1#')).toBeUndefined()
|
||||||
|
expect(getTrailingApiVersion('https://api.example.com/v2beta#')).toBeUndefined()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('withoutTrailingApiVersion', () => {
|
describe('withoutTrailingApiVersion', () => {
|
||||||
@ -484,6 +522,70 @@ describe('api', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('isWithTrailingSharp', () => {
|
||||||
|
it('returns true when URL ends with #', () => {
|
||||||
|
expect(isWithTrailingSharp('https://api.example.com#')).toBe(true)
|
||||||
|
expect(isWithTrailingSharp('http://localhost:3000#')).toBe(true)
|
||||||
|
expect(isWithTrailingSharp('#')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns false when URL does not end with #', () => {
|
||||||
|
expect(isWithTrailingSharp('https://api.example.com')).toBe(false)
|
||||||
|
expect(isWithTrailingSharp('http://localhost:3000')).toBe(false)
|
||||||
|
expect(isWithTrailingSharp('')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns false when URL has # in the middle but not at the end', () => {
|
||||||
|
expect(isWithTrailingSharp('https://api.example.com#path')).toBe(false)
|
||||||
|
expect(isWithTrailingSharp('https://api.example.com#section/path')).toBe(false)
|
||||||
|
expect(isWithTrailingSharp('https://api.example.com#path#other')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles URLs with multiple # characters', () => {
|
||||||
|
expect(isWithTrailingSharp('https://api.example.com##')).toBe(true)
|
||||||
|
expect(isWithTrailingSharp('https://api.example.com#path#')).toBe(true)
|
||||||
|
expect(isWithTrailingSharp('https://api.example.com###')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles URLs with trailing whitespace after #', () => {
|
||||||
|
expect(isWithTrailingSharp('https://api.example.com# ')).toBe(false)
|
||||||
|
expect(isWithTrailingSharp('https://api.example.com#\t')).toBe(false)
|
||||||
|
expect(isWithTrailingSharp('https://api.example.com#\n')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles URLs with whitespace before trailing #', () => {
|
||||||
|
expect(isWithTrailingSharp(' https://api.example.com#')).toBe(true)
|
||||||
|
expect(isWithTrailingSharp('\thttps://localhost:3000#')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('preserves type safety with generic parameter', () => {
|
||||||
|
const url1: string = 'https://api.example.com#'
|
||||||
|
const url2 = 'https://example.com' as const
|
||||||
|
|
||||||
|
expect(isWithTrailingSharp(url1)).toBe(true)
|
||||||
|
expect(isWithTrailingSharp(url2)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles complex real-world URLs', () => {
|
||||||
|
expect(isWithTrailingSharp('https://open.cherryin.net/v1/chat/completions#')).toBe(true)
|
||||||
|
expect(isWithTrailingSharp('https://api.openai.com/v1/engines/gpt-4#')).toBe(true)
|
||||||
|
expect(isWithTrailingSharp('https://gateway.ai.cloudflare.com/v1/xxx/v1beta#')).toBe(true)
|
||||||
|
|
||||||
|
expect(isWithTrailingSharp('https://open.cherryin.net/v1/chat/completions')).toBe(false)
|
||||||
|
expect(isWithTrailingSharp('https://api.openai.com/v1/engines/gpt-4')).toBe(false)
|
||||||
|
expect(isWithTrailingSharp('https://gateway.ai.cloudflare.com/v1/xxx/v1beta')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles edge cases', () => {
|
||||||
|
expect(isWithTrailingSharp('#')).toBe(true)
|
||||||
|
expect(isWithTrailingSharp(' #')).toBe(true)
|
||||||
|
expect(isWithTrailingSharp('# ')).toBe(false)
|
||||||
|
expect(isWithTrailingSharp('path#')).toBe(true)
|
||||||
|
expect(isWithTrailingSharp('/path/with/trailing/#')).toBe(true)
|
||||||
|
expect(isWithTrailingSharp('/path/without/trailing/')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('withoutTrailingSharp', () => {
|
describe('withoutTrailingSharp', () => {
|
||||||
it('removes trailing # from URL', () => {
|
it('removes trailing # from URL', () => {
|
||||||
expect(withoutTrailingSharp('https://api.example.com#')).toBe('https://api.example.com')
|
expect(withoutTrailingSharp('https://api.example.com#')).toBe('https://api.example.com')
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac, isWin } from '@renderer/config/constant'
|
||||||
import { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant'
|
import { useSelectionAssistant } from '@renderer/hooks/useSelectionAssistant'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
@ -8,11 +8,14 @@ import { IpcChannel } from '@shared/IpcChannel'
|
|||||||
import { Button, Slider, Tooltip } from 'antd'
|
import { Button, Slider, Tooltip } from 'antd'
|
||||||
import { Droplet, Minus, Pin, X } from 'lucide-react'
|
import { Droplet, Minus, Pin, X } from 'lucide-react'
|
||||||
import { DynamicIcon } from 'lucide-react/dynamic'
|
import { DynamicIcon } from 'lucide-react/dynamic'
|
||||||
import type { FC } from 'react'
|
import type { FC, MouseEvent as ReactMouseEvent } from 'react'
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
|
// [Windows only] Electron bug workaround type - can be removed once https://github.com/electron/electron/issues/48554 is fixed
|
||||||
|
type ResizeDirection = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw'
|
||||||
|
|
||||||
import ActionGeneral from './components/ActionGeneral'
|
import ActionGeneral from './components/ActionGeneral'
|
||||||
import ActionTranslate from './components/ActionTranslate'
|
import ActionTranslate from './components/ActionTranslate'
|
||||||
|
|
||||||
@ -185,11 +188,62 @@ const SelectionActionApp: FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Windows only] Manual window resize handler
|
||||||
|
*
|
||||||
|
* ELECTRON BUG WORKAROUND:
|
||||||
|
* In Electron, when using `frame: false` + `transparent: true`, the native window
|
||||||
|
* resize functionality is broken on Windows. This is a known Electron bug.
|
||||||
|
* See: https://github.com/electron/electron/issues/48554
|
||||||
|
*
|
||||||
|
* This custom resize implementation can be removed once the Electron bug is fixed.
|
||||||
|
*/
|
||||||
|
const handleResizeStart = useCallback((e: ReactMouseEvent, direction: ResizeDirection) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
let lastX = e.screenX
|
||||||
|
let lastY = e.screenY
|
||||||
|
|
||||||
|
const handleMouseMove = (moveEvent: MouseEvent) => {
|
||||||
|
const deltaX = moveEvent.screenX - lastX
|
||||||
|
const deltaY = moveEvent.screenY - lastY
|
||||||
|
|
||||||
|
if (deltaX !== 0 || deltaY !== 0) {
|
||||||
|
window.api.selection.resizeActionWindow(deltaX, deltaY, direction)
|
||||||
|
lastX = moveEvent.screenX
|
||||||
|
lastY = moveEvent.screenY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
window.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
window.removeEventListener('mouseup', handleMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', handleMouseMove)
|
||||||
|
window.addEventListener('mouseup', handleMouseUp)
|
||||||
|
}, [])
|
||||||
|
|
||||||
//we don't need to render the component if action is not set
|
//we don't need to render the component if action is not set
|
||||||
if (!action) return null
|
if (!action) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WindowFrame $opacity={opacity / 100}>
|
<WindowFrame $opacity={opacity / 100}>
|
||||||
|
{/* [Windows only] Custom resize handles - Electron bug workaround, can be removed once fixed */}
|
||||||
|
{isWin && (
|
||||||
|
<>
|
||||||
|
<ResizeHandle $direction="n" onMouseDown={(e) => handleResizeStart(e, 'n')} />
|
||||||
|
<ResizeHandle $direction="s" onMouseDown={(e) => handleResizeStart(e, 's')} />
|
||||||
|
<ResizeHandle $direction="e" onMouseDown={(e) => handleResizeStart(e, 'e')} />
|
||||||
|
<ResizeHandle $direction="w" onMouseDown={(e) => handleResizeStart(e, 'w')} />
|
||||||
|
<ResizeHandle $direction="ne" onMouseDown={(e) => handleResizeStart(e, 'ne')} />
|
||||||
|
<ResizeHandle $direction="nw" onMouseDown={(e) => handleResizeStart(e, 'nw')} />
|
||||||
|
<ResizeHandle $direction="se" onMouseDown={(e) => handleResizeStart(e, 'se')} />
|
||||||
|
<ResizeHandle $direction="sw" onMouseDown={(e) => handleResizeStart(e, 'sw')} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<TitleBar $isWindowFocus={isWindowFocus} style={isMac ? { paddingLeft: '70px' } : {}}>
|
<TitleBar $isWindowFocus={isWindowFocus} style={isMac ? { paddingLeft: '70px' } : {}}>
|
||||||
{action.icon && (
|
{action.icon && (
|
||||||
<TitleBarIcon>
|
<TitleBarIcon>
|
||||||
@ -431,4 +485,90 @@ const OpacitySlider = styled.div`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Windows only] Custom resize handle styled component
|
||||||
|
*
|
||||||
|
* ELECTRON BUG WORKAROUND:
|
||||||
|
* This component can be removed once https://github.com/electron/electron/issues/48554 is fixed.
|
||||||
|
*/
|
||||||
|
const ResizeHandle = styled.div<{ $direction: ResizeDirection }>`
|
||||||
|
position: absolute;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
${({ $direction }) => {
|
||||||
|
const edgeSize = '6px'
|
||||||
|
const cornerSize = '12px'
|
||||||
|
|
||||||
|
switch ($direction) {
|
||||||
|
case 'n':
|
||||||
|
return `
|
||||||
|
top: 0;
|
||||||
|
left: ${cornerSize};
|
||||||
|
right: ${cornerSize};
|
||||||
|
height: ${edgeSize};
|
||||||
|
cursor: ns-resize;
|
||||||
|
`
|
||||||
|
case 's':
|
||||||
|
return `
|
||||||
|
bottom: 0;
|
||||||
|
left: ${cornerSize};
|
||||||
|
right: ${cornerSize};
|
||||||
|
height: ${edgeSize};
|
||||||
|
cursor: ns-resize;
|
||||||
|
`
|
||||||
|
case 'e':
|
||||||
|
return `
|
||||||
|
right: 0;
|
||||||
|
top: ${cornerSize};
|
||||||
|
bottom: ${cornerSize};
|
||||||
|
width: ${edgeSize};
|
||||||
|
cursor: ew-resize;
|
||||||
|
`
|
||||||
|
case 'w':
|
||||||
|
return `
|
||||||
|
left: 0;
|
||||||
|
top: ${cornerSize};
|
||||||
|
bottom: ${cornerSize};
|
||||||
|
width: ${edgeSize};
|
||||||
|
cursor: ew-resize;
|
||||||
|
`
|
||||||
|
case 'ne':
|
||||||
|
return `
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: ${cornerSize};
|
||||||
|
height: ${cornerSize};
|
||||||
|
cursor: nesw-resize;
|
||||||
|
`
|
||||||
|
case 'nw':
|
||||||
|
return `
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: ${cornerSize};
|
||||||
|
height: ${cornerSize};
|
||||||
|
cursor: nwse-resize;
|
||||||
|
`
|
||||||
|
case 'se':
|
||||||
|
return `
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: ${cornerSize};
|
||||||
|
height: ${cornerSize};
|
||||||
|
cursor: nwse-resize;
|
||||||
|
`
|
||||||
|
case 'sw':
|
||||||
|
return `
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: ${cornerSize};
|
||||||
|
height: ${cornerSize};
|
||||||
|
cursor: nesw-resize;
|
||||||
|
`
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
`
|
||||||
|
|
||||||
export default SelectionActionApp
|
export default SelectionActionApp
|
||||||
|
|||||||
85
yarn.lock
85
yarn.lock
@ -503,9 +503,9 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk@npm:0.1.53":
|
"@anthropic-ai/claude-agent-sdk@npm:0.1.62":
|
||||||
version: 0.1.53
|
version: 0.1.62
|
||||||
resolution: "@anthropic-ai/claude-agent-sdk@npm:0.1.53"
|
resolution: "@anthropic-ai/claude-agent-sdk@npm:0.1.62"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-darwin-arm64": "npm:^0.33.5"
|
"@img/sharp-darwin-arm64": "npm:^0.33.5"
|
||||||
"@img/sharp-darwin-x64": "npm:^0.33.5"
|
"@img/sharp-darwin-x64": "npm:^0.33.5"
|
||||||
@ -534,13 +534,13 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
"@img/sharp-win32-x64":
|
"@img/sharp-win32-x64":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 10c0/9b8e444f113e1f6a425d87287c653a5a441836c6100e954fdc33ce9149c8d87ca1a7d495563f4fac583cbaf14946fe18c321eb555b3f0e44a5de8433ba06bdaf
|
checksum: 10c0/bca0978651cd28798cd71a0071618eca37253905841fa0e20ec59f69ac4865e2c6c4e5fec034bc7b85a5748df5c3c37e3193d6adbd1cad73668f112d049390a3
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@anthropic-ai/claude-agent-sdk@patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.53#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.53-4b77f4cf29.patch":
|
"@anthropic-ai/claude-agent-sdk@patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.62#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.62-23ae56f8c8.patch":
|
||||||
version: 0.1.53
|
version: 0.1.62
|
||||||
resolution: "@anthropic-ai/claude-agent-sdk@patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.53#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.53-4b77f4cf29.patch::version=0.1.53&hash=b05505"
|
resolution: "@anthropic-ai/claude-agent-sdk@patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.62#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.62-23ae56f8c8.patch::version=0.1.62&hash=b8fdbe"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@img/sharp-darwin-arm64": "npm:^0.33.5"
|
"@img/sharp-darwin-arm64": "npm:^0.33.5"
|
||||||
"@img/sharp-darwin-x64": "npm:^0.33.5"
|
"@img/sharp-darwin-x64": "npm:^0.33.5"
|
||||||
@ -569,7 +569,7 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
"@img/sharp-win32-x64":
|
"@img/sharp-win32-x64":
|
||||||
optional: true
|
optional: true
|
||||||
checksum: 10c0/54abfc37ca1e1617503b1a70d31a165b95cb898e6192637d3ab450be081bc8c89933714d1b150f5c3ef3948b3c481f81b9dfaf45fa1edff745477edf3e3c58e5
|
checksum: 10c0/6c59cfc3d3b7d903d946c5da6e0c2ad6798ae837b67c2a9e679df2803d7577823f8feec26e48fa9f815b9ff19612c66e2682fdd182be0344b60febb6e64ac85e
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -10046,7 +10046,7 @@ __metadata:
|
|||||||
"@ai-sdk/perplexity": "npm:^2.0.20"
|
"@ai-sdk/perplexity": "npm:^2.0.20"
|
||||||
"@ai-sdk/test-server": "npm:^0.0.1"
|
"@ai-sdk/test-server": "npm:^0.0.1"
|
||||||
"@ant-design/v5-patch-for-react-19": "npm:^1.0.3"
|
"@ant-design/v5-patch-for-react-19": "npm:^1.0.3"
|
||||||
"@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.53#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.53-4b77f4cf29.patch"
|
"@anthropic-ai/claude-agent-sdk": "patch:@anthropic-ai/claude-agent-sdk@npm%3A0.1.62#~/.yarn/patches/@anthropic-ai-claude-agent-sdk-npm-0.1.62-23ae56f8c8.patch"
|
||||||
"@anthropic-ai/sdk": "npm:^0.41.0"
|
"@anthropic-ai/sdk": "npm:^0.41.0"
|
||||||
"@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch"
|
"@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch"
|
||||||
"@aws-sdk/client-bedrock": "npm:^3.910.0"
|
"@aws-sdk/client-bedrock": "npm:^3.910.0"
|
||||||
@ -10187,7 +10187,6 @@ __metadata:
|
|||||||
clsx: "npm:^2.1.1"
|
clsx: "npm:^2.1.1"
|
||||||
code-inspector-plugin: "npm:^0.20.14"
|
code-inspector-plugin: "npm:^0.20.14"
|
||||||
color: "npm:^5.0.0"
|
color: "npm:^5.0.0"
|
||||||
concurrently: "npm:^9.2.1"
|
|
||||||
country-flag-emoji-polyfill: "npm:0.1.8"
|
country-flag-emoji-polyfill: "npm:0.1.8"
|
||||||
dayjs: "npm:^1.11.11"
|
dayjs: "npm:^1.11.11"
|
||||||
dexie: "npm:^4.0.8"
|
dexie: "npm:^4.0.8"
|
||||||
@ -11504,16 +11503,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"chalk@npm:4.1.2, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2":
|
|
||||||
version: 4.1.2
|
|
||||||
resolution: "chalk@npm:4.1.2"
|
|
||||||
dependencies:
|
|
||||||
ansi-styles: "npm:^4.1.0"
|
|
||||||
supports-color: "npm:^7.1.0"
|
|
||||||
checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"chalk@npm:^3.0.0":
|
"chalk@npm:^3.0.0":
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
resolution: "chalk@npm:3.0.0"
|
resolution: "chalk@npm:3.0.0"
|
||||||
@ -11524,6 +11513,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2":
|
||||||
|
version: 4.1.2
|
||||||
|
resolution: "chalk@npm:4.1.2"
|
||||||
|
dependencies:
|
||||||
|
ansi-styles: "npm:^4.1.0"
|
||||||
|
supports-color: "npm:^7.1.0"
|
||||||
|
checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"chalk@npm:^5.4.1":
|
"chalk@npm:^5.4.1":
|
||||||
version: 5.4.1
|
version: 5.4.1
|
||||||
resolution: "chalk@npm:5.4.1"
|
resolution: "chalk@npm:5.4.1"
|
||||||
@ -12146,23 +12145,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"concurrently@npm:^9.2.1":
|
|
||||||
version: 9.2.1
|
|
||||||
resolution: "concurrently@npm:9.2.1"
|
|
||||||
dependencies:
|
|
||||||
chalk: "npm:4.1.2"
|
|
||||||
rxjs: "npm:7.8.2"
|
|
||||||
shell-quote: "npm:1.8.3"
|
|
||||||
supports-color: "npm:8.1.1"
|
|
||||||
tree-kill: "npm:1.2.2"
|
|
||||||
yargs: "npm:17.7.2"
|
|
||||||
bin:
|
|
||||||
conc: dist/bin/concurrently.js
|
|
||||||
concurrently: dist/bin/concurrently.js
|
|
||||||
checksum: 10c0/da37f239f82eb7ac24f5ddb56259861e5f1d6da2ade7602b6ea7ad3101b13b5ccec02a77b7001402d1028ff2fdc38eed55644b32853ad5abf30e057002a963aa
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"conf@npm:^10.2.0":
|
"conf@npm:^10.2.0":
|
||||||
version: 10.2.0
|
version: 10.2.0
|
||||||
resolution: "conf@npm:10.2.0"
|
resolution: "conf@npm:10.2.0"
|
||||||
@ -23026,15 +23008,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"rxjs@npm:7.8.2":
|
|
||||||
version: 7.8.2
|
|
||||||
resolution: "rxjs@npm:7.8.2"
|
|
||||||
dependencies:
|
|
||||||
tslib: "npm:^2.1.0"
|
|
||||||
checksum: 10c0/1fcd33d2066ada98ba8f21fcbbcaee9f0b271de1d38dc7f4e256bfbc6ffcdde68c8bfb69093de7eeb46f24b1fb820620bf0223706cff26b4ab99a7ff7b2e2c45
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1, safe-buffer@npm:~5.2.0":
|
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1, safe-buffer@npm:~5.2.0":
|
||||||
version: 5.2.1
|
version: 5.2.1
|
||||||
resolution: "safe-buffer@npm:5.2.1"
|
resolution: "safe-buffer@npm:5.2.1"
|
||||||
@ -23367,13 +23340,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"shell-quote@npm:1.8.3":
|
|
||||||
version: 1.8.3
|
|
||||||
resolution: "shell-quote@npm:1.8.3"
|
|
||||||
checksum: 10c0/bee87c34e1e986cfb4c30846b8e6327d18874f10b535699866f368ade11ea4ee45433d97bf5eada22c4320c27df79c3a6a7eb1bf3ecfc47f2c997d9e5e2672fd
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"shiki@npm:3.12.0, shiki@npm:^3.12.0":
|
"shiki@npm:3.12.0, shiki@npm:^3.12.0":
|
||||||
version: 3.12.0
|
version: 3.12.0
|
||||||
resolution: "shiki@npm:3.12.0"
|
resolution: "shiki@npm:3.12.0"
|
||||||
@ -24099,15 +24065,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"supports-color@npm:8.1.1":
|
|
||||||
version: 8.1.1
|
|
||||||
resolution: "supports-color@npm:8.1.1"
|
|
||||||
dependencies:
|
|
||||||
has-flag: "npm:^4.0.0"
|
|
||||||
checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"supports-color@npm:^7.1.0":
|
"supports-color@npm:^7.1.0":
|
||||||
version: 7.2.0
|
version: 7.2.0
|
||||||
resolution: "supports-color@npm:7.2.0"
|
resolution: "supports-color@npm:7.2.0"
|
||||||
@ -24645,7 +24602,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tree-kill@npm:1.2.2, tree-kill@npm:^1.2.2":
|
"tree-kill@npm:^1.2.2":
|
||||||
version: 1.2.2
|
version: 1.2.2
|
||||||
resolution: "tree-kill@npm:1.2.2"
|
resolution: "tree-kill@npm:1.2.2"
|
||||||
bin:
|
bin:
|
||||||
@ -26314,7 +26271,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"yargs@npm:17.7.2, yargs@npm:^17.0.1, yargs@npm:^17.5.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2":
|
"yargs@npm:^17.0.1, yargs@npm:^17.5.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2":
|
||||||
version: 17.7.2
|
version: 17.7.2
|
||||||
resolution: "yargs@npm:17.7.2"
|
resolution: "yargs@npm:17.7.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user