Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 43 additions & 24 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,30 +159,49 @@ jobs:
with:
files: release-assets/*
body: |
## 新增功能

### 优先级分组调度

新增 Level 字段(1-10)用于供应商优先级分组,实现更灵活的降级策略。

**主要改进**:
- 后端:重构调度逻辑为两层循环架构,优先尝试高优先级分组
- 前端:添加 Level 下拉选择器和可视化徽章(L1-L10)
- 国际化:支持中英文 Level 描述文本
- 测试:新增单元测试覆盖分组、排序和序列化逻辑

**使用场景**:
- 成本优化:低成本供应商优先,高成本作为备份
- 稳定性保障:高质量供应商优先,社区供应商降级
- 地域分组:国内供应商优先,国外供应商备份

**向后兼容**:未设置 Level 的供应商自动默认为 Level 1。

## 安装说明

- **Windows**:下载 `codeswitch-amd64-installer.exe` 运行安装,或下载 `codeswitch.exe` 直接运行
- **macOS (Apple Silicon)**:下载 `codeswitch-macos-arm64.zip`
- **macOS (Intel)**:下载 `codeswitch-macos-amd64.zip`
## ✨ 新功能

### 应用自动更新
- 新增自动更新功能,支持一键更新到最新版本
- 每天 8:00 AM 自动检查 GitHub Release 更新
- 网络异常时自动重试 3 次(每次间隔 5 分钟)
- 智能重启机制:下载更新后在下次启动时自动应用

### 设置界面增强
- 设置页面新增"应用更新"板块
- 可查看当前版本、最新版本和上次检查时间
- 支持手动检查更新和立即下载安装
- 提供自动更新开关,状态持久化保存

### UI 改进
- 主页 GitHub 图标显示更新状态徽章
- 🔵 蓝色 "New" 徽章:有新版本可用
- 🟡 黄色进度徽章:正在下载更新
- 🟢 绿色 "Ready" 徽章(脉冲动画):更新已就绪,点击重启
- 完善中英文国际化翻译

## 🔧 技术改进
- 新增 UpdateService 后端服务(400+ 行)
- 版本号管理服务,当前版本 v0.2.6
- 状态持久化到 ~/.code-switch/update-state.json
- 依赖更新:github.com/hashicorp/go-version v1.7.0

## 🐛 Bug 修复
- 修复"立即检查"按钮文字换行问题
- 修复 copyFile 函数重名冲突

## ✨ UI 改进
- 新增主页刷新按钮(一键刷新所有数据)
- 添加刷新按钮旋转动画效果
- 完善按钮样式和禁用状态

## 📦 安装说明

- **Windows**:下载 `CodeSwitch-amd64-installer.exe` 运行安装,或下载 `CodeSwitch.exe` 直接运行
- **macOS (Apple Silicon)**:下载 `codeswitch-macos-arm64.zip`,解压后拖入 Applications 文件夹
- **macOS (Intel)**:下载 `codeswitch-macos-amd64.zip`,解压后拖入 Applications 文件夹

**完整更新日志**: https://github.com/Rogers-F/code-switch-R/compare/v0.2.4...v0.2.6
draft: false
prerelease: false
env:
Expand Down
320 changes: 320 additions & 0 deletions BLACKLIST_FRONTEND_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
# 供应商黑名单功能 - 前端集成指南

## 📝 概述

由于前端文件 `Index.vue` 较大(1693行),手动修改容易出错。此指南提供完整的修改步骤,或者您可以跳过此步骤,先测试后端功能。

---

## ⚡ 快速方案:先测试后端

如果您想快速验证后端功能,可以先跳过前端 UI 修改,直接测试:

1. **运行应用**:`wails3 task dev`
2. **查看后端日志**:在控制台观察是否有拉黑相关日志
3. **检查数据库**:查看 `~/.code-switch/app.db` 中的 `provider_blacklist` 表

---

## 🛠️ 完整方案:集成前端 UI

### 修改清单

| 文件 | 修改内容 | 优先级 |
|------|---------|--------|
| `frontend/src/components/Main/Index.vue` | 添加黑名单 UI 和逻辑 | 高 |
| `frontend/src/locales/zh-CN.json` | 中文文案 | 中 |
| `frontend/src/locales/en-US.json` | 英文文案 | 低 |

---

## 📄 详细修改步骤

### 1. 修改 `Index.vue` - 导入部分

**位置**:第 580 行之后

**添加**:
```typescript
import { getBlacklistStatus, manualUnblock, type BlacklistStatus } from '../../services/blacklist'
```

---

### 2. 修改 `Index.vue` - 添加状态变量

**位置**:第 637 行之后

**添加**:
```typescript
// 黑名单状态
const blacklistStatusMap = reactive<Record<ProviderTab, Record<string, BlacklistStatus>>>({
claude: {},
codex: {},
})
let blacklistTimer: number | undefined
```

---

### 3. 修改 `Index.vue` - 添加方法

**位置**:在 `loadProviderStats` 方法附近

**添加以下 4 个方法**:

```typescript
// 加载黑名单状态
const loadBlacklistStatus = async (tab: ProviderTab) => {
try {
const statuses = await getBlacklistStatus(tab)
const map: Record<string, BlacklistStatus> = {}
statuses.forEach(status => {
map[status.providerName] = status
})
blacklistStatusMap[tab] = map
} catch (err) {
console.error(`加载 ${tab} 黑名单状态失败:`, err)
}
}

// 手动解禁
const handleUnblock = async (providerName: string) => {
try {
await manualUnblock(activeTab.value, providerName)
showToast(t('components.main.blacklist.unblockSuccess', { name: providerName }), 'success')
await loadBlacklistStatus(activeTab.value)
} catch (err) {
console.error('解除拉黑失败:', err)
showToast(t('components.main.blacklist.unblockFailed'), 'error')
}
}

// 格式化倒计时
const formatBlacklistCountdown = (remainingSeconds: number): string => {
const minutes = Math.floor(remainingSeconds / 60)
const seconds = remainingSeconds % 60
return `${minutes}${t('components.main.blacklist.minutes')}${seconds}${t('components.main.blacklist.seconds')}`
}

// 获取 provider 黑名单状态
const getProviderBlacklistStatus = (providerName: string): BlacklistStatus | null => {
return blacklistStatusMap[activeTab.value][providerName] || null
}
```

---

### 4. 修改 `Index.vue` - 修改生命周期钩子

#### 4.1 在 `onMounted` 中添加定时器

**位置**:在现有定时器之后

**添加**:
```typescript
// 加载初始黑名单状态
loadBlacklistStatus(activeTab.value)

// 每秒更新黑名单倒计时
blacklistTimer = window.setInterval(() => {
const tab = activeTab.value
Object.keys(blacklistStatusMap[tab]).forEach(providerName => {
const status = blacklistStatusMap[tab][providerName]
if (status && status.isBlacklisted && status.remainingSeconds > 0) {
status.remainingSeconds--
if (status.remainingSeconds <= 0) {
loadBlacklistStatus(tab)
}
}
})
}, 1000)
```

#### 4.2 在 `onUnmounted` 中清理定时器

**位置**:在现有清理代码之后

**添加**:
```typescript
if (blacklistTimer) {
window.clearInterval(blacklistTimer)
}
```

---

### 5. 修改 `Index.vue` - 模板部分

**位置**:第 353 行的 `</p>` 之后

**添加**:
```vue
<!-- 黑名单横幅 -->
<div
v-if="getProviderBlacklistStatus(card.name)?.isBlacklisted"
:class="['blacklist-banner', { dark: resolvedTheme === 'dark' }]"
>
<span class="blacklist-icon">⛔</span>
<span class="blacklist-text">
{{ t('components.main.blacklist.blocked') }} |
{{ t('components.main.blacklist.remaining') }}:
{{ formatBlacklistCountdown(getProviderBlacklistStatus(card.name)!.remainingSeconds) }}
</span>
<button
class="unblock-btn"
type="button"
@click.stop="handleUnblock(card.name)"
>
{{ t('components.main.blacklist.unblock') }}
</button>
</div>
```

---

### 6. 修改 `Index.vue` - 样式部分

**位置**:在 `</style>` 标签之前

**添加**:
```scss
.blacklist-banner {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
margin-top: 8px;
background: rgba(239, 68, 68, 0.1);
border-left: 3px solid #ef4444;
border-radius: 6px;
font-size: 13px;
color: #dc2626;

&.dark {
background: rgba(239, 68, 68, 0.15);
color: #f87171;
}
}

.blacklist-icon {
font-size: 16px;
flex-shrink: 0;
}

.blacklist-text {
flex: 1;
font-weight: 500;
}

.unblock-btn {
padding: 4px 12px;
font-size: 12px;
font-weight: 500;
color: #fff;
background: #ef4444;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s;

&:hover {
background: #dc2626;
}

&:active {
transform: scale(0.98);
}
}
```

---

### 7. 修改 `zh-CN.json` - 中文文案

**位置**:在 `components.main` 对象中添加

**添加**:
```json
"blacklist": {
"blocked": "已拉黑",
"remaining": "剩余",
"minutes": "分",
"seconds": "秒",
"unblock": "立即解禁",
"unblockSuccess": "已解除 {name} 的拉黑",
"unblockFailed": "解除拉黑失败,请稍后重试"
}
```

---

### 8. 修改 `en-US.json` - 英文文案

**添加**:
```json
"blacklist": {
"blocked": "Blocked",
"remaining": "Remaining",
"minutes": "m",
"seconds": "s",
"unblock": "Unblock",
"unblockSuccess": "{name} has been unblocked",
"unblockFailed": "Failed to unblock"
}
```

---

## 🧪 测试步骤

### 后端功能测试

1. **启动应用**:
```bash
cd G:\claude-lit\cc-r
wails3 task dev
```

2. **检查数据库表**:
- 打开 `~/.code-switch/app.db`
- 确认 `provider_blacklist` 和 `app_settings` 表已创建

3. **触发拉黑**:
- 添加一个故意配置错误的 provider(错误的 API Key)
- 向该 provider 发送 3 次请求
- 查看控制台日志,应该看到 "⛔ Provider XXX 已拉黑 30 分钟"

### 前端 UI 测试(如果完成了前端修改)

1. **验证拉黑横幅**:
- Provider 卡片下方应出现红色横幅
- 显示 "⛔ 已拉黑 | 剩余: 29分59秒"

2. **验证倒计时**:
- 每秒递减
- 格式正确

3. **验证手动解禁**:
- 点击"立即解禁"按钮
- 横幅消失
- Provider 恢复可用

---

## 🐛 故障排除

**问题:拉黑不生效**
- 检查后端日志是否有错误
- 确认数据库表已创建
- 验证 provider 确实失败了

**问题:前端横幅不显示**
- 检查浏览器控制台是否有 API 调用错误
- 确认导入和状态变量已正确添加
- 验证模板代码位置正确

---

作者:Half open flowers
日期:2025-01-14
Loading