Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fb60c0f73 | |||
| c73cf3e6c2 |
@@ -1,4 +1,4 @@
|
||||
name: 🐛 Bug Report
|
||||
name: 🐛 Bug Report (English)
|
||||
description: Create a report to help us improve
|
||||
title: '[Bug]: '
|
||||
labels: ['BUG']
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: 💡 Feature Request
|
||||
name: 💡 Feature Request (English)
|
||||
description: Suggest an idea for this project
|
||||
title: '[Feature]: '
|
||||
labels: ['feature']
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: 🤔 Other Questions
|
||||
name: 🤔 Other Questions (English)
|
||||
description: Submit questions that don't fit into bug reports or feature requests
|
||||
title: '[Other]: '
|
||||
body:
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
# 模块 → 路径匹配(globs)与 GitHub 审核人列表
|
||||
# 多模块命中时取优先级最高的为主类,其余在卡片中显示“涉及模块”
|
||||
|
||||
categories:
|
||||
ai_core:
|
||||
name: "AI Core"
|
||||
globs:
|
||||
- "packages/aiCore/**"
|
||||
- "src/renderer/src/aiCore/**"
|
||||
github_reviewers: ["DeJeune", "MyPrototypeWhat", "Vaayne"]
|
||||
|
||||
agent:
|
||||
name: "Agent"
|
||||
globs:
|
||||
- "packages/shared/agents/**"
|
||||
- "resources/data/agents-*.json"
|
||||
- "src/renderer/src/api/agent.ts"
|
||||
- "src/renderer/src/types/agent.ts"
|
||||
- "src/renderer/src/utils/agentSession.ts"
|
||||
- "src/renderer/src/services/db/AgentMessageDataSource.ts"
|
||||
- "src/renderer/src/hooks/agents/**"
|
||||
- "src/renderer/src/components/Popups/agent/**"
|
||||
- "src/renderer/src/pages/home/**/Agent*.tsx"
|
||||
- "src/renderer/src/pages/settings/AgentSettings/**"
|
||||
- "src/main/services/agents/**"
|
||||
- "src/main/apiServer/routes/agents/**"
|
||||
github_reviewers: ["EurFelux", "Vaayne", "DeJeune"]
|
||||
|
||||
provider:
|
||||
name: "Provider"
|
||||
globs:
|
||||
- "src/renderer/src/config/providers.ts"
|
||||
- "src/renderer/src/config/preprocessProviders.ts"
|
||||
- "src/renderer/src/config/webSearchProviders.ts"
|
||||
- "src/renderer/src/hooks/useWebSearchProviders.ts"
|
||||
- "src/renderer/src/providers/**"
|
||||
- "src/renderer/src/pages/settings/ProviderSettings/**"
|
||||
- "src/renderer/src/pages/settings/WebSearchSettings/**"
|
||||
- "src/renderer/src/pages/settings/DocProcessSettings/PreprocessProviderSettings.tsx"
|
||||
- "src/renderer/src/pages/settings/MCPSettings/providers/**"
|
||||
- "src/renderer/src/assets/images/providers/**"
|
||||
github_reviewers: ["YinsenHo", "kangfenmao", "alephpiece"]
|
||||
|
||||
backend:
|
||||
name: "后端/平台"
|
||||
globs:
|
||||
- "src/main/apiServer/**"
|
||||
- "src/main/services/**"
|
||||
- "src/main/*.ts"
|
||||
- "src/preload/**"
|
||||
- "src/main/mcpServers/**"
|
||||
github_reviewers: ["beyondkmp", "Vaayne", "kangfenmao"]
|
||||
|
||||
knowledge:
|
||||
name: "知识库"
|
||||
globs:
|
||||
- "src/main/knowledge/**"
|
||||
- "src/renderer/src/pages/knowledge/**"
|
||||
- "src/renderer/src/store/knowledge.ts"
|
||||
- "src/renderer/src/queue/KnowledgeQueue.ts"
|
||||
github_reviewers: ["eeee0717", "alephpiece", "GeorgeDong32"]
|
||||
|
||||
data_storage:
|
||||
name: "数据与存储"
|
||||
globs:
|
||||
- "src/renderer/src/databases/**"
|
||||
- "src/renderer/src/services/db/**"
|
||||
- "src/main/services/agents/database/**"
|
||||
- "resources/database/drizzle/**"
|
||||
- "src/renderer/src/store/migrate.ts"
|
||||
- "src/renderer/src/databases/upgrades.ts"
|
||||
github_reviewers: ["0xfullex", "kangfenmao", "Vaayne", "DeJeune"]
|
||||
|
||||
backup_export:
|
||||
name: "备份/导出"
|
||||
globs:
|
||||
- "src/renderer/src/components/*Backup*"
|
||||
- "src/renderer/src/components/Webdav*"
|
||||
- "src/renderer/src/components/ObsidianExportDialog.tsx"
|
||||
- "src/renderer/src/components/S3*"
|
||||
- "src/renderer/src/store/backup.ts"
|
||||
- "src/renderer/src/store/nutstore.ts"
|
||||
- "src/renderer/src/pages/settings/DataSettings/**"
|
||||
github_reviewers: ["beyondkmp", "GeorgeDong32"]
|
||||
|
||||
minapps:
|
||||
name: "小程序"
|
||||
globs:
|
||||
- "src/renderer/src/pages/minapps/**"
|
||||
- "src/renderer/src/store/minapps.ts"
|
||||
- "src/renderer/src/config/minapps.ts"
|
||||
github_reviewers: ["GeorgeDong32", "beyondkmp"]
|
||||
|
||||
chat:
|
||||
name: "对话"
|
||||
globs:
|
||||
- "src/renderer/src/pages/home/**"
|
||||
- "src/renderer/src/store/newMessage.ts"
|
||||
- "src/renderer/src/store/messageBlock.ts"
|
||||
- "src/renderer/src/store/memory.ts"
|
||||
- "src/renderer/src/store/llm.ts"
|
||||
github_reviewers: ["kangfenmao", "alephpiece", "EurFelux"]
|
||||
|
||||
draw:
|
||||
name: "绘图"
|
||||
globs:
|
||||
- "src/renderer/src/pages/paintings/**"
|
||||
- "src/renderer/src/store/paintings.ts"
|
||||
github_reviewers: ["EurFelux", "DeJeune"]
|
||||
|
||||
uiux:
|
||||
name: "UI/UX"
|
||||
globs:
|
||||
- "src/renderer/src/components/**"
|
||||
- "src/renderer/src/ui/**"
|
||||
- "src/renderer/src/assets/styles/**"
|
||||
- "src/renderer/src/windows/**"
|
||||
github_reviewers: ["kangfenmao", "MyPrototypeWhat", "alephpiece"]
|
||||
|
||||
build-config:
|
||||
name: "构建/配置"
|
||||
globs:
|
||||
- "package.json"
|
||||
- "tsconfig*.json"
|
||||
- "electron-builder.yml"
|
||||
- "electron.vite.config.ts"
|
||||
- "vitest.config.ts"
|
||||
- "playwright.config.ts"
|
||||
- ".github/workflows/**"
|
||||
- "scripts/**"
|
||||
github_reviewers: ["kangfenmao", "beyondkmp", "alephpiece"]
|
||||
|
||||
test:
|
||||
name: "测试"
|
||||
globs:
|
||||
- "tests/**"
|
||||
- "src/**/__tests__/**"
|
||||
- "scripts/__tests__/**"
|
||||
github_reviewers: ["alephpiece", "DeJeune", "EurFelux"]
|
||||
|
||||
docs:
|
||||
name: "文档"
|
||||
globs:
|
||||
- "docs/**"
|
||||
- "README*.md"
|
||||
- "SECURITY.md"
|
||||
- "CODE_OF_CONDUCT.md"
|
||||
- "AGENTS.md"
|
||||
github_reviewers: ["kangfenmao", "0xfullex", "EurFelux"]
|
||||
|
||||
rules:
|
||||
vendor_added:
|
||||
# 新增供应商时的强制审核人
|
||||
github_reviewers: ["YinsenHo"]
|
||||
large_change:
|
||||
# 重大变更阈值(改动文件数 > changed_files_gt 触发)
|
||||
changed_files_gt: 30
|
||||
github_reviewers: ["kangfenmao"]
|
||||
|
||||
|
||||
@@ -0,0 +1,455 @@
|
||||
{
|
||||
"generatedAt": "2025-10-29T06:19:19.098Z",
|
||||
"suggestions": {
|
||||
"ai_core": [
|
||||
{
|
||||
"github": "SuYao",
|
||||
"name": "SuYao",
|
||||
"email": "sy20010504@gmail.com",
|
||||
"commits": 33
|
||||
},
|
||||
{
|
||||
"github": "MyPrototypeWhat",
|
||||
"name": "MyPrototypeWhat",
|
||||
"email": "daoquqiexing@gmail.com",
|
||||
"commits": 12
|
||||
},
|
||||
{
|
||||
"github": "Vaayne",
|
||||
"name": "Vaayne",
|
||||
"email": "liu.vaayne@gmail.com",
|
||||
"commits": 10
|
||||
},
|
||||
{
|
||||
"github": "EurFelux",
|
||||
"name": "Phantom",
|
||||
"email": "59059173+EurFelux@users.noreply.github.com",
|
||||
"commits": 9
|
||||
},
|
||||
{
|
||||
"github": "kangfenmao",
|
||||
"name": "kangfenmao",
|
||||
"email": "kangfenmao@qq.com",
|
||||
"commits": 6
|
||||
}
|
||||
],
|
||||
"agent": [
|
||||
{
|
||||
"github": "icarus",
|
||||
"name": "icarus",
|
||||
"email": "eurfelux@gmail.com",
|
||||
"commits": 152
|
||||
},
|
||||
{
|
||||
"github": "Vaayne",
|
||||
"name": "Vaayne",
|
||||
"email": "liu.vaayne@gmail.com",
|
||||
"commits": 80
|
||||
},
|
||||
{
|
||||
"github": "suyao",
|
||||
"name": "suyao",
|
||||
"email": "sy20010504@gmail.com",
|
||||
"commits": 29
|
||||
},
|
||||
{
|
||||
"github": "defi-failure",
|
||||
"name": "defi-failure",
|
||||
"email": "159208748+defi-failure@users.noreply.github.com",
|
||||
"commits": 8
|
||||
},
|
||||
{
|
||||
"github": "Phantom",
|
||||
"name": "Phantom",
|
||||
"email": "eurfelux@gmail.com",
|
||||
"commits": 5
|
||||
}
|
||||
],
|
||||
"provider": [
|
||||
{
|
||||
"github": "kangfenmao",
|
||||
"name": "kangfenmao",
|
||||
"email": "kangfenmao@qq.com",
|
||||
"commits": 53
|
||||
},
|
||||
{
|
||||
"github": "one",
|
||||
"name": "one",
|
||||
"email": "wangan.cs@gmail.com",
|
||||
"commits": 32
|
||||
},
|
||||
{
|
||||
"github": "EurFelux",
|
||||
"name": "Phantom",
|
||||
"email": "59059173+EurFelux@users.noreply.github.com",
|
||||
"commits": 30
|
||||
},
|
||||
{
|
||||
"github": "SuYao",
|
||||
"name": "SuYao",
|
||||
"email": "sy20010504@gmail.com",
|
||||
"commits": 14
|
||||
},
|
||||
{
|
||||
"github": "eeee0717",
|
||||
"name": "Chen Tao",
|
||||
"email": "70054568+eeee0717@users.noreply.github.com",
|
||||
"commits": 10
|
||||
}
|
||||
],
|
||||
"backend": [
|
||||
{
|
||||
"github": "beyondkmp",
|
||||
"name": "beyondkmp",
|
||||
"email": "beyondkmp@gmail.com",
|
||||
"commits": 99
|
||||
},
|
||||
{
|
||||
"github": "Vaayne",
|
||||
"name": "Vaayne",
|
||||
"email": "liu.vaayne@gmail.com",
|
||||
"commits": 96
|
||||
},
|
||||
{
|
||||
"github": "kangfenmao",
|
||||
"name": "kangfenmao",
|
||||
"email": "kangfenmao@qq.com",
|
||||
"commits": 84
|
||||
},
|
||||
{
|
||||
"github": "0xfullex",
|
||||
"name": "fullex",
|
||||
"email": "106392080+0xfullex@users.noreply.github.com",
|
||||
"commits": 49
|
||||
},
|
||||
{
|
||||
"github": "vaayne",
|
||||
"name": "LiuVaayne",
|
||||
"email": "10231735+vaayne@users.noreply.github.com",
|
||||
"commits": 33
|
||||
}
|
||||
],
|
||||
"knowledge": [
|
||||
{
|
||||
"github": "kangfenmao",
|
||||
"name": "kangfenmao",
|
||||
"email": "kangfenmao@qq.com",
|
||||
"commits": 20
|
||||
},
|
||||
{
|
||||
"github": "eeee0717",
|
||||
"name": "Chen Tao",
|
||||
"email": "70054568+eeee0717@users.noreply.github.com",
|
||||
"commits": 13
|
||||
},
|
||||
{
|
||||
"github": "one",
|
||||
"name": "one",
|
||||
"email": "wangan.cs@gmail.com",
|
||||
"commits": 8
|
||||
},
|
||||
{
|
||||
"github": "EurFelux",
|
||||
"name": "Phantom",
|
||||
"email": "59059173+EurFelux@users.noreply.github.com",
|
||||
"commits": 6
|
||||
},
|
||||
{
|
||||
"github": "beyondkmp",
|
||||
"name": "beyondkmp",
|
||||
"email": "beyondkmp@gmail.com",
|
||||
"commits": 5
|
||||
}
|
||||
],
|
||||
"data_storage": [
|
||||
{
|
||||
"github": "kangfenmao",
|
||||
"name": "kangfenmao",
|
||||
"email": "kangfenmao@qq.com",
|
||||
"commits": 63
|
||||
},
|
||||
{
|
||||
"github": "Vaayne",
|
||||
"name": "Vaayne",
|
||||
"email": "liu.vaayne@gmail.com",
|
||||
"commits": 21
|
||||
},
|
||||
{
|
||||
"github": "SuYao",
|
||||
"name": "SuYao",
|
||||
"email": "sy20010504@gmail.com",
|
||||
"commits": 20
|
||||
},
|
||||
{
|
||||
"github": "EurFelux",
|
||||
"name": "Phantom",
|
||||
"email": "59059173+EurFelux@users.noreply.github.com",
|
||||
"commits": 17
|
||||
},
|
||||
{
|
||||
"github": "suyao",
|
||||
"name": "suyao",
|
||||
"email": "sy20010504@gmail.com",
|
||||
"commits": 13
|
||||
}
|
||||
],
|
||||
"backup_export": [
|
||||
{
|
||||
"github": "kangfenmao",
|
||||
"name": "kangfenmao",
|
||||
"email": "kangfenmao@qq.com",
|
||||
"commits": 23
|
||||
},
|
||||
{
|
||||
"github": "beyondkmp",
|
||||
"name": "beyondkmp",
|
||||
"email": "beyondkmp@gmail.com",
|
||||
"commits": 12
|
||||
},
|
||||
{
|
||||
"github": "GeorgeDong32",
|
||||
"name": "George·Dong",
|
||||
"email": "98630204+GeorgeDong32@users.noreply.github.com",
|
||||
"commits": 9
|
||||
},
|
||||
{
|
||||
"github": "one",
|
||||
"name": "one",
|
||||
"email": "wangan.cs@gmail.com",
|
||||
"commits": 5
|
||||
},
|
||||
{
|
||||
"github": "0xfullex",
|
||||
"name": "fullex",
|
||||
"email": "106392080+0xfullex@users.noreply.github.com",
|
||||
"commits": 5
|
||||
}
|
||||
],
|
||||
"minapps": [
|
||||
{
|
||||
"github": "kangfenmao",
|
||||
"name": "kangfenmao",
|
||||
"email": "kangfenmao@qq.com",
|
||||
"commits": 12
|
||||
},
|
||||
{
|
||||
"github": "beyondkmp",
|
||||
"name": "beyondkmp",
|
||||
"email": "beyondkmp@gmail.com",
|
||||
"commits": 5
|
||||
},
|
||||
{
|
||||
"github": "GeorgeDong32",
|
||||
"name": "George·Dong",
|
||||
"email": "98630204+GeorgeDong32@users.noreply.github.com",
|
||||
"commits": 4
|
||||
},
|
||||
{
|
||||
"github": "EurFelux",
|
||||
"name": "Phantom",
|
||||
"email": "59059173+EurFelux@users.noreply.github.com",
|
||||
"commits": 4
|
||||
},
|
||||
{
|
||||
"github": "0xfullex",
|
||||
"name": "fullex",
|
||||
"email": "106392080+0xfullex@users.noreply.github.com",
|
||||
"commits": 3
|
||||
}
|
||||
],
|
||||
"chat": [
|
||||
{
|
||||
"github": "kangfenmao",
|
||||
"name": "kangfenmao",
|
||||
"email": "kangfenmao@qq.com",
|
||||
"commits": 189
|
||||
},
|
||||
{
|
||||
"github": "one",
|
||||
"name": "one",
|
||||
"email": "wangan.cs@gmail.com",
|
||||
"commits": 86
|
||||
},
|
||||
{
|
||||
"github": "icarus",
|
||||
"name": "icarus",
|
||||
"email": "eurfelux@gmail.com",
|
||||
"commits": 85
|
||||
},
|
||||
{
|
||||
"github": "EurFelux",
|
||||
"name": "Phantom",
|
||||
"email": "59059173+EurFelux@users.noreply.github.com",
|
||||
"commits": 52
|
||||
},
|
||||
{
|
||||
"github": "SuYao",
|
||||
"name": "SuYao",
|
||||
"email": "sy20010504@gmail.com",
|
||||
"commits": 48
|
||||
}
|
||||
],
|
||||
"draw": [
|
||||
{
|
||||
"github": "kangfenmao",
|
||||
"name": "kangfenmao",
|
||||
"email": "kangfenmao@qq.com",
|
||||
"commits": 8
|
||||
},
|
||||
{
|
||||
"github": "jin-wang-c",
|
||||
"name": "Caelan",
|
||||
"email": "79105826+jin-wang-c@users.noreply.github.com",
|
||||
"commits": 7
|
||||
},
|
||||
{
|
||||
"github": "DDU1222",
|
||||
"name": "chenxue",
|
||||
"email": "DDU1222@users.noreply.github.com",
|
||||
"commits": 6
|
||||
},
|
||||
{
|
||||
"github": "EurFelux",
|
||||
"name": "Phantom",
|
||||
"email": "59059173+EurFelux@users.noreply.github.com",
|
||||
"commits": 5
|
||||
},
|
||||
{
|
||||
"github": "0xfullex",
|
||||
"name": "fullex",
|
||||
"email": "106392080+0xfullex@users.noreply.github.com",
|
||||
"commits": 4
|
||||
}
|
||||
],
|
||||
"uiux": [
|
||||
{
|
||||
"github": "kangfenmao",
|
||||
"name": "kangfenmao",
|
||||
"email": "kangfenmao@qq.com",
|
||||
"commits": 109
|
||||
},
|
||||
{
|
||||
"github": "one",
|
||||
"name": "one",
|
||||
"email": "wangan.cs@gmail.com",
|
||||
"commits": 89
|
||||
},
|
||||
{
|
||||
"github": "icarus",
|
||||
"name": "icarus",
|
||||
"email": "eurfelux@gmail.com",
|
||||
"commits": 35
|
||||
},
|
||||
{
|
||||
"github": "EurFelux",
|
||||
"name": "Phantom",
|
||||
"email": "59059173+EurFelux@users.noreply.github.com",
|
||||
"commits": 32
|
||||
},
|
||||
{
|
||||
"github": "0xfullex",
|
||||
"name": "fullex",
|
||||
"email": "106392080+0xfullex@users.noreply.github.com",
|
||||
"commits": 24
|
||||
}
|
||||
],
|
||||
"build-config": [
|
||||
{
|
||||
"github": "kangfenmao",
|
||||
"name": "kangfenmao",
|
||||
"email": "kangfenmao@qq.com",
|
||||
"commits": 170
|
||||
},
|
||||
{
|
||||
"github": "beyondkmp",
|
||||
"name": "beyondkmp",
|
||||
"email": "beyondkmp@gmail.com",
|
||||
"commits": 65
|
||||
},
|
||||
{
|
||||
"github": "one",
|
||||
"name": "one",
|
||||
"email": "wangan.cs@gmail.com",
|
||||
"commits": 40
|
||||
},
|
||||
{
|
||||
"github": "EurFelux",
|
||||
"name": "Phantom",
|
||||
"email": "59059173+EurFelux@users.noreply.github.com",
|
||||
"commits": 34
|
||||
},
|
||||
{
|
||||
"github": "SuYao",
|
||||
"name": "SuYao",
|
||||
"email": "sy20010504@gmail.com",
|
||||
"commits": 32
|
||||
}
|
||||
],
|
||||
"test": [
|
||||
{
|
||||
"github": "one",
|
||||
"name": "one",
|
||||
"email": "wangan.cs@gmail.com",
|
||||
"commits": 45
|
||||
},
|
||||
{
|
||||
"github": "SuYao",
|
||||
"name": "SuYao",
|
||||
"email": "sy20010504@gmail.com",
|
||||
"commits": 27
|
||||
},
|
||||
{
|
||||
"github": "EurFelux",
|
||||
"name": "Phantom",
|
||||
"email": "59059173+EurFelux@users.noreply.github.com",
|
||||
"commits": 20
|
||||
},
|
||||
{
|
||||
"github": "Vaayne",
|
||||
"name": "Vaayne",
|
||||
"email": "liu.vaayne@gmail.com",
|
||||
"commits": 19
|
||||
},
|
||||
{
|
||||
"github": "kangfenmao",
|
||||
"name": "kangfenmao",
|
||||
"email": "kangfenmao@qq.com",
|
||||
"commits": 18
|
||||
}
|
||||
],
|
||||
"docs": [
|
||||
{
|
||||
"github": "kangfenmao",
|
||||
"name": "kangfenmao",
|
||||
"email": "kangfenmao@qq.com",
|
||||
"commits": 18
|
||||
},
|
||||
{
|
||||
"github": "0xfullex",
|
||||
"name": "fullex",
|
||||
"email": "106392080+0xfullex@users.noreply.github.com",
|
||||
"commits": 7
|
||||
},
|
||||
{
|
||||
"github": "EurFelux",
|
||||
"name": "Phantom",
|
||||
"email": "59059173+EurFelux@users.noreply.github.com",
|
||||
"commits": 6
|
||||
},
|
||||
{
|
||||
"github": "one",
|
||||
"name": "one",
|
||||
"email": "wangan.cs@gmail.com",
|
||||
"commits": 6
|
||||
},
|
||||
{
|
||||
"github": "sunrise0o0",
|
||||
"name": "牡丹凤凰",
|
||||
"email": "87239270+sunrise0o0@users.noreply.github.com",
|
||||
"commits": 4
|
||||
}
|
||||
],
|
||||
"vendor_added": [],
|
||||
"large_change": []
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Auto I18N Weekly
|
||||
name: Auto I18N
|
||||
|
||||
env:
|
||||
TRANSLATION_API_KEY: ${{ secrets.TRANSLATE_API_KEY }}
|
||||
@@ -7,15 +7,14 @@ env:
|
||||
TRANSLATION_BASE_LOCALE: ${{ vars.AUTO_I18N_BASE_LOCALE || 'en-us'}}
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Runs at 00:00 UTC every Sunday.
|
||||
# This corresponds to 08:00 AM UTC+8 (Beijing time) every Sunday.
|
||||
- cron: "0 0 * * 0"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
auto-i18n:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == 'CherryHQ/cherry-studio'
|
||||
name: Auto I18N
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -25,69 +24,45 @@ jobs:
|
||||
- name: 🐈⬛ Checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- name: 📦 Setting Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 20
|
||||
package-manager-cache: false
|
||||
|
||||
- name: 📦 Install corepack
|
||||
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||
|
||||
- name: 📂 Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 💾 Cache yarn dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
node_modules
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: 📦 Install dependencies
|
||||
- name: 📦 Install dependencies in isolated directory
|
||||
run: |
|
||||
yarn install
|
||||
# 在临时目录安装依赖
|
||||
mkdir -p /tmp/translation-deps
|
||||
cd /tmp/translation-deps
|
||||
echo '{"dependencies": {"@cherrystudio/openai": "^6.5.0", "cli-progress": "^3.12.0", "tsx": "^4.20.3", "@biomejs/biome": "2.2.4"}}' > package.json
|
||||
npm install --no-package-lock
|
||||
|
||||
# 设置 NODE_PATH 让项目能找到这些依赖
|
||||
echo "NODE_PATH=/tmp/translation-deps/node_modules" >> $GITHUB_ENV
|
||||
|
||||
- name: 🏃♀️ Translate
|
||||
run: yarn sync:i18n && yarn auto:i18n
|
||||
run: npx tsx scripts/sync-i18n.ts && npx tsx scripts/auto-translate-i18n.ts
|
||||
|
||||
- name: 🔍 Format
|
||||
run: yarn format
|
||||
run: cd /tmp/translation-deps && npx biome format --config-path /home/runner/work/cherry-studio/cherry-studio/biome.jsonc --write /home/runner/work/cherry-studio/cherry-studio/src/renderer/src/i18n/
|
||||
|
||||
- name: 🔍 Check for changes
|
||||
id: git_status
|
||||
- name: 🔄 Commit changes
|
||||
run: |
|
||||
# Check if there are any uncommitted changes
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git add .
|
||||
git reset -- package.json yarn.lock # 不提交 package.json 和 yarn.lock 的更改
|
||||
git diff --exit-code --quiet || echo "::set-output name=has_changes::true"
|
||||
git status --porcelain
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes to commit"
|
||||
else
|
||||
git commit -m "fix(i18n): Auto update translations for PR #${{ github.event.pull_request.number }}"
|
||||
fi
|
||||
|
||||
- name: 📅 Set current date for PR title
|
||||
id: set_date
|
||||
run: echo "CURRENT_DATE=$(date +'%b %d, %Y')" >> $GITHUB_ENV # e.g., "Jun 06, 2024"
|
||||
|
||||
- name: 🚀 Create Pull Request if changes exist
|
||||
if: steps.git_status.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
- name: 🚀 Push changes
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }} # Use the built-in GITHUB_TOKEN for bot actions
|
||||
commit-message: "feat(bot): Weekly automated script run"
|
||||
title: "🤖 Weekly Automated Update: ${{ env.CURRENT_DATE }}"
|
||||
body: |
|
||||
This PR includes changes generated by the weekly auto i18n.
|
||||
Review the changes before merging.
|
||||
|
||||
---
|
||||
_Generated by the automated weekly workflow_
|
||||
branch: "auto-i18n-weekly-${{ github.run_id }}" # Unique branch name
|
||||
base: "main" # Or 'develop', set your base branch
|
||||
delete-branch: true # Delete the branch after merging or closing the PR
|
||||
|
||||
- name: 📢 Notify if no changes
|
||||
if: steps.git_status.outputs.has_changes != 'true'
|
||||
run: echo "Bot script ran, but no changes were detected. No PR created."
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
@@ -5,7 +5,7 @@ on:
|
||||
types: [opened]
|
||||
schedule:
|
||||
# Run every day at 8:30 Beijing Time (00:30 UTC)
|
||||
- cron: "30 0 * * *"
|
||||
- cron: '30 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -54,9 +54,9 @@ jobs:
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.check_time.outputs.should_delay == 'false'
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: '20'
|
||||
|
||||
- name: Process issue with Claude
|
||||
if: steps.check_time.outputs.should_delay == 'false'
|
||||
@@ -121,9 +121,9 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: '20'
|
||||
|
||||
- name: Process pending issues with Claude
|
||||
uses: anthropics/claude-code-action@main
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
name: GitHub PR Tracker with Feishu Notification
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, ready_for_review, review_requested, reopened]
|
||||
schedule:
|
||||
# Run every day at 8:30 Beijing Time (00:30 UTC)
|
||||
- cron: '30 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
process-new-pr:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check PR conditions
|
||||
id: check_pr
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
|
||||
// Check if PR is draft
|
||||
if (pr.draft) {
|
||||
console.log('⏭️ PR is in draft state, skipping notification');
|
||||
core.setOutput('should_notify', 'false');
|
||||
core.setOutput('skip_reason', 'draft');
|
||||
return;
|
||||
}
|
||||
|
||||
// We will notify regardless of whether reviewers/assignees are set
|
||||
console.log('✅ PR meets notification criteria');
|
||||
core.setOutput('should_notify', 'true');
|
||||
|
||||
// Prepare reviewer and assignee lists
|
||||
const reviewers = (pr.requested_reviewers || []).map(r => r.login).join(',');
|
||||
const assignees = (pr.assignees || []).map(a => a.login).join(',');
|
||||
|
||||
core.setOutput('reviewers', reviewers);
|
||||
core.setOutput('assignees', assignees);
|
||||
|
||||
- name: Check Beijing Time
|
||||
if: steps.check_pr.outputs.should_notify == 'true'
|
||||
id: check_time
|
||||
run: |
|
||||
# Get current time in Beijing timezone (UTC+8)
|
||||
BEIJING_HOUR=$(TZ='Asia/Shanghai' date +%H)
|
||||
BEIJING_MINUTE=$(TZ='Asia/Shanghai' date +%M)
|
||||
|
||||
echo "Beijing Time: ${BEIJING_HOUR}:${BEIJING_MINUTE}"
|
||||
|
||||
# Check if time is between 00:00 and 08:30
|
||||
if [ $BEIJING_HOUR -lt 8 ] || ([ $BEIJING_HOUR -eq 8 ] && [ $BEIJING_MINUTE -le 30 ]); then
|
||||
echo "should_delay=true" >> $GITHUB_OUTPUT
|
||||
echo "⏰ PR created during quiet hours (00:00-08:30 Beijing Time)"
|
||||
echo "Will schedule notification for 08:30"
|
||||
else
|
||||
echo "should_delay=false" >> $GITHUB_OUTPUT
|
||||
echo "✅ PR created during active hours, will notify immediately"
|
||||
fi
|
||||
|
||||
- name: Add pending label if in quiet hours
|
||||
if: steps.check_pr.outputs.should_notify == 'true' && steps.check_time.outputs.should_delay == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pr.number,
|
||||
labels: ['pending-feishu-pr-notification']
|
||||
});
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.check_pr.outputs.should_notify == 'true' && steps.check_time.outputs.should_delay == 'false'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Process PR with Claude
|
||||
if: steps.check_pr.outputs.should_notify == 'true' && steps.check_time.outputs.should_delay == 'false'
|
||||
uses: anthropics/claude-code-action@main
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
allowed_non_write_users: "*"
|
||||
anthropic_api_key: ${{ secrets.CLAUDE_TRANSLATOR_APIKEY }}
|
||||
claude_args: "--allowed-tools Bash(gh pr:*),Bash(node scripts/feishu-pr-notify.js)"
|
||||
prompt: |
|
||||
你是一个GitHub Pull Request自动化处理助手。请完成以下任务:
|
||||
|
||||
## 当前PR信息
|
||||
- PR编号:#${{ github.event.pull_request.number }}
|
||||
- 标题:${{ github.event.pull_request.title }}
|
||||
- 作者:${{ github.event.pull_request.user.login }}
|
||||
- URL:${{ github.event.pull_request.html_url }}
|
||||
- 内容:${{ github.event.pull_request.body }}
|
||||
- 标签:${{ join(github.event.pull_request.labels.*.name, ', ') }}
|
||||
- 改动文件数:${{ github.event.pull_request.changed_files }}
|
||||
- 增加行数:${{ github.event.pull_request.additions }}
|
||||
- 删除行数:${{ github.event.pull_request.deletions }}
|
||||
- Reviewers:${{ steps.check_pr.outputs.reviewers }}
|
||||
- Assignees:${{ steps.check_pr.outputs.assignees }}
|
||||
|
||||
## 任务步骤
|
||||
|
||||
1. **分析PR改动内容**
|
||||
首先使用以下命令获取PR的文件变更列表:
|
||||
```bash
|
||||
gh pr view ${{ github.event.pull_request.number }} --json files --jq '.files[].path'
|
||||
```
|
||||
|
||||
2. **分类PR内容**
|
||||
根据改动的文件路径和PR标题、描述,判断PR的类型(若命中多个模块则输出 `multiple`,若均未命中则 `other`):
|
||||
- chat(对话): src/renderer/src/pages/home/**, src/renderer/src/store/(newMessage|messageBlock|memory).ts
|
||||
- draw(绘图): src/renderer/src/pages/paintings/**, src/renderer/src/store/paintings.ts
|
||||
- uiux(UI/UX): src/renderer/src/components/**, src/renderer/src/ui/**, src/renderer/src/assets/styles/**, src/renderer/src/windows/**
|
||||
- knowledge(知识库): src/main/knowledge/**, src/renderer/src/pages/knowledge/**, src/renderer/src/store/knowledge.ts, src/renderer/src/queue/KnowledgeQueue.ts
|
||||
- minapps(小程序): src/renderer/src/pages/minapps/**, src/renderer/src/store/minapps.ts, src/renderer/src/config/minapps.ts
|
||||
- backup_export(备份/导出): src/renderer/src/components/*Backup*, src/renderer/src/components/Webdav*, src/renderer/src/components/ObsidianExportDialog.tsx, src/renderer/src/components/S3*, src/renderer/src/store/(backup|nutstore).ts
|
||||
- data_storage(数据与存储): src/renderer/src/databases/**, src/renderer/src/services/db/**, src/main/services/agents/database/**, resources/database/drizzle/**, src/renderer/src/store/migrate.ts, src/renderer/src/databases/upgrades.ts
|
||||
- ai_core(AI基础设施): packages/aiCore/**, src/renderer/src/aiCore/**
|
||||
- backend(后端/平台): src/main/apiServer/**, src/main/services/**, src/main/*.ts, src/preload/**, src/main/mcpServers/**
|
||||
- agent(Agent): packages/shared/agents/**, resources/data/agents-*.json, src/renderer/src/api/agent.ts, src/renderer/src/types/agent.ts, src/renderer/src/utils/agentSession.ts, src/renderer/src/services/db/AgentMessageDataSource.ts, src/renderer/src/hooks/agents/**, src/renderer/src/components/Popups/agent/**, src/renderer/src/pages/home/**/Agent*.tsx, src/renderer/src/pages/settings/AgentSettings/**, src/main/services/agents/**, src/main/apiServer/routes/agents/**
|
||||
- provider(Provider): src/renderer/src/config/(providers|preprocessProviders|webSearchProviders).ts, src/renderer/src/hooks/useWebSearchProviders.ts, src/renderer/src/providers/**, src/renderer/src/pages/settings/ProviderSettings/**, src/renderer/src/pages/settings/WebSearchSettings/**, src/renderer/src/pages/settings/DocProcessSettings/(OcrProviderSettings|PreprocessProviderSettings).tsx, src/renderer/src/pages/settings/MCPSettings/providers/**, src/renderer/src/assets/images/providers/**, src/main/services/urlschema/handle-providers.ts
|
||||
- build-config(构建/配置): package.json, tsconfig*.json, electron-builder.yml, electron.vite.config.ts, vitest.config.ts, playwright.config.ts, .github/workflows/**, scripts/**
|
||||
- test(测试): tests/**, src/**/__tests__/**, scripts/__tests__/**
|
||||
- docs(文档): docs/**, README*.md, SECURITY.md, CODE_OF_CONDUCT.md, AGENTS.md
|
||||
|
||||
2.1 **识别是否“新增供应商”**
|
||||
满足以下任一条件则视为“新增供应商”并设置变量 PR_VENDOR_ADDED=true,否则为 false:
|
||||
- 改动文件包含:
|
||||
- src/renderer/src/config/providers.ts
|
||||
- src/renderer/src/providers/**
|
||||
- packages/aiCore/**/provider/** 或 packages/aiCore/src/provider/**
|
||||
- resources/data/agents-*.json
|
||||
- 或 PR 标题/描述包含关键词:"供应商"、"厂商"、"provider"、"新增"、"集成"
|
||||
|
||||
3. **总结PR**
|
||||
用中文(简体)提供简洁的总结(2-3句话),包括:
|
||||
- PR的主要改动内容
|
||||
- 核心功能或修复
|
||||
- 重要的技术细节或影响范围
|
||||
|
||||
4. **发送飞书通知**
|
||||
使用以下命令发送飞书通知:
|
||||
```bash
|
||||
PR_URL="${{ github.event.pull_request.html_url }}" \
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}" \
|
||||
PR_TITLE="${{ github.event.pull_request.title }}" \
|
||||
PR_AUTHOR="${{ github.event.pull_request.user.login }}" \
|
||||
PR_LABELS="${{ join(github.event.pull_request.labels.*.name, ',') }}" \
|
||||
PR_SUMMARY="<你生成的中文总结>" \
|
||||
PR_REVIEWERS="${{ steps.check_pr.outputs.reviewers }}" \
|
||||
PR_ASSIGNEES="${{ steps.check_pr.outputs.assignees }}" \
|
||||
PR_CATEGORY="<你判断的PR类型>" \
|
||||
PR_VENDOR_ADDED="<true 或 false>" \
|
||||
PR_CHANGED_FILES="${{ github.event.pull_request.changed_files }}" \
|
||||
PR_ADDITIONS="${{ github.event.pull_request.additions }}" \
|
||||
PR_DELETIONS="${{ github.event.pull_request.deletions }}" \
|
||||
node scripts/feishu-pr-notify.js
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
- 总结必须使用简体中文
|
||||
- PR_SUMMARY 和其他参数在传递时需要正确转义特殊字符
|
||||
- PR_CATEGORY 必须是上述定义的类型之一
|
||||
- 如果PR内容为空,也要提供一个简短的说明
|
||||
|
||||
请开始执行任务!
|
||||
env:
|
||||
ANTHROPIC_BASE_URL: ${{ secrets.CLAUDE_TRANSLATOR_BASEURL }}
|
||||
FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }}
|
||||
FEISHU_WEBHOOK_SECRET: ${{ secrets.FEISHU_WEBHOOK_SECRET }}
|
||||
FEISHU_USER_MAPPING: ${{ secrets.FEISHU_USER_MAPPING }}
|
||||
|
||||
process-pending-prs:
|
||||
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Process pending PRs with Claude
|
||||
uses: anthropics/claude-code-action@main
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.CLAUDE_TRANSLATOR_APIKEY }}
|
||||
allowed_non_write_users: "*"
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
claude_args: "--allowed-tools Bash(gh pr:*),Bash(gh api:*),Bash(node scripts/feishu-pr-notify.js)"
|
||||
prompt: |
|
||||
你是一个GitHub Pull Request自动化处理助手。请完成以下任务:
|
||||
|
||||
## 任务说明
|
||||
处理所有待发送飞书通知的GitHub PRs(标记为 `pending-feishu-pr-notification` 的PRs)
|
||||
|
||||
## 步骤
|
||||
|
||||
1. **获取待处理的PRs**
|
||||
使用以下命令获取所有带 `pending-feishu-pr-notification` 标签的PRs:
|
||||
```bash
|
||||
gh api repos/${{ github.repository }}/pulls?state=open | jq '.[] | select(.labels[]?.name == "pending-feishu-pr-notification")'
|
||||
```
|
||||
|
||||
2. **验证PR条件**
|
||||
对于每个PR,检查:
|
||||
- 是否仍然不是draft状态
|
||||
- 是否有reviewers或assignees
|
||||
- 如果不满足条件,移除标签并跳过
|
||||
|
||||
3. **分析和分类PR**
|
||||
获取PR的文件变更:
|
||||
```bash
|
||||
gh pr view <PR编号> --json files --jq '.files[].path'
|
||||
```
|
||||
|
||||
根据文件路径判断PR类型(chat/draw/uiux/knowledge/minapps/backup_export/data_storage/ai_core/backend/agent/provider/docs/build-config/test/multiple/other)
|
||||
|
||||
4. **总结每个PR**
|
||||
用中文提供简洁的总结(2-3句话),包括:
|
||||
- PR的主要改动内容
|
||||
- 核心功能或修复
|
||||
- 重要的技术细节
|
||||
|
||||
5. **发送飞书通知**
|
||||
使用以下命令发送通知:
|
||||
```bash
|
||||
PR_URL="<PR的html_url>" \
|
||||
PR_NUMBER="<PR编号>" \
|
||||
PR_TITLE="<PR标题>" \
|
||||
PR_AUTHOR="<PR作者>" \
|
||||
PR_LABELS="<逗号分隔的标签列表,排除pending-feishu-pr-notification>" \
|
||||
PR_SUMMARY="<你生成的中文总结>" \
|
||||
PR_REVIEWERS="<reviewers列表,逗号分隔>" \
|
||||
PR_ASSIGNEES="<assignees列表,逗号分隔>" \
|
||||
PR_CATEGORY="<PR类型>" \
|
||||
PR_VENDOR_ADDED="<true 或 false>" \
|
||||
PR_CHANGED_FILES="<改动文件数>" \
|
||||
PR_ADDITIONS="<增加行数>" \
|
||||
PR_DELETIONS="<删除行数>" \
|
||||
node scripts/feishu-pr-notify.js
|
||||
```
|
||||
|
||||
6. **移除标签**
|
||||
成功发送后,移除标签:
|
||||
```bash
|
||||
gh api -X DELETE repos/${{ github.repository }}/issues/<PR编号>/labels/pending-feishu-pr-notification
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
- Repository: ${{ github.repository }}
|
||||
- Feishu webhook URL和密钥已配置
|
||||
|
||||
## 注意事项
|
||||
- 如果没有待处理的PRs,输出提示后结束
|
||||
- 处理多个PRs时,每个之间等待2-3秒
|
||||
- 某个PR失败不中断整个流程
|
||||
- 所有总结使用简体中文
|
||||
|
||||
请开始执行任务!
|
||||
env:
|
||||
ANTHROPIC_BASE_URL: ${{ secrets.CLAUDE_TRANSLATOR_BASEURL }}
|
||||
FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }}
|
||||
FEISHU_WEBHOOK_SECRET: ${{ secrets.FEISHU_WEBHOOK_SECRET }}
|
||||
FEISHU_USER_MAPPING: ${{ secrets.FEISHU_USER_MAPPING }}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
contents: none
|
||||
steps:
|
||||
- name: Close needs-more-info issues
|
||||
uses: actions/stale@v10
|
||||
uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
only-labels: 'needs-more-info'
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
days-before-pr-close: -1
|
||||
|
||||
- name: Close inactive issues
|
||||
uses: actions/stale@v10
|
||||
uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: ${{ env.daysBeforeStale }}
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Nightly Build
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 17 * * *" # 1:00 BJ Time
|
||||
- cron: '0 17 * * *' # 1:00 BJ Time
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -56,9 +56,9 @@ jobs:
|
||||
ref: main
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 20
|
||||
|
||||
- name: macos-latest dependencies fix
|
||||
if: matrix.os == 'macos-latest'
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
brew install python-setuptools
|
||||
|
||||
- name: Install corepack
|
||||
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||
run: corepack enable && corepack prepare yarn@4.6.0 --activate
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
@@ -208,7 +208,7 @@ jobs:
|
||||
echo "总计: $(find renamed-artifacts -type f | wc -l) 个文件"
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cherry-studio-nightly-${{ steps.date.outputs.date }}-${{ matrix.os }}
|
||||
path: renamed-artifacts/*
|
||||
|
||||
@@ -24,12 +24,12 @@ jobs:
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 20
|
||||
|
||||
- name: Install corepack
|
||||
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||
run: corepack enable && corepack prepare yarn@4.6.0 --activate
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
|
||||
@@ -4,9 +4,9 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Release tag (e.g. v1.0.0)"
|
||||
description: 'Release tag (e.g. v1.0.0)'
|
||||
required: true
|
||||
default: "v1.0.0"
|
||||
default: 'v1.0.0'
|
||||
push:
|
||||
tags:
|
||||
- v*.*.*
|
||||
@@ -47,9 +47,9 @@ jobs:
|
||||
npm version "$VERSION" --no-git-tag-version --allow-same-version
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
node-version: 20
|
||||
|
||||
- name: macos-latest dependencies fix
|
||||
if: matrix.os == 'macos-latest'
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
brew install python-setuptools
|
||||
|
||||
- name: Install corepack
|
||||
run: corepack enable && corepack prepare yarn@4.9.1 --activate
|
||||
run: corepack enable && corepack prepare yarn@4.6.0 --activate
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
@@ -127,5 +127,5 @@ jobs:
|
||||
allowUpdates: true
|
||||
makeLatest: false
|
||||
tag: ${{ steps.get-tag.outputs.tag }}
|
||||
artifacts: "dist/*.exe,dist/*.zip,dist/*.dmg,dist/*.AppImage,dist/*.snap,dist/*.deb,dist/*.rpm,dist/*.tar.gz,dist/latest*.yml,dist/rc*.yml,dist/beta*.yml,dist/*.blockmap"
|
||||
artifacts: 'dist/*.exe,dist/*.zip,dist/*.dmg,dist/*.AppImage,dist/*.snap,dist/*.deb,dist/*.rpm,dist/*.tar.gz,dist/latest*.yml,dist/rc*.yml,dist/beta*.yml,dist/*.blockmap'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
+75
-11
@@ -22,6 +22,7 @@
|
||||
"eslint.config.mjs"
|
||||
],
|
||||
"overrides": [
|
||||
// set different env
|
||||
{
|
||||
"env": {
|
||||
"node": true
|
||||
@@ -35,7 +36,8 @@
|
||||
"files": [
|
||||
"src/renderer/**/*.{ts,tsx}",
|
||||
"packages/aiCore/**",
|
||||
"packages/extension-table-plus/**"
|
||||
"packages/extension-table-plus/**",
|
||||
"resources/js/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -53,16 +55,74 @@
|
||||
"files": ["src/preload/**"]
|
||||
}
|
||||
],
|
||||
// We don't use the React plugin here because its behavior differs slightly from that of ESLint's React plugin.
|
||||
"plugins": ["unicorn", "typescript", "oxc", "import"],
|
||||
"rules": {
|
||||
"constructor-super": "error",
|
||||
"for-direction": "error",
|
||||
"getter-return": "error",
|
||||
"no-array-constructor": "off",
|
||||
// "import/no-cycle": "error", // tons of error, bro
|
||||
"no-async-promise-executor": "error",
|
||||
"no-caller": "warn",
|
||||
"no-case-declarations": "error",
|
||||
"no-class-assign": "error",
|
||||
"no-compare-neg-zero": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-const-assign": "error",
|
||||
"no-constant-binary-expression": "error",
|
||||
"no-constant-condition": "error",
|
||||
"no-control-regex": "error",
|
||||
"no-debugger": "error",
|
||||
"no-delete-var": "error",
|
||||
"no-dupe-args": "error",
|
||||
"no-dupe-class-members": "error",
|
||||
"no-dupe-else-if": "error",
|
||||
"no-dupe-keys": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-empty": "error",
|
||||
"no-empty-character-class": "error",
|
||||
"no-empty-pattern": "error",
|
||||
"no-empty-static-block": "error",
|
||||
"no-eval": "warn",
|
||||
"no-ex-assign": "error",
|
||||
"no-extra-boolean-cast": "error",
|
||||
"no-fallthrough": "warn",
|
||||
"no-func-assign": "error",
|
||||
"no-global-assign": "error",
|
||||
"no-import-assign": "error",
|
||||
"no-invalid-regexp": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-loss-of-precision": "error",
|
||||
"no-misleading-character-class": "error",
|
||||
"no-new-native-nonconstructor": "error",
|
||||
"no-nonoctal-decimal-escape": "error",
|
||||
"no-obj-calls": "error",
|
||||
"no-octal": "error",
|
||||
"no-prototype-builtins": "error",
|
||||
"no-redeclare": "error",
|
||||
"no-regex-spaces": "error",
|
||||
"no-self-assign": "error",
|
||||
"no-setter-return": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-sparse-arrays": "error",
|
||||
"no-this-before-super": "error",
|
||||
"no-unassigned-vars": "warn",
|
||||
"no-unused-expressions": "off",
|
||||
"no-undef": "error",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-unreachable": "error",
|
||||
"no-unsafe-finally": "error",
|
||||
"no-unsafe-negation": "error",
|
||||
"no-unsafe-optional-chaining": "error",
|
||||
"no-unused-expressions": "off", // this rule disallow us to use expression to call function, like `condition && fn()`
|
||||
"no-unused-labels": "error",
|
||||
"no-unused-private-class-members": "error",
|
||||
"no-unused-vars": ["warn", { "caughtErrors": "none" }],
|
||||
"no-useless-backreference": "error",
|
||||
"no-useless-catch": "error",
|
||||
"no-useless-escape": "error",
|
||||
"no-useless-rename": "warn",
|
||||
"no-with": "error",
|
||||
"oxc/bad-array-method-on-arguments": "warn",
|
||||
"oxc/bad-char-at-comparison": "warn",
|
||||
"oxc/bad-comparison-sequence": "warn",
|
||||
@@ -74,17 +134,19 @@
|
||||
"oxc/erasing-op": "warn",
|
||||
"oxc/missing-throw": "warn",
|
||||
"oxc/number-arg-out-of-range": "warn",
|
||||
"oxc/only-used-in-recursion": "off",
|
||||
"oxc/only-used-in-recursion": "off", // manually off bacause of existing warning. may turn it on in the future
|
||||
"oxc/uninvoked-array-callback": "warn",
|
||||
"require-yield": "error",
|
||||
"typescript/await-thenable": "warn",
|
||||
"typescript/consistent-type-imports": "error",
|
||||
// "typescript/ban-ts-comment": "error",
|
||||
"typescript/no-array-constructor": "error",
|
||||
// "typescript/consistent-type-imports": "error",
|
||||
"typescript/no-array-delete": "warn",
|
||||
"typescript/no-base-to-string": "warn",
|
||||
"typescript/no-duplicate-enum-values": "error",
|
||||
"typescript/no-duplicate-type-constituents": "warn",
|
||||
"typescript/no-empty-object-type": "off",
|
||||
"typescript/no-explicit-any": "off",
|
||||
"typescript/no-explicit-any": "off", // not safe but too many errors
|
||||
"typescript/no-extra-non-null-assertion": "error",
|
||||
"typescript/no-floating-promises": "warn",
|
||||
"typescript/no-for-in-array": "warn",
|
||||
@@ -93,7 +155,7 @@
|
||||
"typescript/no-misused-new": "error",
|
||||
"typescript/no-misused-spread": "warn",
|
||||
"typescript/no-namespace": "error",
|
||||
"typescript/no-non-null-asserted-optional-chain": "off",
|
||||
"typescript/no-non-null-asserted-optional-chain": "off", // it's off now. but may turn it on.
|
||||
"typescript/no-redundant-type-constituents": "warn",
|
||||
"typescript/no-require-imports": "off",
|
||||
"typescript/no-this-alias": "error",
|
||||
@@ -111,18 +173,20 @@
|
||||
"typescript/triple-slash-reference": "error",
|
||||
"typescript/unbound-method": "warn",
|
||||
"unicorn/no-await-in-promise-methods": "warn",
|
||||
"unicorn/no-empty-file": "off",
|
||||
"unicorn/no-empty-file": "off", // manually off bacause of existing warning. may turn it on in the future
|
||||
"unicorn/no-invalid-fetch-options": "warn",
|
||||
"unicorn/no-invalid-remove-event-listener": "warn",
|
||||
"unicorn/no-new-array": "off",
|
||||
"unicorn/no-new-array": "off", // manually off bacause of existing warning. may turn it on in the future
|
||||
"unicorn/no-single-promise-in-promise-methods": "warn",
|
||||
"unicorn/no-thenable": "off",
|
||||
"unicorn/no-thenable": "off", // manually off bacause of existing warning. may turn it on in the future
|
||||
"unicorn/no-unnecessary-await": "warn",
|
||||
"unicorn/no-useless-fallback-in-spread": "warn",
|
||||
"unicorn/no-useless-length-check": "warn",
|
||||
"unicorn/no-useless-spread": "off",
|
||||
"unicorn/no-useless-spread": "off", // manually off bacause of existing warning. may turn it on in the future
|
||||
"unicorn/prefer-set-size": "warn",
|
||||
"unicorn/prefer-string-starts-ends-with": "warn"
|
||||
"unicorn/prefer-string-starts-ends-with": "warn",
|
||||
"use-isnan": "error",
|
||||
"valid-typeof": "error"
|
||||
},
|
||||
"settings": {
|
||||
"jsdoc": {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index cac044aab0255fa72f68b36ecd2c5b12d424c379..ad6ee8ecfc5cbc3ec43ba59a44eda21e8e4d353f 100644
|
||||
index 4cc66d83af1cef39f6447dc62e680251e05ddf9f..eb9819cb674c1808845ceb29936196c4bb355172 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -471,7 +471,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) {
|
||||
@@ -12,7 +12,7 @@ index cac044aab0255fa72f68b36ecd2c5b12d424c379..ad6ee8ecfc5cbc3ec43ba59a44eda21e
|
||||
|
||||
// src/google-generative-ai-options.ts
|
||||
diff --git a/dist/index.mjs b/dist/index.mjs
|
||||
index 0793085005d7968638d355f2f1e127939d965165..1c8bf852baf025d56dc35a0691eb95967de7e5c8 100644
|
||||
index a032505ec54e132dc386dde001dc51f710f84c83..5efada51b9a8b56e3f01b35e734908ebe3c37043 100644
|
||||
--- a/dist/index.mjs
|
||||
+++ b/dist/index.mjs
|
||||
@@ -477,7 +477,7 @@ function convertToGoogleGenerativeAIMessages(prompt, options) {
|
||||
+10
-8
@@ -1,5 +1,5 @@
|
||||
diff --git a/dist/index.js b/dist/index.js
|
||||
index 992c85ac6656e51c3471af741583533c5a7bf79f..83c05952a07aebb95fc6c62f9ddb8aa96b52ac0d 100644
|
||||
index cc6652c4e7f32878a64a2614115bf7eeb3b7c890..76e989017549c89b45d633525efb1f318026d9b2 100644
|
||||
--- a/dist/index.js
|
||||
+++ b/dist/index.js
|
||||
@@ -274,6 +274,7 @@ var openaiChatResponseSchema = (0, import_provider_utils3.lazyValidator)(
|
||||
@@ -18,29 +18,30 @@ index 992c85ac6656e51c3471af741583533c5a7bf79f..83c05952a07aebb95fc6c62f9ddb8aa9
|
||||
tool_calls: import_v42.z.array(
|
||||
import_v42.z.object({
|
||||
index: import_v42.z.number(),
|
||||
@@ -785,6 +787,13 @@ var OpenAIChatLanguageModel = class {
|
||||
@@ -785,6 +787,14 @@ var OpenAIChatLanguageModel = class {
|
||||
if (text != null && text.length > 0) {
|
||||
content.push({ type: "text", text });
|
||||
}
|
||||
+ const reasoning = choice.message.reasoning_content;
|
||||
+ const reasoning =
|
||||
+ choice.message.reasoning_content;
|
||||
+ if (reasoning != null && reasoning.length > 0) {
|
||||
+ content.push({
|
||||
+ type: 'reasoning',
|
||||
+ text: reasoning
|
||||
+ text: reasoning,
|
||||
+ });
|
||||
+ }
|
||||
for (const toolCall of (_a = choice.message.tool_calls) != null ? _a : []) {
|
||||
content.push({
|
||||
type: "tool-call",
|
||||
@@ -866,6 +875,7 @@ var OpenAIChatLanguageModel = class {
|
||||
@@ -866,6 +876,7 @@ var OpenAIChatLanguageModel = class {
|
||||
};
|
||||
let metadataExtracted = false;
|
||||
let isFirstChunk = true;
|
||||
let isActiveText = false;
|
||||
+ let isActiveReasoning = false;
|
||||
const providerMetadata = { openai: {} };
|
||||
return {
|
||||
stream: response.pipeThrough(
|
||||
@@ -923,6 +933,21 @@ var OpenAIChatLanguageModel = class {
|
||||
@@ -920,6 +931,22 @@ var OpenAIChatLanguageModel = class {
|
||||
return;
|
||||
}
|
||||
const delta = choice.delta;
|
||||
@@ -53,6 +54,7 @@ index 992c85ac6656e51c3471af741583533c5a7bf79f..83c05952a07aebb95fc6c62f9ddb8aa9
|
||||
+ });
|
||||
+ isActiveReasoning = true;
|
||||
+ }
|
||||
+
|
||||
+ controller.enqueue({
|
||||
+ type: 'reasoning-delta',
|
||||
+ id: 'reasoning-0',
|
||||
@@ -62,7 +64,7 @@ index 992c85ac6656e51c3471af741583533c5a7bf79f..83c05952a07aebb95fc6c62f9ddb8aa9
|
||||
if (delta.content != null) {
|
||||
if (!isActiveText) {
|
||||
controller.enqueue({ type: "text-start", id: "0" });
|
||||
@@ -1035,6 +1060,9 @@ var OpenAIChatLanguageModel = class {
|
||||
@@ -1032,6 +1059,9 @@ var OpenAIChatLanguageModel = class {
|
||||
}
|
||||
},
|
||||
flush(controller) {
|
||||
@@ -0,0 +1,71 @@
|
||||
diff --git a/dist/utils/tiktoken.cjs b/dist/utils/tiktoken.cjs
|
||||
index 973b0d0e75aeaf8de579419af31b879b32975413..f23c7caa8b9dc8bd404132725346a4786f6b278b 100644
|
||||
--- a/dist/utils/tiktoken.cjs
|
||||
+++ b/dist/utils/tiktoken.cjs
|
||||
@@ -1,25 +1,14 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.encodingForModel = exports.getEncoding = void 0;
|
||||
-const lite_1 = require("js-tiktoken/lite");
|
||||
const async_caller_js_1 = require("./async_caller.cjs");
|
||||
const cache = {};
|
||||
const caller = /* #__PURE__ */ new async_caller_js_1.AsyncCaller({});
|
||||
async function getEncoding(encoding) {
|
||||
- if (!(encoding in cache)) {
|
||||
- cache[encoding] = caller
|
||||
- .fetch(`https://tiktoken.pages.dev/js/${encoding}.json`)
|
||||
- .then((res) => res.json())
|
||||
- .then((data) => new lite_1.Tiktoken(data))
|
||||
- .catch((e) => {
|
||||
- delete cache[encoding];
|
||||
- throw e;
|
||||
- });
|
||||
- }
|
||||
- return await cache[encoding];
|
||||
+ throw new Error("TikToken Not implemented");
|
||||
}
|
||||
exports.getEncoding = getEncoding;
|
||||
async function encodingForModel(model) {
|
||||
- return getEncoding((0, lite_1.getEncodingNameForModel)(model));
|
||||
+ throw new Error("TikToken Not implemented");
|
||||
}
|
||||
exports.encodingForModel = encodingForModel;
|
||||
diff --git a/dist/utils/tiktoken.js b/dist/utils/tiktoken.js
|
||||
index 8e41ee6f00f2f9c7fa2c59fa2b2f4297634b97aa..aa5f314a6349ad0d1c5aea8631a56aad099176e0 100644
|
||||
--- a/dist/utils/tiktoken.js
|
||||
+++ b/dist/utils/tiktoken.js
|
||||
@@ -1,20 +1,9 @@
|
||||
-import { Tiktoken, getEncodingNameForModel, } from "js-tiktoken/lite";
|
||||
import { AsyncCaller } from "./async_caller.js";
|
||||
const cache = {};
|
||||
const caller = /* #__PURE__ */ new AsyncCaller({});
|
||||
export async function getEncoding(encoding) {
|
||||
- if (!(encoding in cache)) {
|
||||
- cache[encoding] = caller
|
||||
- .fetch(`https://tiktoken.pages.dev/js/${encoding}.json`)
|
||||
- .then((res) => res.json())
|
||||
- .then((data) => new Tiktoken(data))
|
||||
- .catch((e) => {
|
||||
- delete cache[encoding];
|
||||
- throw e;
|
||||
- });
|
||||
- }
|
||||
- return await cache[encoding];
|
||||
+ throw new Error("TikToken Not implemented");
|
||||
}
|
||||
export async function encodingForModel(model) {
|
||||
- return getEncoding(getEncodingNameForModel(model));
|
||||
+ throw new Error("TikToken Not implemented");
|
||||
}
|
||||
diff --git a/package.json b/package.json
|
||||
index 36072aecf700fca1bc49832a19be832eca726103..90b8922fba1c3d1b26f78477c891b07816d6238a 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -37,7 +37,6 @@
|
||||
"ansi-styles": "^5.0.0",
|
||||
"camelcase": "6",
|
||||
"decamelize": "1.2.0",
|
||||
- "js-tiktoken": "^1.0.12",
|
||||
"langsmith": ">=0.2.8 <0.4.0",
|
||||
"mustache": "^4.2.0",
|
||||
"p-queue": "^6.6.2",
|
||||
@@ -1,68 +0,0 @@
|
||||
diff --git a/dist/utils/tiktoken.cjs b/dist/utils/tiktoken.cjs
|
||||
index c5b41f121d2e3d24c3a4969e31fa1acffdcad3b9..ec724489dcae79ee6c61acf2d4d84bd19daef036 100644
|
||||
--- a/dist/utils/tiktoken.cjs
|
||||
+++ b/dist/utils/tiktoken.cjs
|
||||
@@ -1,6 +1,5 @@
|
||||
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
||||
const require_utils_async_caller = require('./async_caller.cjs');
|
||||
-const js_tiktoken_lite = require_rolldown_runtime.__toESM(require("js-tiktoken/lite"));
|
||||
|
||||
//#region src/utils/tiktoken.ts
|
||||
var tiktoken_exports = {};
|
||||
@@ -11,14 +10,10 @@ require_rolldown_runtime.__export(tiktoken_exports, {
|
||||
const cache = {};
|
||||
const caller = /* @__PURE__ */ new require_utils_async_caller.AsyncCaller({});
|
||||
async function getEncoding(encoding) {
|
||||
- if (!(encoding in cache)) cache[encoding] = caller.fetch(`https://tiktoken.pages.dev/js/${encoding}.json`).then((res) => res.json()).then((data) => new js_tiktoken_lite.Tiktoken(data)).catch((e) => {
|
||||
- delete cache[encoding];
|
||||
- throw e;
|
||||
- });
|
||||
- return await cache[encoding];
|
||||
+ throw new Error("TikToken Not implemented");
|
||||
}
|
||||
async function encodingForModel(model) {
|
||||
- return getEncoding((0, js_tiktoken_lite.getEncodingNameForModel)(model));
|
||||
+ throw new Error("TikToken Not implemented");
|
||||
}
|
||||
|
||||
//#endregion
|
||||
diff --git a/dist/utils/tiktoken.js b/dist/utils/tiktoken.js
|
||||
index 641acca03cb92f04a6fa5c9c31f1880ce635572e..707389970ad957aa0ff20ef37fa8dd2875be737c 100644
|
||||
--- a/dist/utils/tiktoken.js
|
||||
+++ b/dist/utils/tiktoken.js
|
||||
@@ -1,6 +1,5 @@
|
||||
import { __export } from "../_virtual/rolldown_runtime.js";
|
||||
import { AsyncCaller } from "./async_caller.js";
|
||||
-import { Tiktoken, getEncodingNameForModel } from "js-tiktoken/lite";
|
||||
|
||||
//#region src/utils/tiktoken.ts
|
||||
var tiktoken_exports = {};
|
||||
@@ -11,14 +10,10 @@ __export(tiktoken_exports, {
|
||||
const cache = {};
|
||||
const caller = /* @__PURE__ */ new AsyncCaller({});
|
||||
async function getEncoding(encoding) {
|
||||
- if (!(encoding in cache)) cache[encoding] = caller.fetch(`https://tiktoken.pages.dev/js/${encoding}.json`).then((res) => res.json()).then((data) => new Tiktoken(data)).catch((e) => {
|
||||
- delete cache[encoding];
|
||||
- throw e;
|
||||
- });
|
||||
- return await cache[encoding];
|
||||
+ throw new Error("TikToken Not implemented");
|
||||
}
|
||||
async function encodingForModel(model) {
|
||||
- return getEncoding(getEncodingNameForModel(model));
|
||||
+ throw new Error("TikToken Not implemented");
|
||||
}
|
||||
|
||||
//#endregion
|
||||
diff --git a/package.json b/package.json
|
||||
index a24f8fc61de58526051999260f2ebee5f136354b..e885359e8966e7730c51772533ce37e01edb3046 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -20,7 +20,6 @@
|
||||
"ansi-styles": "^5.0.0",
|
||||
"camelcase": "6",
|
||||
"decamelize": "1.2.0",
|
||||
- "js-tiktoken": "^1.0.12",
|
||||
"langsmith": "^0.3.64",
|
||||
"mustache": "^4.2.0",
|
||||
"p-queue": "^6.6.2",
|
||||
@@ -0,0 +1,19 @@
|
||||
diff --git a/dist/embeddings.js b/dist/embeddings.js
|
||||
index 1f8154be3e9c22442a915eb4b85fa6d2a21b0d0c..dc13ef4a30e6c282824a5357bcee9bd0ae222aab 100644
|
||||
--- a/dist/embeddings.js
|
||||
+++ b/dist/embeddings.js
|
||||
@@ -214,10 +214,12 @@ export class OpenAIEmbeddings extends Embeddings {
|
||||
* @returns Promise that resolves to an embedding for the document.
|
||||
*/
|
||||
async embedQuery(text) {
|
||||
+ const isBaiduCloud = this.clientConfig.baseURL.includes('baidubce.com')
|
||||
+ const input = this.stripNewLines ? text.replace(/\n/g, ' ') : text
|
||||
const params = {
|
||||
model: this.model,
|
||||
- input: this.stripNewLines ? text.replace(/\n/g, " ") : text,
|
||||
- };
|
||||
+ input: isBaiduCloud ? [input] : input
|
||||
+ }
|
||||
if (this.dimensions) {
|
||||
params.dimensions = this.dimensions;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
diff --git a/dist/embeddings.js b/dist/embeddings.js
|
||||
index 6f4b928d3e4717309382e1b5c2e31ab5bc6c5af0..bc79429c88a6d27d4997a2740c4d8ae0707f5991 100644
|
||||
--- a/dist/embeddings.js
|
||||
+++ b/dist/embeddings.js
|
||||
@@ -94,9 +94,11 @@ var OpenAIEmbeddings = class extends Embeddings {
|
||||
* @returns Promise that resolves to an embedding for the document.
|
||||
*/
|
||||
async embedQuery(text) {
|
||||
+ const isBaiduCloud = this.clientConfig.baseURL.includes('baidubce.com');
|
||||
+ const input = this.stripNewLines ? text.replace(/\n/g, " ") : text
|
||||
const params = {
|
||||
model: this.model,
|
||||
- input: this.stripNewLines ? text.replace(/\n/g, " ") : text
|
||||
+ input: isBaiduCloud ? [input] : input
|
||||
};
|
||||
if (this.dimensions) params.dimensions = this.dimensions;
|
||||
if (this.encodingFormat) params.encoding_format = this.encodingFormat;
|
||||
@@ -7,10 +7,12 @@ This file provides guidance to AI coding assistants when working with code in th
|
||||
- **Keep it clear**: Write code that is easy to read, maintain, and explain.
|
||||
- **Match the house style**: Reuse existing patterns, naming, and conventions.
|
||||
- **Search smart**: Prefer `ast-grep` for semantic queries; fall back to `rg`/`grep` when needed.
|
||||
- **Build with HeroUI**: Use HeroUI for every new UI component; never add `antd` or `styled-components`.
|
||||
- **Log centrally**: Route all logging through `loggerService` with the right context—no `console.log`.
|
||||
- **Research via subagent**: Lean on `subagent` for external docs, APIs, news, and references.
|
||||
- **Always propose before executing**: Before making any changes, clearly explain your planned approach and wait for explicit user approval to ensure alignment and prevent unwanted modifications.
|
||||
- **Write conventional commits**: Commit small, focused changes using Conventional Commit messages (e.g., `feat:`, `fix:`, `refactor:`, `docs:`).
|
||||
- **Write conventional commits with emoji**: Commit small, focused changes using emoji-prefixed Conventional Commit messages (e.g., `✨ feat:`, `🐛 fix:`, `♻️ refactor:`, `
|
||||
📝 docs:`).
|
||||
|
||||
## Development Commands
|
||||
|
||||
@@ -39,6 +41,7 @@ This file provides guidance to AI coding assistants when working with code in th
|
||||
- **Services** (`src/main/services/`): MCPService, KnowledgeService, WindowService, etc.
|
||||
- **Build System**: Electron-Vite with experimental rolldown-vite, yarn workspaces.
|
||||
- **State Management**: Redux Toolkit (`src/renderer/src/store/`) for predictable state.
|
||||
- **UI Components**: HeroUI (`@heroui/*`) for all new UI elements.
|
||||
|
||||
### Logging
|
||||
```typescript
|
||||
|
||||
@@ -82,7 +82,7 @@ Cherry Studio is a desktop client that supports multiple LLM providers, availabl
|
||||
1. **Diverse LLM Provider Support**:
|
||||
|
||||
- ☁️ Major LLM Cloud Services: OpenAI, Gemini, Anthropic, and more
|
||||
- 🔗 AI Web Service Integration: Claude, Perplexity, [Poe](https://poe.com/), and others
|
||||
- 🔗 AI Web Service Integration: Claude, Perplexity, Poe, and others
|
||||
- 💻 Local Model Support with Ollama, LM Studio
|
||||
|
||||
2. **AI Assistants & Conversations**:
|
||||
@@ -238,6 +238,10 @@ The Enterprise Edition addresses core challenges in team collaboration by centra
|
||||
|
||||
## ✨ Online Demo
|
||||
|
||||
> 🚧 **Public Beta Notice**
|
||||
>
|
||||
> The Enterprise Edition is currently in its early public beta stage, and we are actively iterating and optimizing its features. We are aware that it may not be perfectly stable yet. If you encounter any issues or have valuable suggestions during your trial, we would be very grateful if you could contact us via email to provide feedback.
|
||||
|
||||
**🔗 [Cherry Studio Enterprise](https://www.cherry-ai.com/enterprise)**
|
||||
|
||||
## Version Comparison
|
||||
@@ -245,7 +249,7 @@ The Enterprise Edition addresses core challenges in team collaboration by centra
|
||||
| Feature | Community Edition | Enterprise Edition |
|
||||
| :---------------- | :----------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Open Source** | ✅ Yes | ⭕️ Partially released to customers |
|
||||
| **Cost** | [AGPL-3.0 License](https://github.com/CherryHQ/cherry-studio?tab=AGPL-3.0-1-ov-file) | Buyout / Subscription Fee |
|
||||
| **Cost** | Free for Personal Use / Commercial License | Buyout / Subscription Fee |
|
||||
| **Admin Backend** | — | ● Centralized **Model** Access<br>● **Employee** Management<br>● Shared **Knowledge Base**<br>● **Access** Control<br>● **Data** Backup |
|
||||
| **Server** | — | ✅ Dedicated Private Deployment |
|
||||
|
||||
@@ -258,12 +262,8 @@ We believe the Enterprise Edition will become your team's AI productivity engine
|
||||
|
||||
# 🔗 Related Projects
|
||||
|
||||
- [new-api](https://github.com/QuantumNous/new-api): The next-generation LLM gateway and AI asset management system supports multiple languages.
|
||||
|
||||
- [one-api](https://github.com/songquanpeng/one-api): LLM API management and distribution system supporting mainstream models like OpenAI, Azure, and Anthropic. Features a unified API interface, suitable for key management and secondary distribution.
|
||||
|
||||
- [Poe](https://poe.com/): Poe gives you access to the best AI, all in one place. Explore GPT-5, Claude Opus 4.1, DeepSeek-R1, Veo 3, ElevenLabs, and millions of others.
|
||||
|
||||
- [ublacklist](https://github.com/iorate/ublacklist): Blocks specific sites from appearing in Google search results
|
||||
|
||||
# 🚀 Contributors
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"includes": ["**", "!**/.claude/**"],
|
||||
"includes": ["**"],
|
||||
"maxSize": 2097152
|
||||
},
|
||||
"formatter": {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"aliases": {
|
||||
"components": "@renderer/ui/third-party",
|
||||
"hooks": "@renderer/hooks",
|
||||
"lib": "@renderer/lib",
|
||||
"ui": "@renderer/ui",
|
||||
"utils": "@renderer/utils"
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"rsc": false,
|
||||
"style": "new-york",
|
||||
"tailwind": {
|
||||
"baseColor": "zinc",
|
||||
"config": "",
|
||||
"css": "src/renderer/src/assets/styles/tailwind.css",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"tsx": true
|
||||
}
|
||||
+2
-2
@@ -18,13 +18,13 @@ yarn
|
||||
|
||||
### Setup Node.js
|
||||
|
||||
Download and install [Node.js v22.x.x](https://nodejs.org/en/download)
|
||||
Download and install [Node.js v20.x.x](https://nodejs.org/en/download)
|
||||
|
||||
### Setup Yarn
|
||||
|
||||
```bash
|
||||
corepack enable
|
||||
corepack prepare yarn@4.9.1 --activate
|
||||
corepack prepare yarn@4.6.0 --activate
|
||||
```
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
@@ -11,8 +11,6 @@ The Test Plan is divided into the RC channel and the Beta channel, with the foll
|
||||
|
||||
Users can enable the "Test Plan" and select the version channel in the software's `Settings` > `About`. Please note that the versions in the "Test Plan" cannot guarantee data consistency, so be sure to back up your data before using them.
|
||||
|
||||
After enabling the RC channel or Beta channel, if a stable version is released, users will still be upgraded to the stable version.
|
||||
|
||||
Users are welcome to submit issues or provide feedback through other channels for any bugs encountered during testing. Your feedback is very important to us.
|
||||
|
||||
## Developer Guide
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
|
||||
用户可以在软件的`设置`-`关于`中,开启“测试计划”并选择版本通道。请注意“测试计划”的版本无法保证数据的一致性,请使用前一定要备份数据。
|
||||
|
||||
用户选择RC版通道或Beta版通道后,若发布了正式版,仍旧会升级到正式版。
|
||||
|
||||
用户在测试过程中发现的BUG,欢迎提交issue或通过其他渠道反馈。用户的反馈对我们非常重要。
|
||||
|
||||
## 开发者指南
|
||||
|
||||
+42
-34
@@ -21,8 +21,6 @@ files:
|
||||
- "**/*"
|
||||
- "!**/{.vscode,.yarn,.yarn-lock,.github,.cursorrules,.prettierrc}"
|
||||
- "!electron.vite.config.{js,ts,mjs,cjs}}"
|
||||
- "!.*"
|
||||
- "!components.json"
|
||||
- "!**/{.eslintignore,.eslintrc.js,.eslintrc.json,.eslintcache,root.eslint.config.js,eslint.config.js,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,eslint.config.mjs,dev-app-update.yml,CHANGELOG.md,README.md,biome.jsonc}"
|
||||
- "!**/{.env,.env.*,.npmrc,pnpm-lock.yaml}"
|
||||
- "!**/{tsconfig.json,tsconfig.tsbuildinfo,tsconfig.node.json,tsconfig.web.json}"
|
||||
@@ -135,50 +133,60 @@ artifactBuildCompleted: scripts/artifact-build-completed.js
|
||||
releaseInfo:
|
||||
releaseNotes: |
|
||||
<!--LANG:en-->
|
||||
What's New in v1.7.0-beta.5
|
||||
What's New in v1.7.0-beta.2
|
||||
|
||||
New Features:
|
||||
- MCPRouter Provider: Added MCPRouter provider integration with token management and server synchronization
|
||||
- MCP Marketplace: Enhanced MCP server discovery and management with multi-provider marketplace support
|
||||
- Agent Permission Mode Display: Visual permission mode cards in empty session states
|
||||
- Assistant Subscription Settings: Added subscription URL management in assistant presets
|
||||
- Session Settings: Manage session-specific settings and model configurations independently
|
||||
- Notes Full-Text Search: Search across all notes with match highlighting
|
||||
- Built-in DiDi MCP Server: Integration with DiDi ride-hailing services (China only)
|
||||
- Intel OV OCR: Hardware-accelerated OCR using Intel NPU
|
||||
- Auto-start API Server: Automatically starts when agents exist
|
||||
|
||||
Improvements:
|
||||
- UI Optimization: Sidebar tooltip placement improved on macOS to avoid overlapping window controls
|
||||
- MCP Server Logos: Display server logos in Agent settings tooling section
|
||||
- Long Command Handling: Bash command tags now auto-truncate (hover to view full command for commands over 100 chars)
|
||||
- MCP OAuth Callback: Fixed callback page hanging and added multilingual support (10 languages)
|
||||
- Error Display: Improved error block display order for better readability
|
||||
- Plugin Browser: Centered tab alignment for better visual consistency
|
||||
- Agent model selection now requires explicit user choice
|
||||
- Added Mistral AI provider support
|
||||
- Added NewAPI generic provider support
|
||||
- Improved navbar layout consistency across different modes
|
||||
- Enhanced chat component responsiveness
|
||||
- Better code block display on small screens
|
||||
- Updated OVMS to 2025.3 official release
|
||||
- Added Greek language support
|
||||
|
||||
Bug Fixes:
|
||||
- Fixed Agent sessions not inheriting allowed_tools configuration
|
||||
- Fixed Gemini endpoint thinking budget spelling error
|
||||
- Fixed MCP card description text overflow
|
||||
- Fixed unnecessary message timestamp updates on UI-only state changes
|
||||
- Updated dependencies: Bun to 1.3.1, uv to 0.9.5
|
||||
- Fixed GitHub Copilot gpt-5-codex streaming issues
|
||||
- Fixed assistant creation failures
|
||||
- Fixed translate auto-copy functionality
|
||||
- Fixed miniapps external link opening
|
||||
- Fixed message layout and overflow issues
|
||||
- Fixed API key parsing to preserve spaces
|
||||
- Fixed agent display in different navbar layouts
|
||||
|
||||
<!--LANG:zh-CN-->
|
||||
v1.7.0-beta.5 新特性
|
||||
v1.7.0-beta.2 新特性
|
||||
|
||||
新功能:
|
||||
- MCPRouter 提供商:新增 MCPRouter 提供商集成,支持 token 管理和服务器同步
|
||||
- MCP 市场:增强 MCP 服务器发现和管理功能,支持多提供商市场
|
||||
- Agent 权限模式展示:空会话状态显示可视化权限模式卡片
|
||||
- 助手订阅设置:在助手预设中添加订阅 URL 管理功能
|
||||
- 会话设置:独立管理会话特定的设置和模型配置
|
||||
- 笔记全文搜索:跨所有笔记搜索并高亮匹配内容
|
||||
- 内置滴滴 MCP 服务器:集成滴滴打车服务(仅限中国地区)
|
||||
- Intel OV OCR:使用 Intel NPU 的硬件加速 OCR
|
||||
- 自动启动 API 服务器:当存在 Agent 时自动启动
|
||||
|
||||
改进:
|
||||
- UI 优化:macOS 上侧边栏工具提示位置优化,避免与窗口控制按钮重叠
|
||||
- MCP 服务器标志:在 Agent 设置工具部分显示服务器 logo
|
||||
- 长命令处理:Bash 命令标签自动截断(超过 100 字符时悬停查看完整内容)
|
||||
- MCP OAuth 回调:修复回调页面挂起问题并添加多语言支持(10 种语言)
|
||||
- 错误信息展示:改进错误块显示顺序,提高可读性
|
||||
- 插件浏览器:标签页居中对齐,视觉效果更统一
|
||||
- Agent 模型选择现在需要用户显式选择
|
||||
- 添加 Mistral AI 提供商支持
|
||||
- 添加 NewAPI 通用提供商支持
|
||||
- 改进不同模式下的导航栏布局一致性
|
||||
- 增强聊天组件响应式设计
|
||||
- 优化小屏幕代码块显示
|
||||
- 更新 OVMS 至 2025.3 正式版
|
||||
- 添加希腊语支持
|
||||
|
||||
问题修复:
|
||||
- 修复 Agent 会话未继承 allowed_tools 配置
|
||||
- 修复 Gemini 端点 thinking budget 拼写错误
|
||||
- 修复 MCP 卡片描述文本溢出问题
|
||||
- 修复仅 UI 状态变化时消息时间戳不必要的更新
|
||||
- 依赖更新:Bun 升级到 1.3.1,uv 升级到 0.9.5
|
||||
- 修复 GitHub Copilot gpt-5-codex 流式传输问题
|
||||
- 修复助手创建失败
|
||||
- 修复翻译自动复制功能
|
||||
- 修复小程序外部链接打开
|
||||
- 修复消息布局和溢出问题
|
||||
- 修复 API 密钥解析以保留空格
|
||||
- 修复不同导航栏布局中的 Agent 显示
|
||||
<!--LANG:END-->
|
||||
|
||||
+17
-27
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "CherryStudio",
|
||||
"version": "1.7.0-beta.3",
|
||||
"version": "1.7.0-beta.2",
|
||||
"private": true,
|
||||
"description": "A powerful AI assistant for producer.",
|
||||
"main": "./out/main/index.js",
|
||||
@@ -82,7 +82,6 @@
|
||||
"@libsql/client": "0.14.0",
|
||||
"@libsql/win32-x64-msvc": "^0.4.7",
|
||||
"@napi-rs/system-ocr": "patch:@napi-rs/system-ocr@npm%3A1.0.2#~/.yarn/patches/@napi-rs-system-ocr-npm-1.0.2-59e7a78e8b.patch",
|
||||
"@paymoapp/electron-shutdown-handler": "^1.1.2",
|
||||
"@strongtz/win32-arm64-msvc": "^0.4.7",
|
||||
"express": "^5.1.0",
|
||||
"font-list": "^2.0.0",
|
||||
@@ -93,10 +92,8 @@
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"officeparser": "^4.2.0",
|
||||
"os-proxy-config": "^1.1.2",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"selection-hook": "^1.0.12",
|
||||
"sharp": "^0.34.3",
|
||||
"socket.io": "^4.8.1",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"tesseract.js": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch",
|
||||
@@ -106,17 +103,17 @@
|
||||
"@agentic/exa": "^7.3.3",
|
||||
"@agentic/searxng": "^7.3.3",
|
||||
"@agentic/tavily": "^7.3.3",
|
||||
"@ai-sdk/amazon-bedrock": "^3.0.53",
|
||||
"@ai-sdk/google-vertex": "^3.0.61",
|
||||
"@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.8#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.8-d4d0aaac93.patch",
|
||||
"@ai-sdk/mistral": "^2.0.23",
|
||||
"@ai-sdk/perplexity": "^2.0.17",
|
||||
"@ai-sdk/amazon-bedrock": "^3.0.42",
|
||||
"@ai-sdk/google-vertex": "^3.0.48",
|
||||
"@ai-sdk/huggingface": "patch:@ai-sdk/huggingface@npm%3A0.0.4#~/.yarn/patches/@ai-sdk-huggingface-npm-0.0.4-8080836bc1.patch",
|
||||
"@ai-sdk/mistral": "^2.0.19",
|
||||
"@ai-sdk/perplexity": "^2.0.13",
|
||||
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
||||
"@anthropic-ai/sdk": "^0.41.0",
|
||||
"@anthropic-ai/vertex-sdk": "patch:@anthropic-ai/vertex-sdk@npm%3A0.11.4#~/.yarn/patches/@anthropic-ai-vertex-sdk-npm-0.11.4-c19cb41edb.patch",
|
||||
"@aws-sdk/client-bedrock": "^3.910.0",
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.910.0",
|
||||
"@aws-sdk/client-s3": "^3.910.0",
|
||||
"@aws-sdk/client-bedrock": "^3.840.0",
|
||||
"@aws-sdk/client-bedrock-runtime": "^3.840.0",
|
||||
"@aws-sdk/client-s3": "^3.840.0",
|
||||
"@biomejs/biome": "2.2.4",
|
||||
"@cherrystudio/ai-core": "workspace:^1.0.0-alpha.18",
|
||||
"@cherrystudio/embedjs": "^0.1.31",
|
||||
@@ -147,10 +144,9 @@
|
||||
"@eslint/js": "^9.22.0",
|
||||
"@google/genai": "patch:@google/genai@npm%3A1.0.1#~/.yarn/patches/@google-genai-npm-1.0.1-e26f0f9af7.patch",
|
||||
"@hello-pangea/dnd": "^18.0.1",
|
||||
"@heroui/react": "^2.8.3",
|
||||
"@kangfenmao/keyv-storage": "^0.1.0",
|
||||
"@langchain/community": "^1.0.0",
|
||||
"@langchain/core": "patch:@langchain/core@npm%3A1.0.2#~/.yarn/patches/@langchain-core-npm-1.0.2-183ef83fe4.patch",
|
||||
"@langchain/openai": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
|
||||
"@langchain/community": "^0.3.50",
|
||||
"@mistralai/mistralai": "^1.7.5",
|
||||
"@modelcontextprotocol/sdk": "^1.17.5",
|
||||
"@mozilla/readability": "^0.6.0",
|
||||
@@ -231,7 +227,7 @@
|
||||
"@viz-js/lang-dot": "^1.0.5",
|
||||
"@viz-js/viz": "^3.14.0",
|
||||
"@xyflow/react": "^12.4.4",
|
||||
"ai": "^5.0.90",
|
||||
"ai": "^5.0.76",
|
||||
"antd": "patch:antd@npm%3A5.27.0#~/.yarn/patches/antd-npm-5.27.0-aa91c36546.patch",
|
||||
"archiver": "^7.0.1",
|
||||
"async-mutex": "^0.5.0",
|
||||
@@ -241,7 +237,7 @@
|
||||
"check-disk-space": "3.4.0",
|
||||
"cheerio": "^1.1.2",
|
||||
"chokidar": "^4.0.3",
|
||||
"claude-code-plugins": "1.0.3",
|
||||
"claude-code-plugins": "1.0.1",
|
||||
"cli-progress": "^3.12.0",
|
||||
"clsx": "^2.1.1",
|
||||
"code-inspector-plugin": "^0.20.14",
|
||||
@@ -348,7 +344,6 @@
|
||||
"striptags": "^3.2.0",
|
||||
"styled-components": "^6.1.11",
|
||||
"swr": "^2.3.6",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.13",
|
||||
"tar": "^7.4.3",
|
||||
"tiny-pinyin": "^1.3.2",
|
||||
@@ -374,11 +369,12 @@
|
||||
"zod": "^4.1.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"@smithy/types": "4.7.1",
|
||||
"@codemirror/language": "6.11.3",
|
||||
"@codemirror/lint": "6.8.5",
|
||||
"@codemirror/view": "6.38.1",
|
||||
"@langchain/core@npm:^0.3.26": "patch:@langchain/core@npm%3A1.0.2#~/.yarn/patches/@langchain-core-npm-1.0.2-183ef83fe4.patch",
|
||||
"@langchain/core@npm:^0.3.26": "patch:@langchain/core@npm%3A0.3.44#~/.yarn/patches/@langchain-core-npm-0.3.44-41d5c3cb0a.patch",
|
||||
"@langchain/openai@npm:^0.3.16": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch",
|
||||
"@langchain/openai@npm:>=0.1.0 <0.4.0": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch",
|
||||
"app-builder-lib@npm:26.0.13": "patch:app-builder-lib@npm%3A26.0.13#~/.yarn/patches/app-builder-lib-npm-26.0.13-a064c9e1d0.patch",
|
||||
"app-builder-lib@npm:26.0.15": "patch:app-builder-lib@npm%3A26.0.15#~/.yarn/patches/app-builder-lib-npm-26.0.15-360e5b0476.patch",
|
||||
"atomically@npm:^1.7.0": "patch:atomically@npm%3A1.7.0#~/.yarn/patches/atomically-npm-1.7.0-e742e5293b.patch",
|
||||
@@ -402,13 +398,7 @@
|
||||
"@img/sharp-linux-arm64": "0.34.3",
|
||||
"@img/sharp-linux-x64": "0.34.3",
|
||||
"@img/sharp-win32-x64": "0.34.3",
|
||||
"openai@npm:5.12.2": "npm:@cherrystudio/openai@6.5.0",
|
||||
"@langchain/openai@npm:>=0.1.0 <0.6.0": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
|
||||
"@langchain/openai@npm:^0.3.16": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
|
||||
"@langchain/openai@npm:>=0.2.0 <0.7.0": "patch:@langchain/openai@npm%3A1.0.0#~/.yarn/patches/@langchain-openai-npm-1.0.0-474d0ad9d4.patch",
|
||||
"@ai-sdk/google@npm:2.0.30": "patch:@ai-sdk/google@npm%3A2.0.30#~/.yarn/patches/@ai-sdk-google-npm-2.0.30-3b31632362.patch",
|
||||
"@ai-sdk/openai@npm:2.0.64": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch",
|
||||
"@ai-sdk/openai@npm:^2.0.42": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch"
|
||||
"openai@npm:5.12.2": "npm:@cherrystudio/openai@6.5.0"
|
||||
},
|
||||
"packageManager": "yarn@4.9.1",
|
||||
"lint-staged": {
|
||||
|
||||
@@ -36,14 +36,14 @@
|
||||
"ai": "^5.0.26"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^2.0.43",
|
||||
"@ai-sdk/azure": "^2.0.66",
|
||||
"@ai-sdk/deepseek": "^1.0.27",
|
||||
"@ai-sdk/openai": "patch:@ai-sdk/openai@npm%3A2.0.64#~/.yarn/patches/@ai-sdk-openai-npm-2.0.64-48f99f5bf3.patch",
|
||||
"@ai-sdk/openai-compatible": "^1.0.26",
|
||||
"@ai-sdk/anthropic": "^2.0.32",
|
||||
"@ai-sdk/azure": "^2.0.53",
|
||||
"@ai-sdk/deepseek": "^1.0.23",
|
||||
"@ai-sdk/openai": "patch:@ai-sdk/openai@npm%3A2.0.52#~/.yarn/patches/@ai-sdk-openai-npm-2.0.52-b36d949c76.patch",
|
||||
"@ai-sdk/openai-compatible": "^1.0.22",
|
||||
"@ai-sdk/provider": "^2.0.0",
|
||||
"@ai-sdk/provider-utils": "^3.0.16",
|
||||
"@ai-sdk/xai": "^2.0.31",
|
||||
"@ai-sdk/provider-utils": "^3.0.12",
|
||||
"@ai-sdk/xai": "^2.0.26",
|
||||
"zod": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 中间件管理器
|
||||
* 专注于 AI SDK 中间件的管理,与插件系统分离
|
||||
*/
|
||||
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
|
||||
/**
|
||||
* 创建中间件列表
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 中间件系统类型定义
|
||||
*/
|
||||
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
|
||||
/**
|
||||
* 具名中间件接口
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 模型包装工具函数
|
||||
* 用于将中间件应用到LanguageModel上
|
||||
*/
|
||||
import type { LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { wrapLanguageModel } from 'ai'
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 集成了来自 ModelCreator 的特殊处理逻辑
|
||||
*/
|
||||
|
||||
import type { EmbeddingModelV2, ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { EmbeddingModelV2, ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
|
||||
import { wrapModelWithMiddlewares } from '../middleware/wrapper'
|
||||
import { DEFAULT_SEPARATOR, globalRegistryManagement } from '../providers/RegistryManagement'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Creation 模块类型定义
|
||||
*/
|
||||
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
|
||||
import type { ProviderId, ProviderSettingsMap } from '../providers/types'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ExtractProviderOptions, ProviderOptionsMap, TypedProviderOptions } from './types'
|
||||
import { ExtractProviderOptions, ProviderOptionsMap, TypedProviderOptions } from './types'
|
||||
|
||||
/**
|
||||
* 创建特定供应商的选项
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { AiRequestContext } from '../../types'
|
||||
import { StreamEventManager } from './StreamEventManager'
|
||||
import { type TagConfig, TagExtractor } from './tagExtraction'
|
||||
import { ToolExecutor } from './ToolExecutor'
|
||||
import type { PromptToolUseConfig, ToolUseResult } from './type'
|
||||
import { PromptToolUseConfig, ToolUseResult } from './type'
|
||||
|
||||
/**
|
||||
* 工具使用标签配置
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ToolSet } from 'ai'
|
||||
import { ToolSet } from 'ai'
|
||||
|
||||
import type { AiRequestContext } from '../..'
|
||||
import { AiRequestContext } from '../..'
|
||||
|
||||
/**
|
||||
* 解析结果类型
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { anthropic } from '@ai-sdk/anthropic'
|
||||
import type { google } from '@ai-sdk/google'
|
||||
import type { openai } from '@ai-sdk/openai'
|
||||
import type { InferToolInput, InferToolOutput } from 'ai'
|
||||
import { type Tool } from 'ai'
|
||||
import { anthropic } from '@ai-sdk/anthropic'
|
||||
import { google } from '@ai-sdk/google'
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
import { InferToolInput, InferToolOutput, type Tool } from 'ai'
|
||||
|
||||
import type { ProviderOptionsMap } from '../../../options/types'
|
||||
import type { OpenRouterSearchConfig } from './openrouter'
|
||||
import { ProviderOptionsMap } from '../../../options/types'
|
||||
import { OpenRouterSearchConfig } from './openrouter'
|
||||
|
||||
/**
|
||||
* 从 AI SDK 的工具函数中提取参数类型,以确保类型安全。
|
||||
|
||||
@@ -9,8 +9,7 @@ import { openai } from '@ai-sdk/openai'
|
||||
import { createOpenRouterOptions, createXaiOptions, mergeProviderOptions } from '../../../options'
|
||||
import { definePlugin } from '../../'
|
||||
import type { AiRequestContext } from '../../types'
|
||||
import type { WebSearchPluginConfig } from './helper'
|
||||
import { DEFAULT_WEB_SEARCH_CONFIG } from './helper'
|
||||
import { DEFAULT_WEB_SEARCH_CONFIG, WebSearchPluginConfig } from './helper'
|
||||
|
||||
/**
|
||||
* 网络搜索插件
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AiPlugin, AiRequestContext } from './types'
|
||||
import { AiPlugin, AiRequestContext } from './types'
|
||||
|
||||
/**
|
||||
* 插件管理器
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 例如: aihubmix:anthropic:claude-3.5-sonnet
|
||||
*/
|
||||
|
||||
import type { ProviderV2 } from '@ai-sdk/provider'
|
||||
import { ProviderV2 } from '@ai-sdk/provider'
|
||||
import { customProvider } from 'ai'
|
||||
|
||||
import { globalRegistryManagement } from './RegistryManagement'
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* 基于 AI SDK 原生的 createProviderRegistry
|
||||
*/
|
||||
|
||||
import type { EmbeddingModelV2, ImageModelV2, LanguageModelV2, ProviderV2 } from '@ai-sdk/provider'
|
||||
import { EmbeddingModelV2, ImageModelV2, LanguageModelV2, ProviderV2 } from '@ai-sdk/provider'
|
||||
import { createProviderRegistry, type ProviderRegistryProvider } from 'ai'
|
||||
|
||||
type PROVIDERS = Record<string, ProviderV2>
|
||||
|
||||
@@ -10,11 +10,10 @@ import { createGoogleGenerativeAI } from '@ai-sdk/google'
|
||||
import { createHuggingFace } from '@ai-sdk/huggingface'
|
||||
import { createOpenAI, type OpenAIProviderSettings } from '@ai-sdk/openai'
|
||||
import { createOpenAICompatible } from '@ai-sdk/openai-compatible'
|
||||
import type { LanguageModelV2 } from '@ai-sdk/provider'
|
||||
import { LanguageModelV2 } from '@ai-sdk/provider'
|
||||
import { createXai } from '@ai-sdk/xai'
|
||||
import { createOpenRouter } from '@openrouter/ai-sdk-provider'
|
||||
import type { Provider } from 'ai'
|
||||
import { customProvider } from 'ai'
|
||||
import { customProvider, Provider } from 'ai'
|
||||
import * as z from 'zod'
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,7 @@ import { type DeepSeekProviderSettings } from '@ai-sdk/deepseek'
|
||||
import { type GoogleGenerativeAIProviderSettings } from '@ai-sdk/google'
|
||||
import { type OpenAIProviderSettings } from '@ai-sdk/openai'
|
||||
import { type OpenAICompatibleProviderSettings } from '@ai-sdk/openai-compatible'
|
||||
import type {
|
||||
import {
|
||||
EmbeddingModelV2 as EmbeddingModel,
|
||||
ImageModelV2 as ImageModel,
|
||||
LanguageModelV2 as LanguageModel,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ImageModelV2 } from '@ai-sdk/provider'
|
||||
import { ImageModelV2 } from '@ai-sdk/provider'
|
||||
import { experimental_generateImage as aiGenerateImage, NoImageGeneratedError } from 'ai'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
* 运行时执行器
|
||||
* 专注于插件化的AI调用处理
|
||||
*/
|
||||
import type { ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import type { LanguageModel } from 'ai'
|
||||
import { ImageModelV2, LanguageModelV2, LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import {
|
||||
experimental_generateImage as _generateImage,
|
||||
generateObject as _generateObject,
|
||||
generateText as _generateText,
|
||||
LanguageModel,
|
||||
streamObject as _streamObject,
|
||||
streamText as _streamText
|
||||
} from 'ai'
|
||||
|
||||
@@ -11,7 +11,7 @@ export type { RuntimeConfig } from './types'
|
||||
|
||||
// === 便捷工厂函数 ===
|
||||
|
||||
import type { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
import { LanguageModelV2Middleware } from '@ai-sdk/provider'
|
||||
|
||||
import { type AiPlugin } from '../plugins'
|
||||
import { type ProviderId, type ProviderSettingsMap } from '../providers/types'
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
/* eslint-disable @eslint-react/naming-convention/context-name */
|
||||
import type { ImageModelV2 } from '@ai-sdk/provider'
|
||||
import type {
|
||||
experimental_generateImage,
|
||||
generateObject,
|
||||
generateText,
|
||||
LanguageModel,
|
||||
streamObject,
|
||||
streamText
|
||||
} from 'ai'
|
||||
import { ImageModelV2 } from '@ai-sdk/provider'
|
||||
import { experimental_generateImage, generateObject, generateText, LanguageModel, streamObject, streamText } from 'ai'
|
||||
|
||||
import { type AiPlugin, createContext, PluginManager } from '../plugins'
|
||||
import { type ProviderId } from '../providers/types'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* Runtime 层类型定义
|
||||
*/
|
||||
import type { ImageModelV2 } from '@ai-sdk/provider'
|
||||
import type { experimental_generateImage, generateObject, generateText, streamObject, streamText } from 'ai'
|
||||
import { ImageModelV2 } from '@ai-sdk/provider'
|
||||
import { experimental_generateImage, generateObject, generateText, streamObject, streamText } from 'ai'
|
||||
|
||||
import { type ModelConfig } from '../models/types'
|
||||
import { type AiPlugin } from '../plugins'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Node } from '@tiptap/core'
|
||||
import { Extension } from '@tiptap/core'
|
||||
import { Extension, Node } from '@tiptap/core'
|
||||
|
||||
import type { TableCellOptions } from '../cell/index.js'
|
||||
import { TableCell } from '../cell/index.js'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SpanKind, SpanStatusCode } from '@opentelemetry/api'
|
||||
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
||||
import { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
||||
|
||||
import type { SpanEntity } from '../types/config'
|
||||
import { SpanEntity } from '../types/config'
|
||||
|
||||
/**
|
||||
* convert ReadableSpan to SpanEntity
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
||||
import { ReadableSpan } from '@opentelemetry/sdk-trace-base'
|
||||
|
||||
export interface TraceCache {
|
||||
createSpan: (span: ReadableSpan) => void
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ExportResult } from '@opentelemetry/core'
|
||||
import { ExportResultCode } from '@opentelemetry/core'
|
||||
import type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { ExportResult, ExportResultCode } from '@opentelemetry/core'
|
||||
import { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
|
||||
export type SaveFunction = (spans: ReadableSpan[]) => Promise<void>
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { Context } from '@opentelemetry/api'
|
||||
import { trace } from '@opentelemetry/api'
|
||||
import type { BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import { Context, trace } from '@opentelemetry/api'
|
||||
import { BatchSpanProcessor, BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
|
||||
import type { TraceCache } from '../core/traceCache'
|
||||
import { TraceCache } from '../core/traceCache'
|
||||
|
||||
export class CacheBatchSpanProcessor extends BatchSpanProcessor {
|
||||
private cache: TraceCache
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { Context } from '@opentelemetry/api'
|
||||
import type { BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import type { EventEmitter } from 'stream'
|
||||
import { Context } from '@opentelemetry/api'
|
||||
import { BatchSpanProcessor, BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { EventEmitter } from 'stream'
|
||||
|
||||
import { convertSpanToSpanEntity } from '../core/spanConvert'
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { Context } from '@opentelemetry/api'
|
||||
import { trace } from '@opentelemetry/api'
|
||||
import type { BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import { Context, trace } from '@opentelemetry/api'
|
||||
import { BatchSpanProcessor, BufferConfig, ReadableSpan, Span, SpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
|
||||
export type SpanFunction = (span: ReadableSpan) => void
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Link } from '@opentelemetry/api'
|
||||
import type { TimedEvent } from '@opentelemetry/sdk-trace-base'
|
||||
import { Link } from '@opentelemetry/api'
|
||||
import { TimedEvent } from '@opentelemetry/sdk-trace-base'
|
||||
|
||||
export type AttributeValue =
|
||||
| string
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import type { Tracer } from '@opentelemetry/api'
|
||||
import { trace } from '@opentelemetry/api'
|
||||
import { trace, Tracer } from '@opentelemetry/api'
|
||||
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'
|
||||
import { W3CTraceContextPropagator } from '@opentelemetry/core'
|
||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
||||
import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor, ConsoleSpanExporter, SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
|
||||
|
||||
import type { TraceConfig } from '../trace-core/types/config'
|
||||
import { defaultConfig } from '../trace-core/types/config'
|
||||
import { defaultConfig, TraceConfig } from '../trace-core/types/config'
|
||||
|
||||
export class NodeTracer {
|
||||
private static provider: NodeTracerProvider
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Context, ContextManager } from '@opentelemetry/api'
|
||||
import { ROOT_CONTEXT } from '@opentelemetry/api'
|
||||
import { Context, ContextManager, ROOT_CONTEXT } from '@opentelemetry/api'
|
||||
|
||||
export class TopicContextManager implements ContextManager {
|
||||
private topicContextStack: Map<string, Context[]>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Context } from '@opentelemetry/api'
|
||||
import { context } from '@opentelemetry/api'
|
||||
import { Context, context } from '@opentelemetry/api'
|
||||
|
||||
const originalPromise = globalThis.Promise
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { W3CTraceContextPropagator } from '@opentelemetry/core'
|
||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
||||
import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor, ConsoleSpanExporter } from '@opentelemetry/sdk-trace-base'
|
||||
import { BatchSpanProcessor, ConsoleSpanExporter, SpanProcessor } from '@opentelemetry/sdk-trace-base'
|
||||
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'
|
||||
|
||||
import type { TraceConfig } from '../trace-core/types/config'
|
||||
import { defaultConfig } from '../trace-core/types/config'
|
||||
import { defaultConfig, TraceConfig } from '../trace-core/types/config'
|
||||
import { TopicContextManager } from './TopicContextManager'
|
||||
|
||||
export const contextManager = new TopicContextManager()
|
||||
|
||||
@@ -322,7 +322,6 @@ export enum IpcChannel {
|
||||
ApiServer_Stop = 'api-server:stop',
|
||||
ApiServer_Restart = 'api-server:restart',
|
||||
ApiServer_GetStatus = 'api-server:get-status',
|
||||
ApiServer_Ready = 'api-server:ready',
|
||||
// NOTE: This api is not be used.
|
||||
ApiServer_GetConfig = 'api-server:get-config',
|
||||
|
||||
@@ -364,12 +363,5 @@ export enum IpcChannel {
|
||||
ClaudeCodePlugin_ListInstalled = 'claudeCodePlugin:list-installed',
|
||||
ClaudeCodePlugin_InvalidateCache = 'claudeCodePlugin:invalidate-cache',
|
||||
ClaudeCodePlugin_ReadContent = 'claudeCodePlugin:read-content',
|
||||
ClaudeCodePlugin_WriteContent = 'claudeCodePlugin:write-content',
|
||||
|
||||
// WebSocket
|
||||
WebSocket_Start = 'webSocket:start',
|
||||
WebSocket_Stop = 'webSocket:stop',
|
||||
WebSocket_Status = 'webSocket:status',
|
||||
WebSocket_SendFile = 'webSocket:send-file',
|
||||
WebSocket_GetAllCandidates = 'webSocket:get-all-candidates'
|
||||
ClaudeCodePlugin_WriteContent = 'claudeCodePlugin:write-content'
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
*/
|
||||
|
||||
import Anthropic from '@anthropic-ai/sdk'
|
||||
import type { TextBlockParam } from '@anthropic-ai/sdk/resources'
|
||||
import { TextBlockParam } from '@anthropic-ai/sdk/resources'
|
||||
import { loggerService } from '@logger'
|
||||
import type { Provider } from '@types'
|
||||
import { Provider } from '@types'
|
||||
import type { ModelMessage } from 'ai'
|
||||
|
||||
const logger = loggerService.withContext('anthropic-sdk')
|
||||
|
||||
@@ -470,6 +470,3 @@ export const MACOS_TERMINALS_WITH_COMMANDS: TerminalConfigWithCommand[] = [
|
||||
})
|
||||
}
|
||||
]
|
||||
|
||||
// resources/scripts should be maintained manually
|
||||
export const HOME_CHERRY_DIR = '.cherrystudio'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ProcessingStatus } from '@types'
|
||||
import { ProcessingStatus } from '@types'
|
||||
|
||||
export type LoaderReturn = {
|
||||
entriesAdded: number
|
||||
@@ -31,16 +31,3 @@ export type WebviewKeyEvent = {
|
||||
shift: boolean
|
||||
alt: boolean
|
||||
}
|
||||
|
||||
export interface WebSocketStatusResponse {
|
||||
isRunning: boolean
|
||||
port?: number
|
||||
ip?: string
|
||||
clientConnected: boolean
|
||||
}
|
||||
|
||||
export interface WebSocketCandidatesResponse {
|
||||
host: string
|
||||
interface: string
|
||||
priority: number
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
;(() => {
|
||||
let messageId = 0
|
||||
const pendingCalls = new Map()
|
||||
|
||||
function api(method, ...args) {
|
||||
const id = messageId++
|
||||
return new Promise((resolve, reject) => {
|
||||
pendingCalls.set(id, { resolve, reject })
|
||||
window.parent.postMessage({ id, type: 'api-call', method, args }, '*')
|
||||
})
|
||||
}
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data.type === 'api-response') {
|
||||
const { id, result, error } = event.data
|
||||
const pendingCall = pendingCalls.get(id)
|
||||
if (pendingCall) {
|
||||
if (error) {
|
||||
pendingCall.reject(new Error(error))
|
||||
} else {
|
||||
pendingCall.resolve(result)
|
||||
}
|
||||
pendingCalls.delete(id)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
window.api = new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, prop) => {
|
||||
return (...args) => api(prop, ...args)
|
||||
}
|
||||
}
|
||||
)
|
||||
})()
|
||||
@@ -0,0 +1,5 @@
|
||||
export function getQueryParam(paramName) {
|
||||
const url = new URL(window.location.href)
|
||||
const params = new URLSearchParams(url.search)
|
||||
return params.get(paramName)
|
||||
}
|
||||
@@ -7,7 +7,7 @@ const { downloadWithRedirects } = require('./download')
|
||||
|
||||
// Base URL for downloading bun binaries
|
||||
const BUN_RELEASE_BASE_URL = 'https://gitcode.com/CherryHQ/bun/releases/download'
|
||||
const DEFAULT_BUN_VERSION = '1.3.1' // Default fallback version
|
||||
const DEFAULT_BUN_VERSION = '1.2.17' // Default fallback version
|
||||
|
||||
// Mapping of platform+arch to binary package name
|
||||
const BUN_PACKAGES = {
|
||||
|
||||
@@ -7,29 +7,28 @@ const { downloadWithRedirects } = require('./download')
|
||||
|
||||
// Base URL for downloading uv binaries
|
||||
const UV_RELEASE_BASE_URL = 'https://gitcode.com/CherryHQ/uv/releases/download'
|
||||
const DEFAULT_UV_VERSION = '0.9.5'
|
||||
const DEFAULT_UV_VERSION = '0.7.13'
|
||||
|
||||
// Mapping of platform+arch to binary package name
|
||||
const UV_PACKAGES = {
|
||||
'darwin-arm64': 'uv-aarch64-apple-darwin.tar.gz',
|
||||
'darwin-x64': 'uv-x86_64-apple-darwin.tar.gz',
|
||||
'darwin-arm64': 'uv-aarch64-apple-darwin.zip',
|
||||
'darwin-x64': 'uv-x86_64-apple-darwin.zip',
|
||||
'win32-arm64': 'uv-aarch64-pc-windows-msvc.zip',
|
||||
'win32-ia32': 'uv-i686-pc-windows-msvc.zip',
|
||||
'win32-x64': 'uv-x86_64-pc-windows-msvc.zip',
|
||||
'linux-arm64': 'uv-aarch64-unknown-linux-gnu.tar.gz',
|
||||
'linux-ia32': 'uv-i686-unknown-linux-gnu.tar.gz',
|
||||
'linux-ppc64': 'uv-powerpc64-unknown-linux-gnu.tar.gz',
|
||||
'linux-ppc64le': 'uv-powerpc64le-unknown-linux-gnu.tar.gz',
|
||||
'linux-riscv64': 'uv-riscv64gc-unknown-linux-gnu.tar.gz',
|
||||
'linux-s390x': 'uv-s390x-unknown-linux-gnu.tar.gz',
|
||||
'linux-x64': 'uv-x86_64-unknown-linux-gnu.tar.gz',
|
||||
'linux-armv7l': 'uv-armv7-unknown-linux-gnueabihf.tar.gz',
|
||||
'linux-arm64': 'uv-aarch64-unknown-linux-gnu.zip',
|
||||
'linux-ia32': 'uv-i686-unknown-linux-gnu.zip',
|
||||
'linux-ppc64': 'uv-powerpc64-unknown-linux-gnu.zip',
|
||||
'linux-ppc64le': 'uv-powerpc64le-unknown-linux-gnu.zip',
|
||||
'linux-s390x': 'uv-s390x-unknown-linux-gnu.zip',
|
||||
'linux-x64': 'uv-x86_64-unknown-linux-gnu.zip',
|
||||
'linux-armv7l': 'uv-armv7-unknown-linux-gnueabihf.zip',
|
||||
// MUSL variants
|
||||
'linux-musl-arm64': 'uv-aarch64-unknown-linux-musl.tar.gz',
|
||||
'linux-musl-ia32': 'uv-i686-unknown-linux-musl.tar.gz',
|
||||
'linux-musl-x64': 'uv-x86_64-unknown-linux-musl.tar.gz',
|
||||
'linux-musl-armv6l': 'uv-arm-unknown-linux-musleabihf.tar.gz',
|
||||
'linux-musl-armv7l': 'uv-armv7-unknown-linux-musleabihf.tar.gz'
|
||||
'linux-musl-arm64': 'uv-aarch64-unknown-linux-musl.zip',
|
||||
'linux-musl-ia32': 'uv-i686-unknown-linux-musl.zip',
|
||||
'linux-musl-x64': 'uv-x86_64-unknown-linux-musl.zip',
|
||||
'linux-musl-armv6l': 'uv-arm-unknown-linux-musleabihf.zip',
|
||||
'linux-musl-armv7l': 'uv-armv7-unknown-linux-musleabihf.zip'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +56,6 @@ async function downloadUvBinary(platform, arch, version = DEFAULT_UV_VERSION, is
|
||||
const downloadUrl = `${UV_RELEASE_BASE_URL}/${version}/${packageName}`
|
||||
const tempdir = os.tmpdir()
|
||||
const tempFilename = path.join(tempdir, packageName)
|
||||
const isTarGz = packageName.endsWith('.tar.gz')
|
||||
|
||||
try {
|
||||
console.log(`Downloading uv ${version} for ${platformKey}...`)
|
||||
@@ -67,58 +65,34 @@ async function downloadUvBinary(platform, arch, version = DEFAULT_UV_VERSION, is
|
||||
|
||||
console.log(`Extracting ${packageName} to ${binDir}...`)
|
||||
|
||||
if (isTarGz) {
|
||||
// Use tar command to extract tar.gz files (macOS and Linux)
|
||||
const tempExtractDir = path.join(tempdir, `uv-extract-${Date.now()}`)
|
||||
fs.mkdirSync(tempExtractDir, { recursive: true })
|
||||
const zip = new StreamZip.async({ file: tempFilename })
|
||||
|
||||
execSync(`tar -xzf "${tempFilename}" -C "${tempExtractDir}"`, { stdio: 'inherit' })
|
||||
// Get all entries in the zip file
|
||||
const entries = await zip.entries()
|
||||
|
||||
// Find all files in the extracted directory and move them to binDir
|
||||
const findAndMoveFiles = (dir) => {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
findAndMoveFiles(fullPath)
|
||||
} else {
|
||||
const filename = path.basename(entry.name)
|
||||
const outputPath = path.join(binDir, filename)
|
||||
fs.copyFileSync(fullPath, outputPath)
|
||||
console.log(`Extracted ${entry.name} -> ${outputPath}`)
|
||||
// Make executable on Unix-like systems
|
||||
// Extract files directly to binDir, flattening the directory structure
|
||||
for (const entry of Object.values(entries)) {
|
||||
if (!entry.isDirectory) {
|
||||
// Get just the filename without path
|
||||
const filename = path.basename(entry.name)
|
||||
const outputPath = path.join(binDir, filename)
|
||||
|
||||
console.log(`Extracting ${entry.name} -> ${filename}`)
|
||||
await zip.extract(entry.name, outputPath)
|
||||
// Make executable files executable on Unix-like systems
|
||||
if (platform !== 'win32') {
|
||||
try {
|
||||
fs.chmodSync(outputPath, 0o755)
|
||||
} catch (chmodError) {
|
||||
console.error(`Warning: Failed to set executable permissions on ${filename}`)
|
||||
return 102
|
||||
}
|
||||
}
|
||||
console.log(`Extracted ${entry.name} -> ${outputPath}`)
|
||||
}
|
||||
|
||||
findAndMoveFiles(tempExtractDir)
|
||||
|
||||
// Clean up temporary extraction directory
|
||||
fs.rmSync(tempExtractDir, { recursive: true })
|
||||
} else {
|
||||
// Use StreamZip for zip files (Windows)
|
||||
const zip = new StreamZip.async({ file: tempFilename })
|
||||
|
||||
// Get all entries in the zip file
|
||||
const entries = await zip.entries()
|
||||
|
||||
// Extract files directly to binDir, flattening the directory structure
|
||||
for (const entry of Object.values(entries)) {
|
||||
if (!entry.isDirectory) {
|
||||
// Get just the filename without path
|
||||
const filename = path.basename(entry.name)
|
||||
const outputPath = path.join(binDir, filename)
|
||||
|
||||
console.log(`Extracting ${entry.name} -> ${filename}`)
|
||||
await zip.extract(entry.name, outputPath)
|
||||
console.log(`Extracted ${entry.name} -> ${outputPath}`)
|
||||
}
|
||||
}
|
||||
|
||||
await zip.close()
|
||||
}
|
||||
|
||||
await zip.close()
|
||||
fs.unlinkSync(tempFilename)
|
||||
console.log(`Successfully installed uv ${version} for ${platform}-${arch}`)
|
||||
return 0
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
const https = require('https')
|
||||
const { loggerService } = require('@logger')
|
||||
|
||||
const logger = loggerService.withContext('IpService')
|
||||
|
||||
/**
|
||||
* 获取用户的IP地址所在国家
|
||||
* @returns {Promise<string>} 返回国家代码,默认为'CN'
|
||||
*/
|
||||
async function getIpCountry() {
|
||||
return new Promise((resolve) => {
|
||||
// 添加超时控制
|
||||
const timeout = setTimeout(() => {
|
||||
logger.info('IP Address Check Timeout, default to China Mirror')
|
||||
resolve('CN')
|
||||
}, 5000)
|
||||
|
||||
const options = {
|
||||
hostname: 'ipinfo.io',
|
||||
path: '/json',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
||||
'Accept-Language': 'en-US,en;q=0.9'
|
||||
}
|
||||
}
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
clearTimeout(timeout)
|
||||
let data = ''
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk
|
||||
})
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const parsed = JSON.parse(data)
|
||||
const country = parsed.country || 'CN'
|
||||
logger.info(`Detected user IP address country: ${country}`)
|
||||
resolve(country)
|
||||
} catch (error) {
|
||||
logger.error('Failed to parse IP address information:', error.message)
|
||||
resolve('CN')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
req.on('error', (error) => {
|
||||
clearTimeout(timeout)
|
||||
logger.error('Failed to get IP address information:', error.message)
|
||||
resolve('CN')
|
||||
})
|
||||
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否在中国
|
||||
* @returns {Promise<boolean>} 如果用户在中国返回true,否则返回false
|
||||
*/
|
||||
async function isUserInChina() {
|
||||
const country = await getIpCountry()
|
||||
return country.toLowerCase() === 'cn'
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户位置获取适合的npm镜像URL
|
||||
* @returns {Promise<string>} 返回npm镜像URL
|
||||
*/
|
||||
async function getNpmRegistryUrl() {
|
||||
const inChina = await isUserInChina()
|
||||
if (inChina) {
|
||||
logger.info('User in China, using Taobao npm mirror')
|
||||
return 'https://registry.npmmirror.com'
|
||||
} else {
|
||||
logger.info('User not in China, using default npm mirror')
|
||||
return 'https://registry.npmjs.org'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getIpCountry,
|
||||
isUserInChina,
|
||||
getNpmRegistryUrl
|
||||
}
|
||||
@@ -18,10 +18,8 @@ import { sortedObjectByKeys } from './sort'
|
||||
// ========== SCRIPT CONFIGURATION AREA - MODIFY SETTINGS HERE ==========
|
||||
const SCRIPT_CONFIG = {
|
||||
// 🔧 Concurrency Control Configuration
|
||||
MAX_CONCURRENT_TRANSLATIONS: process.env.TRANSLATION_MAX_CONCURRENT_REQUESTS
|
||||
? parseInt(process.env.TRANSLATION_MAX_CONCURRENT_REQUESTS)
|
||||
: 5, // Max concurrent requests (Make sure the concurrency level does not exceed your provider's limits.)
|
||||
TRANSLATION_DELAY_MS: process.env.TRANSLATION_DELAY_MS ? parseInt(process.env.TRANSLATION_DELAY_MS) : 500, // Delay between requests to avoid rate limiting (Recommended: 100-500ms, Range: 0-5000ms)
|
||||
MAX_CONCURRENT_TRANSLATIONS: 5, // Max concurrent requests (Make sure the concurrency level does not exceed your provider's limits.)
|
||||
TRANSLATION_DELAY_MS: 100, // Delay between requests to avoid rate limiting (Recommended: 100-500ms, Range: 0-5000ms)
|
||||
|
||||
// 🔑 API Configuration
|
||||
API_KEY: process.env.TRANSLATION_API_KEY || '', // API key from environment variable
|
||||
|
||||
@@ -0,0 +1,635 @@
|
||||
/**
|
||||
* Feishu (Lark) Webhook Notification Script for Pull Requests
|
||||
* Sends GitHub PR summaries to Feishu with @ mentions for reviewers and assignees
|
||||
*/
|
||||
|
||||
const crypto = require('crypto')
|
||||
const https = require('https')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
/**
|
||||
* Generate Feishu webhook signature
|
||||
* @param {string} secret - Feishu webhook secret
|
||||
* @param {number} timestamp - Unix timestamp in seconds
|
||||
* @returns {string} Base64 encoded signature
|
||||
*/
|
||||
function generateSignature(secret, timestamp) {
|
||||
const stringToSign = `${timestamp}\n${secret}`
|
||||
const hmac = crypto.createHmac('sha256', stringToSign)
|
||||
return hmac.digest('base64')
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to Feishu webhook
|
||||
* @param {string} webhookUrl - Feishu webhook URL
|
||||
* @param {string} secret - Feishu webhook secret
|
||||
* @param {object} content - Message content
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function sendToFeishu(webhookUrl, secret, content) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timestamp = Math.floor(Date.now() / 1000)
|
||||
const sign = generateSignature(secret, timestamp)
|
||||
|
||||
const payload = JSON.stringify({
|
||||
timestamp: timestamp.toString(),
|
||||
sign: sign,
|
||||
msg_type: 'interactive',
|
||||
card: content
|
||||
})
|
||||
|
||||
const url = new URL(webhookUrl)
|
||||
const options = {
|
||||
hostname: url.hostname,
|
||||
path: url.pathname + url.search,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(payload)
|
||||
}
|
||||
}
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let data = ''
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk
|
||||
})
|
||||
res.on('end', () => {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
console.log('✅ Successfully sent to Feishu:', data)
|
||||
resolve()
|
||||
} else {
|
||||
reject(new Error(`Feishu API error: ${res.statusCode} - ${data}`))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
req.on('error', (error) => {
|
||||
reject(error)
|
||||
})
|
||||
|
||||
req.write(payload)
|
||||
req.end()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse user mapping from environment variable
|
||||
* Expected format: "github_user1:feishu_id1,github_user2:feishu_id2"
|
||||
* @param {string} mappingStr - User mapping string
|
||||
* @returns {Map<string, string>} Map of GitHub username to Feishu user ID
|
||||
*/
|
||||
function parseUserMapping(mappingStr) {
|
||||
const mapping = new Map()
|
||||
if (!mappingStr) {
|
||||
return mapping
|
||||
}
|
||||
|
||||
const pairs = mappingStr.split(',')
|
||||
for (const pair of pairs) {
|
||||
const [github, feishu] = pair.split(':').map((s) => s.trim())
|
||||
if (github && feishu) {
|
||||
mapping.set(github, feishu)
|
||||
}
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PR category display info
|
||||
* @param {string} category - PR category
|
||||
* @returns {object} Category display info
|
||||
*/
|
||||
function getCategoryInfo(category) {
|
||||
const categoryMap = {
|
||||
chat: { emoji: '💬', name: '对话', color: 'blue' },
|
||||
draw: { emoji: '🖼️', name: '绘图', color: 'blue' },
|
||||
uiux: { emoji: '🎨', name: 'UI/UX', color: 'blue' },
|
||||
knowledge: { emoji: '🧠', name: '知识库', color: 'green' },
|
||||
agent: { emoji: '🕹️', name: 'Agent', color: 'turquoise' },
|
||||
provider: { emoji: '🔌', name: 'Provider', color: 'turquoise' },
|
||||
minapps: { emoji: '🧩', name: '小程序', color: 'turquoise' },
|
||||
backup_export: { emoji: '💾', name: '备份/导出', color: 'purple' },
|
||||
data_storage: { emoji: '🗄️', name: '数据与存储', color: 'purple' },
|
||||
ai_core: { emoji: '🤖', name: 'AI基础设施', color: 'purple' },
|
||||
backend: { emoji: '⚙️', name: '后端/平台', color: 'green' },
|
||||
docs: { emoji: '📚', name: '文档', color: 'grey' },
|
||||
'build-config': { emoji: '🔧', name: '构建/配置', color: 'orange' },
|
||||
test: { emoji: '🧪', name: '测试', color: 'yellow' },
|
||||
multiple: { emoji: '🔀', name: '多模块', color: 'red' },
|
||||
other: { emoji: '📝', name: '其他', color: 'blue' }
|
||||
}
|
||||
|
||||
return categoryMap[category] || categoryMap.other
|
||||
}
|
||||
|
||||
/**
|
||||
* Load GitHub reviewers per category from .github/pr-modules.yml (optional)
|
||||
* Supports inline array style: github_reviewers: ["user1","user2"] or []
|
||||
* @returns {Map<string, string[]>}
|
||||
*/
|
||||
function loadConfigGithubReviewersByCategory() {
|
||||
const result = new Map()
|
||||
result.__rules = { vendor_added: [], large_change: { changed_files_gt: 30, reviewers: [] } }
|
||||
try {
|
||||
const candidates = [
|
||||
path.join(process.cwd(), '.github', 'pr-modules.yml'),
|
||||
path.join(process.cwd(), '.github', 'pr-modules.yaml')
|
||||
]
|
||||
let filePath = null
|
||||
for (const p of candidates) {
|
||||
if (fs.existsSync(p)) {
|
||||
filePath = p
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!filePath) return result
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf8')
|
||||
const lines = content.split(/\r?\n/)
|
||||
let inCategories = false
|
||||
let inRules = false
|
||||
let currentCategory = null
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
if (!inCategories && !inRules) {
|
||||
if (/^categories:\s*$/.test(line)) {
|
||||
inCategories = true
|
||||
continue
|
||||
}
|
||||
if (/^rules:\s*$/.test(line)) {
|
||||
inRules = true
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (inCategories) {
|
||||
const catMatch = /^\s{2}([a-zA-Z0-9_-]+):\s*$/.exec(line)
|
||||
if (catMatch) {
|
||||
currentCategory = catMatch[1]
|
||||
continue
|
||||
}
|
||||
|
||||
if (currentCategory) {
|
||||
const reviewersMatch = /^\s{4}github_reviewers:\s*(.*)$/.exec(line)
|
||||
if (reviewersMatch) {
|
||||
let value = (reviewersMatch[1] || '').trim()
|
||||
let users = []
|
||||
if (value.startsWith('[') && value.endsWith(']')) {
|
||||
const inner = value.slice(1, -1).trim()
|
||||
if (inner.length > 0) {
|
||||
users = inner
|
||||
.split(',')
|
||||
.map((s) => s.trim().replace(/^"|"$/g, '').replace(/^'|'$/g, ''))
|
||||
.filter(Boolean)
|
||||
}
|
||||
} else if (value === '' || value === '[]') {
|
||||
// try to parse dash list style
|
||||
const collected = []
|
||||
let j = i + 1
|
||||
while (j < lines.length) {
|
||||
const l = lines[j]
|
||||
const dash = /^\s{6}-\s*(["']?)([^"']*)\1\s*$/.exec(l)
|
||||
if (dash) {
|
||||
const user = dash[2].trim()
|
||||
if (user) collected.push(user)
|
||||
j++
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
users = collected
|
||||
}
|
||||
result.set(currentCategory, Array.from(new Set(users)))
|
||||
}
|
||||
}
|
||||
} else if (inRules) {
|
||||
// vendor_added block
|
||||
if (/^\s{2}vendor_added:\s*$/.test(line)) {
|
||||
// parse github_reviewers under vendor_added
|
||||
let j = i + 1
|
||||
const reviewers = []
|
||||
while (j < lines.length) {
|
||||
const l = lines[j]
|
||||
const reviewersLine = /^\s{4}github_reviewers:\s*(.*)$/.exec(l)
|
||||
if (reviewersLine) {
|
||||
let value = (reviewersLine[1] || '').trim()
|
||||
if (value.startsWith('[') && value.endsWith(']')) {
|
||||
const inner = value.slice(1, -1).trim()
|
||||
if (inner.length > 0) {
|
||||
inner.split(',').forEach((s) => {
|
||||
const u = s.trim().replace(/^"|"$/g, '').replace(/^'|'$/g, '')
|
||||
if (u) reviewers.push(u)
|
||||
})
|
||||
}
|
||||
}
|
||||
j++
|
||||
continue
|
||||
}
|
||||
const dash = /^\s{6}-\s*(["']?)([^"']*)\1\s*$/.exec(l)
|
||||
if (dash) {
|
||||
const u = dash[2].trim()
|
||||
if (u) reviewers.push(u)
|
||||
j++
|
||||
continue
|
||||
}
|
||||
if (/^\s{2}[a-zA-Z0-9_-]+:\s*$/.test(l)) break
|
||||
j++
|
||||
}
|
||||
result.__rules.vendor_added = Array.from(new Set(reviewers))
|
||||
}
|
||||
|
||||
// large_change block
|
||||
if (/^\s{2}large_change:\s*$/.test(line)) {
|
||||
let j = i + 1
|
||||
const rule = { changed_files_gt: 30, reviewers: [] }
|
||||
while (j < lines.length) {
|
||||
const l = lines[j]
|
||||
const threshold = /^\s{4}changed_files_gt:\s*(\d+)\s*$/.exec(l)
|
||||
if (threshold) {
|
||||
rule.changed_files_gt = parseInt(threshold[1], 10)
|
||||
j++
|
||||
continue
|
||||
}
|
||||
const reviewersLine = /^\s{4}github_reviewers:\s*(.*)$/.exec(l)
|
||||
if (reviewersLine) {
|
||||
let value = (reviewersLine[1] || '').trim()
|
||||
if (value.startsWith('[') && value.endsWith(']')) {
|
||||
const inner = value.slice(1, -1).trim()
|
||||
if (inner.length > 0) {
|
||||
inner.split(',').forEach((s) => {
|
||||
const u = s.trim().replace(/^"|"$/g, '').replace(/^'|'$/g, '')
|
||||
if (u) rule.reviewers.push(u)
|
||||
})
|
||||
}
|
||||
}
|
||||
j++
|
||||
continue
|
||||
}
|
||||
const dash = /^\s{6}-\s*(["']?)([^"']*)\1\s*$/.exec(l)
|
||||
if (dash) {
|
||||
const u = dash[2].trim()
|
||||
if (u) rule.reviewers.push(u)
|
||||
j++
|
||||
continue
|
||||
}
|
||||
if (/^\s{2}[a-zA-Z0-9_-]+:\s*$/.test(l)) break
|
||||
j++
|
||||
}
|
||||
rule.reviewers = Array.from(new Set(rule.reviewers))
|
||||
result.__rules.large_change = rule
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('⚠️ Failed to load .github/pr-modules.yml:', e.message)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recommended reviewers based on PR category
|
||||
* This is a helper for Claude to suggest appropriate reviewers
|
||||
* @param {string} category - PR category
|
||||
* @param {Map<string, string>} userMapping - GitHub to Feishu user mapping
|
||||
* @returns {string[]} List of Feishu user IDs to notify
|
||||
*/
|
||||
function getRecommendedReviewersByCategory(category, userMapping, configGithubReviewersMap) {
|
||||
// Fallback mapping when config not provided
|
||||
const fallback = {
|
||||
backend: ['kangfenmao'],
|
||||
ai_core: ['kangfenmao'],
|
||||
'build-config': ['kangfenmao'],
|
||||
multiple: ['kangfenmao']
|
||||
}
|
||||
|
||||
const configUsers = (configGithubReviewersMap && configGithubReviewersMap.get(category)) || []
|
||||
const fallbackUsers = fallback[category] || []
|
||||
const githubUsers = Array.from(new Set([...configUsers, ...fallbackUsers]))
|
||||
return githubUsers.map((gh) => userMapping.get(gh)).filter(Boolean)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Feishu card message from PR data
|
||||
* @param {object} prData - GitHub PR data
|
||||
* @param {Map<string, string>} userMapping - GitHub to Feishu user mapping
|
||||
* @returns {object} Feishu card content
|
||||
*/
|
||||
function createPRCard(prData, userMapping, configGithubReviewersMap) {
|
||||
const {
|
||||
prUrl,
|
||||
prNumber,
|
||||
prTitle,
|
||||
prSummary,
|
||||
prAuthor,
|
||||
labels,
|
||||
reviewers,
|
||||
assignees,
|
||||
category,
|
||||
changedFiles,
|
||||
additions,
|
||||
deletions,
|
||||
vendorAdded
|
||||
} = prData
|
||||
|
||||
const categoryInfo = getCategoryInfo(category)
|
||||
|
||||
// Build labels section
|
||||
const labelElements =
|
||||
labels && labels.length > 0
|
||||
? [
|
||||
{
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `**🏷️ Labels:** ${labels.map((l) => `\`${l}\``).join(' ')}`
|
||||
}
|
||||
}
|
||||
]
|
||||
: []
|
||||
|
||||
// Build stats section
|
||||
const statsContent = [
|
||||
`📁 ${changedFiles || 0} files`,
|
||||
`<font color='green'>+${additions || 0}</font>`,
|
||||
`<font color='red'>-${deletions || 0}</font>`
|
||||
].join(' · ')
|
||||
|
||||
// Build mention content for reviewers and assignees
|
||||
const mentions = []
|
||||
const mentionedUsers = new Set()
|
||||
|
||||
// Add reviewers
|
||||
if (reviewers && reviewers.length > 0) {
|
||||
reviewers.forEach((reviewer) => {
|
||||
const feishuId = userMapping.get(reviewer)
|
||||
if (feishuId && !mentionedUsers.has(feishuId)) {
|
||||
mentions.push(`<at id="${feishuId}"></at>`)
|
||||
mentionedUsers.add(feishuId)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Add assignees
|
||||
if (assignees && assignees.length > 0) {
|
||||
assignees.forEach((assignee) => {
|
||||
const feishuId = userMapping.get(assignee)
|
||||
if (feishuId && !mentionedUsers.has(feishuId)) {
|
||||
mentions.push(`<at id="${feishuId}"></at>`)
|
||||
mentionedUsers.add(feishuId)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Add category-based experts (if not already mentioned)
|
||||
const categoryExperts = getRecommendedReviewersByCategory(category, userMapping, configGithubReviewersMap)
|
||||
categoryExperts.forEach((feishuId) => {
|
||||
if (feishuId && !mentionedUsers.has(feishuId)) {
|
||||
mentions.push(`<at id="${feishuId}"></at>`)
|
||||
mentionedUsers.add(feishuId)
|
||||
}
|
||||
})
|
||||
|
||||
// Enforce mandatory reviewers based on rules
|
||||
const mandatoryGithubUsers = []
|
||||
const rules = configGithubReviewersMap.__rules || {
|
||||
vendor_added: [],
|
||||
large_change: { changed_files_gt: 30, reviewers: [] }
|
||||
}
|
||||
if (vendorAdded) {
|
||||
mandatoryGithubUsers.push(...(rules.vendor_added || ['Yinsen-Ho']))
|
||||
}
|
||||
const changedFilesNum = Number(changedFiles) || 0
|
||||
const threshold = (rules.large_change && rules.large_change.changed_files_gt) || 30
|
||||
if (changedFilesNum > threshold) {
|
||||
const reviewers = (rules.large_change && rules.large_change.reviewers) || ['kangfenmao']
|
||||
mandatoryGithubUsers.push(...reviewers)
|
||||
}
|
||||
|
||||
mandatoryGithubUsers.forEach((gh) => {
|
||||
const feishuId = userMapping.get(gh)
|
||||
if (feishuId && !mentionedUsers.has(feishuId)) {
|
||||
mentions.push(`<at id="${feishuId}"></at>`)
|
||||
mentionedUsers.add(feishuId)
|
||||
}
|
||||
})
|
||||
|
||||
// Build mentions section
|
||||
const mentionElements =
|
||||
mentions.length > 0
|
||||
? [
|
||||
{
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `**👥 请关注:** ${mentions.join(' ')}`
|
||||
}
|
||||
}
|
||||
]
|
||||
: []
|
||||
|
||||
// Build reviewer and assignee info
|
||||
const reviewerInfo = []
|
||||
if (reviewers && reviewers.length > 0) {
|
||||
reviewerInfo.push({
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `**👀 Reviewers:** ${reviewers.map((r) => `\`${r}\``).join(', ')}`
|
||||
}
|
||||
})
|
||||
}
|
||||
if (assignees && assignees.length > 0) {
|
||||
reviewerInfo.push({
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `**👤 Assignees:** ${assignees.map((a) => `\`${a}\``).join(', ')}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
elements: [
|
||||
{
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `**🔀 New Pull Request #${prNumber}**`
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: 'hr'
|
||||
},
|
||||
{
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `**${categoryInfo.emoji} 类型:** ${categoryInfo.name}`
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `**📝 Title:** ${prTitle}`
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `**👤 Author:** \`${prAuthor}\``
|
||||
}
|
||||
},
|
||||
...reviewerInfo,
|
||||
...labelElements,
|
||||
{
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `**📊 Changes:** ${statsContent}`
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: 'hr'
|
||||
},
|
||||
{
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `**📋 Summary:**\n${prSummary}`
|
||||
}
|
||||
},
|
||||
...mentionElements,
|
||||
{
|
||||
tag: 'hr'
|
||||
},
|
||||
{
|
||||
tag: 'action',
|
||||
actions: [
|
||||
{
|
||||
tag: 'button',
|
||||
text: {
|
||||
tag: 'plain_text',
|
||||
content: '🔗 View PR'
|
||||
},
|
||||
type: 'primary',
|
||||
url: prUrl
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
header: {
|
||||
template: categoryInfo.color,
|
||||
title: {
|
||||
tag: 'plain_text',
|
||||
content: `${categoryInfo.emoji} Cherry Studio - New PR [${categoryInfo.name}]`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function
|
||||
*/
|
||||
async function main() {
|
||||
try {
|
||||
// Get environment variables
|
||||
const webhookUrl = process.env.FEISHU_WEBHOOK_URL
|
||||
const secret = process.env.FEISHU_WEBHOOK_SECRET
|
||||
const userMappingStr = process.env.FEISHU_USER_MAPPING || ''
|
||||
|
||||
const prUrl = process.env.PR_URL
|
||||
const prNumber = process.env.PR_NUMBER
|
||||
const prTitle = process.env.PR_TITLE
|
||||
const prSummary = process.env.PR_SUMMARY
|
||||
const prAuthor = process.env.PR_AUTHOR
|
||||
const labelsStr = process.env.PR_LABELS || ''
|
||||
const reviewersStr = process.env.PR_REVIEWERS || ''
|
||||
const assigneesStr = process.env.PR_ASSIGNEES || ''
|
||||
const category = process.env.PR_CATEGORY || 'multiple'
|
||||
const vendorAdded = String(process.env.PR_VENDOR_ADDED || 'false').toLowerCase() === 'true'
|
||||
const changedFiles = process.env.PR_CHANGED_FILES || '0'
|
||||
const additions = process.env.PR_ADDITIONS || '0'
|
||||
const deletions = process.env.PR_DELETIONS || '0'
|
||||
|
||||
// Validate required environment variables
|
||||
if (!webhookUrl) {
|
||||
throw new Error('FEISHU_WEBHOOK_URL environment variable is required')
|
||||
}
|
||||
if (!secret) {
|
||||
throw new Error('FEISHU_WEBHOOK_SECRET environment variable is required')
|
||||
}
|
||||
if (!prUrl || !prNumber || !prTitle || !prSummary) {
|
||||
throw new Error('PR data environment variables are required')
|
||||
}
|
||||
|
||||
// Parse data
|
||||
const userMapping = parseUserMapping(userMappingStr)
|
||||
const configGithubReviewersMap = loadConfigGithubReviewersByCategory()
|
||||
|
||||
const labels = labelsStr
|
||||
? labelsStr
|
||||
.split(',')
|
||||
.map((l) => l.trim())
|
||||
.filter(Boolean)
|
||||
: []
|
||||
|
||||
const reviewers = reviewersStr
|
||||
? reviewersStr
|
||||
.split(',')
|
||||
.map((r) => r.trim())
|
||||
.filter(Boolean)
|
||||
: []
|
||||
|
||||
const assignees = assigneesStr
|
||||
? assigneesStr
|
||||
.split(',')
|
||||
.map((a) => a.trim())
|
||||
.filter(Boolean)
|
||||
: []
|
||||
|
||||
// Create PR data object
|
||||
const prData = {
|
||||
prUrl,
|
||||
prNumber,
|
||||
prTitle,
|
||||
prSummary,
|
||||
prAuthor: prAuthor || 'Unknown',
|
||||
labels,
|
||||
reviewers,
|
||||
assignees,
|
||||
category,
|
||||
vendorAdded,
|
||||
changedFiles,
|
||||
additions,
|
||||
deletions
|
||||
}
|
||||
|
||||
console.log('📤 Sending PR notification to Feishu...')
|
||||
console.log(`PR #${prNumber}: ${prTitle}`)
|
||||
console.log(`Category: ${category}`)
|
||||
console.log(`Vendor added: ${vendorAdded}`)
|
||||
console.log(`Reviewers: ${reviewers.join(', ') || 'None'}`)
|
||||
console.log(`Assignees: ${assignees.join(', ') || 'None'}`)
|
||||
console.log(`User mapping entries: ${userMapping.size}`)
|
||||
|
||||
// Create card content
|
||||
const card = createPRCard(prData, userMapping, configGithubReviewersMap)
|
||||
|
||||
// Send to Feishu
|
||||
await sendToFeishu(webhookUrl, secret, card)
|
||||
|
||||
console.log('✅ PR notification sent successfully!')
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error.message)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Run main function
|
||||
main()
|
||||
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* Stats major contributors per module based on .github/pr-modules.yml
|
||||
* Output a markdown summary and write JSON to .github/reviewer-suggestions.json
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/stats-contributors.js [--top 3] [--since 1.year] [--mode auto|shortlog|log|blame] [--blame-sample 30]
|
||||
*/
|
||||
|
||||
const { spawnSync } = require('child_process')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
function readText(file) {
|
||||
try {
|
||||
return fs.readFileSync(file, 'utf8')
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs() {
|
||||
const args = process.argv.slice(2)
|
||||
const out = { top: 3, since: '', mode: 'auto', blameSample: 30 }
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--top' && i + 1 < args.length) {
|
||||
out.top = parseInt(args[++i], 10) || 3
|
||||
} else if (args[i] === '--since' && i + 1 < args.length) {
|
||||
out.since = String(args[++i])
|
||||
} else if (args[i] === '--mode' && i + 1 < args.length) {
|
||||
out.mode = String(args[++i])
|
||||
} else if (args[i] === '--blame-sample' && i + 1 < args.length) {
|
||||
out.blameSample = parseInt(args[++i], 10) || 30
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Minimal YAML parser for categories/globs in .github/pr-modules.yml
|
||||
function parseModulesConfig(configPath) {
|
||||
const text = readText(configPath)
|
||||
if (!text) throw new Error(`Cannot read ${configPath}`)
|
||||
const lines = text.split(/\r?\n/)
|
||||
const categories = []
|
||||
let inCategories = false
|
||||
let current = null
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
if (!inCategories) {
|
||||
if (/^categories:\s*$/.test(line)) inCategories = true
|
||||
continue
|
||||
}
|
||||
|
||||
// New category key
|
||||
const catMatch = /^\s{2}([a-zA-Z0-9_-]+):\s*$/.exec(line)
|
||||
if (catMatch) {
|
||||
if (current) categories.push(current)
|
||||
current = { key: catMatch[1], name: '', globs: [] }
|
||||
continue
|
||||
}
|
||||
|
||||
if (!current) continue
|
||||
|
||||
const nameMatch = /^\s{4}name:\s*"?([^"]+)"?\s*$/.exec(line)
|
||||
if (nameMatch) {
|
||||
current.name = nameMatch[1].trim()
|
||||
continue
|
||||
}
|
||||
|
||||
// Enter globs list, then collect dash items
|
||||
const globsHeader = /^\s{4}globs:\s*$/.exec(line)
|
||||
if (globsHeader) {
|
||||
let j = i + 1
|
||||
while (j < lines.length) {
|
||||
const l = lines[j]
|
||||
const item = /^\s{6}-\s*"?([^"]+)"?\s*$/.exec(l)
|
||||
if (!item) break
|
||||
current.globs.push(item[1].trim())
|
||||
j++
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (current) categories.push(current)
|
||||
return categories
|
||||
}
|
||||
|
||||
function git(args, cwd) {
|
||||
const res = spawnSync('git', args, { cwd, encoding: 'utf8' })
|
||||
if (res.status !== 0) {
|
||||
const msg = (res.stderr || '').trim() || `git ${args.join(' ')} failed`
|
||||
throw new Error(msg)
|
||||
}
|
||||
return res.stdout
|
||||
}
|
||||
|
||||
function buildPathspecs(globs) {
|
||||
// Use pathspec magic :(glob)pattern so that ** works and we avoid shell expansion
|
||||
return globs.map((g) => `:(glob)${g}`)
|
||||
}
|
||||
|
||||
function lsFilesForGlobs(globs, repoRoot) {
|
||||
const pathspecs = buildPathspecs(globs)
|
||||
if (pathspecs.length === 0) return []
|
||||
try {
|
||||
const stdout = git(['ls-files', '--', ...pathspecs], repoRoot)
|
||||
return stdout
|
||||
.split(/\r?\n/)
|
||||
.map((l) => l.trim())
|
||||
.filter(Boolean)
|
||||
} catch (e) {
|
||||
// No matched files or pathspec error → treat as empty
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function shortlogFor(globs, repoRoot, since) {
|
||||
const files = lsFilesForGlobs(globs, repoRoot)
|
||||
if (files.length === 0) return []
|
||||
const base = ['shortlog', '-sne']
|
||||
if (since) base.push(`--since=${since}`)
|
||||
const stdout = git([...base, '--', ...files], repoRoot)
|
||||
const lines = stdout
|
||||
.split(/\r?\n/)
|
||||
.map((l) => l.trim())
|
||||
.filter(Boolean)
|
||||
const rows = []
|
||||
for (const l of lines) {
|
||||
// e.g. " 42 John Doe <john@example.com>"
|
||||
const m = /^(\d+)\s+(.+?)\s+<([^>]+)>$/.exec(l)
|
||||
if (!m) continue
|
||||
const commits = parseInt(m[1], 10)
|
||||
const name = m[2]
|
||||
const email = m[3]
|
||||
const gh = extractGithubUsername(name, email)
|
||||
rows.push({ commits, name, email, github: gh })
|
||||
}
|
||||
rows.sort((a, b) => b.commits - a.commits)
|
||||
return rows
|
||||
}
|
||||
|
||||
function logAuthorsFor(globs, repoRoot, since) {
|
||||
const files = lsFilesForGlobs(globs, repoRoot)
|
||||
if (files.length === 0) return []
|
||||
const base = ['log', '--format=%an <%ae>']
|
||||
if (since) base.push(`--since=${since}`)
|
||||
const stdout = git([...base, '--', ...files], repoRoot)
|
||||
const lines = stdout
|
||||
.split(/\r?\n/)
|
||||
.map((l) => l.trim())
|
||||
.filter(Boolean)
|
||||
const map = new Map()
|
||||
for (const l of lines) {
|
||||
const m = /^(.+?)\s+<([^>]+)>$/.exec(l)
|
||||
if (!m) continue
|
||||
const name = m[1]
|
||||
const email = m[2]
|
||||
const gh = extractGithubUsername(name, email)
|
||||
const key = `${name} <${email}>`
|
||||
map.set(key, (map.get(key) || 0) + 1)
|
||||
}
|
||||
const out = []
|
||||
for (const [key, commits] of map.entries()) {
|
||||
const m = /^(.+?)\s+<([^>]+)>$/.exec(key)
|
||||
out.push({ commits, name: m[1], email: m[2], github: extractGithubUsername(m[1], m[2]) })
|
||||
}
|
||||
out.sort((a, b) => b.commits - a.commits)
|
||||
return out
|
||||
}
|
||||
|
||||
function blameAuthorsSample(globs, repoRoot, sample) {
|
||||
const files = lsFilesForGlobs(globs, repoRoot)
|
||||
if (files.length === 0) return []
|
||||
const pick = files.slice(0, Math.max(1, sample))
|
||||
const map = new Map()
|
||||
for (const f of pick) {
|
||||
let stdout = ''
|
||||
try {
|
||||
stdout = git(['blame', '--line-porcelain', '--', f], repoRoot)
|
||||
} catch (e) {
|
||||
continue
|
||||
}
|
||||
const lines = stdout.split(/\r?\n/)
|
||||
for (const line of lines) {
|
||||
// author and author-mail lines
|
||||
const am = /^author-mail\s+<([^>]+)>$/.exec(line)
|
||||
if (am) {
|
||||
const email = am[1]
|
||||
// We do not rely on index; we just keep email-based identity
|
||||
const gh = extractGithubUsername('', email)
|
||||
const key = `${gh || ''}<${email}>`
|
||||
map.set(key, (map.get(key) || 0) + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
const out = []
|
||||
for (const [key, commits] of map.entries()) {
|
||||
const m = /^(.*?)<([^>]+)>$/.exec(key)
|
||||
const email = m ? m[2] : ''
|
||||
const gh = extractGithubUsername('', email)
|
||||
out.push({ commits, name: gh || email, email, github: gh })
|
||||
}
|
||||
out.sort((a, b) => b.commits - a.commits)
|
||||
return out
|
||||
}
|
||||
|
||||
function extractGithubUsername(name, email) {
|
||||
// Try noreply forms: 12345+user@users.noreply.github.com or user@users.noreply.github.com
|
||||
const noreply = /^(?:\d+\+)?([A-Za-z0-9-]+)@users\.noreply\.github\.com$/.exec(email)
|
||||
if (noreply) return noreply[1]
|
||||
// If name itself looks like a probable GitHub handle
|
||||
if (/^[A-Za-z0-9-]{3,}$/.test(name)) return name
|
||||
return ''
|
||||
}
|
||||
|
||||
function main() {
|
||||
const repoRoot = process.cwd()
|
||||
const { top, since, mode, blameSample } = parseArgs()
|
||||
const configPath = path.join(repoRoot, '.github', 'pr-modules.yml')
|
||||
const categories = parseModulesConfig(configPath)
|
||||
|
||||
const suggestions = {}
|
||||
const markdownLines = []
|
||||
markdownLines.push('| Module | Top Contributors (commits) |')
|
||||
markdownLines.push('|---|---|')
|
||||
|
||||
for (const cat of categories) {
|
||||
let rows = []
|
||||
try {
|
||||
if (mode === 'shortlog' || mode === 'auto') rows = shortlogFor(cat.globs, repoRoot, since)
|
||||
if (rows.length === 0 && (mode === 'log' || mode === 'auto')) rows = logAuthorsFor(cat.globs, repoRoot, since)
|
||||
if (rows.length === 0 && (mode === 'blame' || mode === 'auto'))
|
||||
rows = blameAuthorsSample(cat.globs, repoRoot, blameSample)
|
||||
} catch (e) {
|
||||
// Fallback to next method if one fails
|
||||
if (mode === 'auto') {
|
||||
try {
|
||||
rows = logAuthorsFor(cat.globs, repoRoot, since)
|
||||
} catch (e2) {
|
||||
// ignore and continue
|
||||
}
|
||||
if (rows.length === 0) {
|
||||
try {
|
||||
rows = blameAuthorsSample(cat.globs, repoRoot, blameSample)
|
||||
} catch (e3) {
|
||||
// ignore and continue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Non-auto mode: report empty on error
|
||||
rows = []
|
||||
}
|
||||
}
|
||||
const topRows = rows.slice(0, top)
|
||||
suggestions[cat.key] = topRows.map((r) => ({
|
||||
github: r.github,
|
||||
name: r.name,
|
||||
email: r.email,
|
||||
commits: r.commits
|
||||
}))
|
||||
const cell = topRows
|
||||
.map((r) => {
|
||||
const id = r.github ? `@${r.github}` : r.name
|
||||
return `${id} (${r.commits})`
|
||||
})
|
||||
.join(', ')
|
||||
markdownLines.push(`| ${cat.key} | ${cell || '-'} |`)
|
||||
}
|
||||
|
||||
const outJsonPath = path.join(repoRoot, '.github', 'reviewer-suggestions.json')
|
||||
fs.writeFileSync(outJsonPath, JSON.stringify({ generatedAt: new Date().toISOString(), suggestions }, null, 2))
|
||||
|
||||
console.log(markdownLines.join('\n'))
|
||||
console.log(`\nSaved JSON: ${path.relative(repoRoot, outJsonPath)}`)
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ApiServerConfig } from '@types'
|
||||
import { ApiServerConfig } from '@types'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { loggerService } from '../services/LoggerService'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import crypto from 'crypto'
|
||||
import type { NextFunction, Request, Response } from 'express'
|
||||
import { NextFunction, Request, Response } from 'express'
|
||||
|
||||
import { config } from '../config'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { NextFunction, Request, Response } from 'express'
|
||||
import { NextFunction, Request, Response } from 'express'
|
||||
|
||||
import { loggerService } from '../../services/LoggerService'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Express } from 'express'
|
||||
import { Express } from 'express'
|
||||
import swaggerJSDoc from 'swagger-jsdoc'
|
||||
import swaggerUi from 'swagger-ui-express'
|
||||
|
||||
@@ -171,7 +171,7 @@ const swaggerOptions: swaggerJSDoc.Options = {
|
||||
}
|
||||
]
|
||||
},
|
||||
apis: ['./src/main/apiServer/routes/**/*.ts', './src/main/apiServer/app.ts']
|
||||
apis: ['./src/main/apiServer/routes/*.ts', './src/main/apiServer/app.ts']
|
||||
}
|
||||
|
||||
export function setupOpenAPIDocumentation(app: Express) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { AgentModelValidationError, agentService, sessionService } from '@main/services/agents'
|
||||
import type { ListAgentsResponse } from '@types'
|
||||
import { type ReplaceAgentRequest, type UpdateAgentRequest } from '@types'
|
||||
import type { Request, Response } from 'express'
|
||||
import { ListAgentsResponse, type ReplaceAgentRequest, type UpdateAgentRequest } from '@types'
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
import type { ValidationRequest } from '../validators/zodValidator'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { loggerService } from '@logger'
|
||||
import { MESSAGE_STREAM_TIMEOUT_MS } from '@main/apiServer/config/timeouts'
|
||||
import { createStreamAbortController, STREAM_TIMEOUT_REASON } from '@main/apiServer/utils/createStreamAbortController'
|
||||
import { agentService, sessionMessageService, sessionService } from '@main/services/agents'
|
||||
import type { Request, Response } from 'express'
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
const logger = loggerService.withContext('ApiServerMessagesHandlers')
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { AgentModelValidationError, sessionMessageService, sessionService } from '@main/services/agents'
|
||||
import type { ListAgentSessionsResponse, UpdateSessionResponse } from '@types'
|
||||
import { type ReplaceSessionRequest } from '@types'
|
||||
import type { Request, Response } from 'express'
|
||||
import { ListAgentSessionsResponse, type ReplaceSessionRequest, UpdateSessionResponse } from '@types'
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
import type { ValidationRequest } from '../validators/zodValidator'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Request, Response } from 'express'
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
import { agentService } from '../../../../services/agents'
|
||||
import { loggerService } from '../../../../services/LoggerService'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { NextFunction, Request, Response } from 'express'
|
||||
import type { ZodType } from 'zod'
|
||||
import { ZodError } from 'zod'
|
||||
import { NextFunction, Request, Response } from 'express'
|
||||
import { ZodError, ZodType } from 'zod'
|
||||
|
||||
export interface ValidationRequest extends Request {
|
||||
validatedBody?: any
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ChatCompletionCreateParams } from '@cherrystudio/openai/resources'
|
||||
import type { Request, Response } from 'express'
|
||||
import express from 'express'
|
||||
import { ChatCompletionCreateParams } from '@cherrystudio/openai/resources'
|
||||
import express, { Request, Response } from 'express'
|
||||
|
||||
import { loggerService } from '../../services/LoggerService'
|
||||
import {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Request, Response } from 'express'
|
||||
import express from 'express'
|
||||
import express, { Request, Response } from 'express'
|
||||
|
||||
import { loggerService } from '../../services/LoggerService'
|
||||
import { mcpApiService } from '../services/mcp'
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { MessageCreateParams } from '@anthropic-ai/sdk/resources'
|
||||
import { MessageCreateParams } from '@anthropic-ai/sdk/resources'
|
||||
import { loggerService } from '@logger'
|
||||
import type { Provider } from '@types'
|
||||
import type { Request, Response } from 'express'
|
||||
import express from 'express'
|
||||
import { Provider } from '@types'
|
||||
import express, { Request, Response } from 'express'
|
||||
|
||||
import { messagesService } from '../services/messages'
|
||||
import { getProviderById, validateModelId } from '../utils'
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { ApiModelsResponse } from '@types'
|
||||
import { ApiModelsFilterSchema } from '@types'
|
||||
import type { Request, Response } from 'express'
|
||||
import express from 'express'
|
||||
import { ApiModelsFilterSchema, ApiModelsResponse } from '@types'
|
||||
import express, { Request, Response } from 'express'
|
||||
|
||||
import { loggerService } from '../../services/LoggerService'
|
||||
import { modelsService } from '../services/models'
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { createServer } from 'node:http'
|
||||
|
||||
import { loggerService } from '@logger'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
|
||||
import { agentService } from '../services/agents'
|
||||
import { windowService } from '../services/WindowService'
|
||||
import { app } from './app'
|
||||
import { config } from './config'
|
||||
|
||||
@@ -45,13 +43,6 @@ export class ApiServer {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.server!.listen(port, host, () => {
|
||||
logger.info('API server started', { host, port })
|
||||
|
||||
// Notify renderer that API server is ready
|
||||
const mainWindow = windowService.getMainWindow()
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send(IpcChannel.ApiServer_Ready)
|
||||
}
|
||||
|
||||
resolve()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import OpenAI from '@cherrystudio/openai'
|
||||
import type { ChatCompletionCreateParams, ChatCompletionCreateParamsStreaming } from '@cherrystudio/openai/resources'
|
||||
import type { Provider } from '@types'
|
||||
import { ChatCompletionCreateParams, ChatCompletionCreateParamsStreaming } from '@cherrystudio/openai/resources'
|
||||
import { Provider } from '@types'
|
||||
|
||||
import { loggerService } from '../../services/LoggerService'
|
||||
import type { ModelValidationError } from '../utils'
|
||||
import { validateModelId } from '../utils'
|
||||
import { ModelValidationError, validateModelId } from '../utils'
|
||||
|
||||
const logger = loggerService.withContext('ChatCompletionService')
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import mcpService from '@main/services/MCPService'
|
||||
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp'
|
||||
import type { JSONRPCMessage, MessageExtraInfo } from '@modelcontextprotocol/sdk/types'
|
||||
import { isJSONRPCRequest, JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types'
|
||||
import type { MCPServer } from '@types'
|
||||
import {
|
||||
isJSONRPCRequest,
|
||||
JSONRPCMessage,
|
||||
JSONRPCMessageSchema,
|
||||
MessageExtraInfo
|
||||
} from '@modelcontextprotocol/sdk/types'
|
||||
import { MCPServer } from '@types'
|
||||
import { randomUUID } from 'crypto'
|
||||
import { EventEmitter } from 'events'
|
||||
import type { Request, Response } from 'express'
|
||||
import type { IncomingMessage, ServerResponse } from 'http'
|
||||
import { Request, Response } from 'express'
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
|
||||
import { loggerService } from '../../services/LoggerService'
|
||||
import { getMcpServerById, getMCPServersFromRedux } from '../utils/mcp'
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type Anthropic from '@anthropic-ai/sdk'
|
||||
import type { MessageCreateParams, MessageStreamEvent } from '@anthropic-ai/sdk/resources'
|
||||
import Anthropic from '@anthropic-ai/sdk'
|
||||
import { MessageCreateParams, MessageStreamEvent } from '@anthropic-ai/sdk/resources'
|
||||
import { loggerService } from '@logger'
|
||||
import anthropicService from '@main/services/AnthropicService'
|
||||
import { buildClaudeCodeSystemMessage, getSdkClient } from '@shared/anthropic'
|
||||
import type { Provider } from '@types'
|
||||
import type { Response } from 'express'
|
||||
import { Provider } from '@types'
|
||||
import { Response } from 'express'
|
||||
|
||||
const logger = loggerService.withContext('MessagesService')
|
||||
const EXCLUDED_FORWARD_HEADERS: ReadonlySet<string> = new Set([
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { isEmpty } from 'lodash'
|
||||
|
||||
import type { ApiModel, ApiModelsFilter, ApiModelsResponse } from '../../../renderer/src/types/apiModels'
|
||||
import { ApiModel, ApiModelsFilter, ApiModelsResponse } from '../../../renderer/src/types/apiModels'
|
||||
import { loggerService } from '../../services/LoggerService'
|
||||
import {
|
||||
getAvailableProviders,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CacheService } from '@main/services/CacheService'
|
||||
import { loggerService } from '@main/services/LoggerService'
|
||||
import { reduxService } from '@main/services/ReduxService'
|
||||
import type { ApiModel, Model, Provider } from '@types'
|
||||
import { ApiModel, Model, Provider } from '@types'
|
||||
|
||||
const logger = loggerService.withContext('ApiServerUtils')
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { CacheService } from '@main/services/CacheService'
|
||||
import mcpService from '@main/services/MCPService'
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
||||
import type { ListToolsResult } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
|
||||
import type { MCPServer } from '@types'
|
||||
import { CallToolRequestSchema, ListToolsRequestSchema, ListToolsResult } from '@modelcontextprotocol/sdk/types.js'
|
||||
import { MCPServer } from '@types'
|
||||
|
||||
import { loggerService } from '../../services/LoggerService'
|
||||
import { reduxService } from '../../services/ReduxService'
|
||||
|
||||
@@ -21,7 +21,6 @@ import { appMenuService } from './services/AppMenuService'
|
||||
import { configManager } from './services/ConfigManager'
|
||||
import mcpService from './services/MCPService'
|
||||
import { nodeTraceService } from './services/NodeTraceService'
|
||||
import powerMonitorService from './services/PowerMonitorService'
|
||||
import {
|
||||
CHERRY_STUDIO_PROTOCOL,
|
||||
handleProtocolUrl,
|
||||
@@ -31,7 +30,6 @@ import {
|
||||
import selectionService, { initSelectionService } from './services/SelectionService'
|
||||
import { registerShortcuts } from './services/ShortcutService'
|
||||
import { TrayService } from './services/TrayService'
|
||||
import { versionService } from './services/VersionService'
|
||||
import { windowService } from './services/WindowService'
|
||||
import { initWebviewHotkeys } from './services/WebviewService'
|
||||
|
||||
@@ -112,10 +110,6 @@ if (!app.requestSingleInstanceLock()) {
|
||||
// Some APIs can only be used after this event occurs.
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
// Record current version for tracking
|
||||
// A preparation for v2 data refactoring
|
||||
versionService.recordCurrentVersion()
|
||||
|
||||
initWebviewHotkeys()
|
||||
// Set app user model id for windows
|
||||
electronApp.setAppUserModelId(import.meta.env.VITE_MAIN_BUNDLE_ID || 'com.kangfenmao.CherryStudio')
|
||||
@@ -133,7 +127,6 @@ if (!app.requestSingleInstanceLock()) {
|
||||
appMenuService?.setupApplicationMenu()
|
||||
|
||||
nodeTraceService.init()
|
||||
powerMonitorService.init()
|
||||
|
||||
app.on('activate', function () {
|
||||
const mainWindow = windowService.getMainWindow()
|
||||
|
||||
+7
-27
@@ -8,12 +8,11 @@ import { generateSignature } from '@main/integration/cherryai'
|
||||
import anthropicService from '@main/services/AnthropicService'
|
||||
import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process'
|
||||
import { handleZoomFactor } from '@main/utils/zoom'
|
||||
import type { SpanEntity, TokenUsage } from '@mcp-trace/trace-core'
|
||||
import type { UpgradeChannel } from '@shared/config/constant'
|
||||
import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH } from '@shared/config/constant'
|
||||
import { SpanEntity, TokenUsage } from '@mcp-trace/trace-core'
|
||||
import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, UpgradeChannel } from '@shared/config/constant'
|
||||
import { IpcChannel } from '@shared/IpcChannel'
|
||||
import type { PluginError } from '@types'
|
||||
import type {
|
||||
import {
|
||||
AgentPersistedMessage,
|
||||
FileMetadata,
|
||||
Notification,
|
||||
@@ -24,12 +23,10 @@ import type {
|
||||
ThemeMode
|
||||
} from '@types'
|
||||
import checkDiskSpace from 'check-disk-space'
|
||||
import type { ProxyConfig } from 'electron'
|
||||
import { BrowserWindow, dialog, ipcMain, session, shell, systemPreferences, webContents } from 'electron'
|
||||
import { BrowserWindow, dialog, ipcMain, ProxyConfig, session, shell, systemPreferences, webContents } from 'electron'
|
||||
import fontList from 'font-list'
|
||||
|
||||
import { agentMessageRepository } from './services/agents/database'
|
||||
import { PluginService } from './services/agents/plugins/PluginService'
|
||||
import { apiServerService } from './services/ApiServerService'
|
||||
import appService from './services/AppService'
|
||||
import AppUpdater from './services/AppUpdater'
|
||||
@@ -50,7 +47,7 @@ import * as NutstoreService from './services/NutstoreService'
|
||||
import ObsidianVaultService from './services/ObsidianVaultService'
|
||||
import { ocrService } from './services/ocr/OcrService'
|
||||
import OvmsManager from './services/OvmsManager'
|
||||
import powerMonitorService from './services/PowerMonitorService'
|
||||
import { PluginService } from './services/PluginService'
|
||||
import { proxyManager } from './services/ProxyManager'
|
||||
import { pythonService } from './services/PythonService'
|
||||
import { FileServiceManager } from './services/remotefile/FileServiceManager'
|
||||
@@ -73,7 +70,6 @@ import {
|
||||
import storeSyncService from './services/StoreSyncService'
|
||||
import { themeService } from './services/ThemeService'
|
||||
import VertexAIService from './services/VertexAIService'
|
||||
import WebSocketService from './services/WebSocketService'
|
||||
import { setOpenLinkExternal } from './services/WebviewService'
|
||||
import { windowService } from './services/WindowService'
|
||||
import { calculateDirectorySize, getResourcePath } from './utils'
|
||||
@@ -116,17 +112,8 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
const appUpdater = new AppUpdater()
|
||||
const notificationService = new NotificationService()
|
||||
|
||||
// Register shutdown handlers
|
||||
powerMonitorService.registerShutdownHandler(() => {
|
||||
appUpdater.setAutoUpdate(false)
|
||||
})
|
||||
|
||||
powerMonitorService.registerShutdownHandler(() => {
|
||||
const mw = windowService.getMainWindow()
|
||||
if (mw && !mw.isDestroyed()) {
|
||||
mw.webContents.send(IpcChannel.App_SaveData)
|
||||
}
|
||||
})
|
||||
// Initialize Python service with main window
|
||||
pythonService.setMainWindow(mainWindow)
|
||||
|
||||
const checkMainWindow = () => {
|
||||
if (!mainWindow || mainWindow.isDestroyed()) {
|
||||
@@ -1030,11 +1017,4 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
return { success: false, error }
|
||||
}
|
||||
})
|
||||
|
||||
// WebSocket
|
||||
ipcMain.handle(IpcChannel.WebSocket_Start, WebSocketService.start)
|
||||
ipcMain.handle(IpcChannel.WebSocket_Stop, WebSocketService.stop)
|
||||
ipcMain.handle(IpcChannel.WebSocket_Status, WebSocketService.getStatus)
|
||||
ipcMain.handle(IpcChannel.WebSocket_SendFile, WebSocketService.sendFile)
|
||||
ipcMain.handle(IpcChannel.WebSocket_GetAllCandidates, WebSocketService.getAllCandidates)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { BaseEmbeddings } from '@cherrystudio/embedjs-interfaces'
|
||||
import { TraceMethod } from '@mcp-trace/trace-core'
|
||||
import type { ApiClient } from '@types'
|
||||
import { ApiClient } from '@types'
|
||||
|
||||
import EmbeddingsFactory from './EmbeddingsFactory'
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { BaseEmbeddings } from '@cherrystudio/embedjs-interfaces'
|
||||
import { OllamaEmbeddings } from '@cherrystudio/embedjs-ollama'
|
||||
import { OpenAiEmbeddings } from '@cherrystudio/embedjs-openai'
|
||||
import type { ApiClient } from '@types'
|
||||
import { net } from 'electron'
|
||||
import { ApiClient } from '@types'
|
||||
|
||||
import { VoyageEmbeddings } from './VoyageEmbeddings'
|
||||
|
||||
@@ -44,7 +43,7 @@ export default class EmbeddingsFactory {
|
||||
apiKey,
|
||||
dimensions,
|
||||
batchSize,
|
||||
configuration: { baseURL, fetch: net.fetch as typeof fetch }
|
||||
configuration: { baseURL }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import type { RAGApplication } from '@cherrystudio/embedjs'
|
||||
import { JsonLoader, LocalPathLoader, TextLoader } from '@cherrystudio/embedjs'
|
||||
import { JsonLoader, LocalPathLoader, RAGApplication, TextLoader } from '@cherrystudio/embedjs'
|
||||
import type { AddLoaderReturn } from '@cherrystudio/embedjs-interfaces'
|
||||
import { WebLoader } from '@cherrystudio/embedjs-loader-web'
|
||||
import { loggerService } from '@logger'
|
||||
import { readTextFileWithAutoEncoding } from '@main/utils/file'
|
||||
import type { LoaderReturn } from '@shared/config/types'
|
||||
import type { FileMetadata, KnowledgeBaseParams } from '@types'
|
||||
import { LoaderReturn } from '@shared/config/types'
|
||||
import { FileMetadata, KnowledgeBaseParams } from '@types'
|
||||
|
||||
import { DraftsExportLoader } from './draftsExportLoader'
|
||||
import { EpubLoader } from './epubLoader'
|
||||
|
||||
@@ -3,8 +3,7 @@ import { cleanString } from '@cherrystudio/embedjs-utils'
|
||||
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters'
|
||||
import { loggerService } from '@logger'
|
||||
import md5 from 'md5'
|
||||
import type { OfficeParserConfig } from 'officeparser'
|
||||
import { parseOfficeAsync } from 'officeparser'
|
||||
import { OfficeParserConfig, parseOfficeAsync } from 'officeparser'
|
||||
|
||||
const logger = loggerService.withContext('OdLoader')
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user