3 Commits
Author SHA1 Message Date
zzzjy d660c204c2 feat: 区分智谱 GLM 两个接入点(普通 API / GLM Coding Plan 订阅专属接入点)
CI / check (push) Successful in 10s
Release / release (push) Successful in 7s
2026-09-17 23:34:28 +08:00
zzzjy c471c0f348 release: v1.0.1
CI / check (push) Successful in 4s
Release / release (push) Successful in 5s
2026-09-17 01:11:40 +08:00
zzzjy cc365c2cd8 ci: Release 支持 workflow_dispatch 网页手动触发发版(自动同步版本/打 tag,幂等可重试)
CI / check (push) Successful in 6s
2026-09-17 01:11:34 +08:00
7 changed files with 128 additions and 31 deletions
+101 -17
View File
@@ -4,6 +4,14 @@ on:
push: push:
tags: tags:
- 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+'
workflow_dispatch:
inputs:
version:
description: '要发布的版本号(不含 v 前缀,例如 1.0.1)'
required: true
notes:
description: '发布说明(可选,留空自动生成 changelog)'
required: false
jobs: jobs:
release: release:
@@ -14,17 +22,76 @@ jobs:
with: with:
fetch-depth: 0 # 生成 changelog 需要完整历史 fetch-depth: 0 # 生成 changelog 需要完整历史
- name: 校验 tag 与 manifest 版本一致 - name: 确定版本号
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_VERSION: ${{ inputs.version }}
run: | run: |
set -euo pipefail set -euo pipefail
TAG="${GITHUB_REF_NAME#v}" if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
VERSION=$(python3 -c "import json; print(json.load(open('manifest.json'))['version'])") VERSION="$INPUT_VERSION"
if [ "$TAG" != "$VERSION" ]; then else
echo "::error::tag ${GITHUB_REF_NAME} 与 manifest.json 版本 ${VERSION} 不一致。请先更新 manifest.json 的 version 再打 tag。" VERSION="${GITHUB_REF_NAME#v}"
fi
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::版本号格式应为 X.Y.Z(收到:${VERSION:-空})"
exit 1 exit 1
fi fi
echo "VERSION=$VERSION" >> "$GITHUB_ENV" echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "版本校验通过: v$VERSION" echo "TAG=v$VERSION" >> "$GITHUB_ENV"
echo "本次发版:v$VERSION(触发方式:$EVENT_NAME)"
- name: 网页触发:同步 manifest 版本并打 tag
if: ${{ github.event_name == 'workflow_dispatch' }}
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
ACTOR: ${{ github.actor }}
run: |
set -euo pipefail
CUR=$(python3 -c "import json; print(json.load(open('manifest.json'))['version'])")
if [ "$CUR" != "$VERSION" ]; then
python3 - "$VERSION" <<'PY'
import json, sys
v = sys.argv[1]
m = json.load(open('manifest.json'))
m['version'] = v
with open('manifest.json', 'w') as f:
json.dump(m, f, ensure_ascii=False, indent=2)
f.write('\n')
PY
git config user.name "${ACTOR}"
git config user.email "${ACTOR}@users.noreply.${GITHUB_SERVER_URL#https://}"
git add manifest.json
git commit -m "release: v${VERSION} [skip ci]"
echo "manifest.json 版本 $CUR -> $VERSION,已提交([skip ci] 防止重复触发流水线)"
else
echo "manifest.json 已是 $VERSION,无需提交"
fi
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
TAG_SHA=$(git rev-parse "${TAG}^{commit}")
HEAD_SHA=$(git rev-parse HEAD)
if [ "$TAG_SHA" != "$HEAD_SHA" ]; then
echo "::error::tag ${TAG} 已存在且指向其他提交,请先处理(删除旧 tag 或换版本号)"
exit 1
fi
echo "tag ${TAG} 已存在且指向当前提交,复用"
else
git tag "$TAG"
fi
HOST="${GITHUB_SERVER_URL#https://}"
PUSH_URL="https://x-access-token:${GITEA_TOKEN}@${HOST}/${GITHUB_REPOSITORY}.git"
git push "$PUSH_URL" "HEAD:${GITHUB_REF_NAME}" 2>/dev/null || echo "分支无新提交,跳过推送"
git push "$PUSH_URL" "$TAG" 2>/dev/null || echo "tag 已在远端,跳过推送"
- name: 校验版本一致性
run: |
set -euo pipefail
MV=$(python3 -c "import json; print(json.load(open('manifest.json'))['version'])")
if [ "$MV" != "$VERSION" ]; then
echo "::error::manifest.json 版本 $MV 与目标 $VERSION 不一致"
exit 1
fi
echo "版本一致性校验通过:v$VERSION"
- name: 打包扩展 - name: 打包扩展
run: | run: |
@@ -50,16 +117,23 @@ jobs:
echo "ZIP=$ZIP" >> "$GITHUB_ENV" echo "ZIP=$ZIP" >> "$GITHUB_ENV"
- name: 生成 Release Notes - name: 生成 Release Notes
env:
INPUT_NOTES: ${{ inputs.notes }}
run: | run: |
set -euo pipefail set -euo pipefail
PREV_TAG=$(git tag --sort=-creatordate | grep -v "^${GITHUB_REF_NAME}$" | head -1 || true) if [ -n "${INPUT_NOTES:-}" ]; then
NOTES="$INPUT_NOTES"
echo "使用手动填写的发布说明"
else
PREV_TAG=$(git tag --sort=-creatordate | grep -v "^${TAG}$" | head -1 || true)
if [ -n "$PREV_TAG" ]; then if [ -n "$PREV_TAG" ]; then
NOTES=$(git log --pretty=format:'- %s' "${PREV_TAG}..${GITHUB_REF_NAME}") NOTES=$(git log --pretty=format:'- %s' "${PREV_TAG}..${TAG}")
echo "changelog 范围: ${PREV_TAG}..${GITHUB_REF_NAME}" echo "changelog 范围: ${PREV_TAG}..${TAG}"
else else
NOTES=$(git log --pretty=format:'- %s') NOTES=$(git log --pretty=format:'- %s')
echo "首个版本,changelog 包含全部提交" echo "首个版本,changelog 包含全部提交"
fi fi
fi
export RELEASE_NOTES="${NOTES:-初始发布}" export RELEASE_NOTES="${NOTES:-初始发布}"
python3 <<'PY' python3 <<'PY'
import json, os import json, os
@@ -70,7 +144,7 @@ jobs:
f"3. 点击工具栏图标完成模型配置即可使用" f"3. 点击工具栏图标完成模型配置即可使用"
) )
meta = { meta = {
'tag_name': os.environ['GITHUB_REF_NAME'], 'tag_name': os.environ['TAG'],
'name': '译泡 PopLingo v' + os.environ['VERSION'], 'name': '译泡 PopLingo v' + os.environ['VERSION'],
'body': body, 'body': body,
'draft': False, 'draft': False,
@@ -81,20 +155,30 @@ jobs:
print('release_meta.json 已生成') print('release_meta.json 已生成')
PY PY
- name: 创建 Release 并上传附件 - name: 创建 Release 并上传附件(幂等,可安全重试)
env: env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: | run: |
set -euo pipefail set -euo pipefail
API="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}" API="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}"
AUTH="Authorization: token ${GITEA_TOKEN}"
EXIST=$(curl -sf -H "$AUTH" "${API}/releases/tags/${TAG}" || true)
if [ -n "$EXIST" ]; then
ID=$(echo "$EXIST" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')
HAS_ASSET=$(echo "$EXIST" | python3 -c "import json,sys; r=json.load(sys.stdin); print(1 if any(a['name']=='$ZIP' for a in r.get('assets',[])) else 0)")
echo "Release ${TAG} 已存在(id=${ID}),复用"
if [ "$HAS_ASSET" = "1" ]; then
echo "附件 ${ZIP} 已存在,无需重复上传"
exit 0
fi
else
curl -sf -X POST "${API}/releases" \ curl -sf -X POST "${API}/releases" \
-H "Authorization: token ${GITEA_TOKEN}" \ -H "$AUTH" -H "Content-Type: application/json" \
-H "Content-Type: application/json" \
--data-binary @release_meta.json > release_resp.json --data-binary @release_meta.json > release_resp.json
ID=$(python3 -c 'import json; print(json.load(open("release_resp.json"))["id"])') ID=$(python3 -c 'import json; print(json.load(open("release_resp.json"))["id"])')
echo "Release 已创建 (id=${ID})" echo "Release 已创建(id=${ID})"
fi
curl -sf -X POST "${API}/releases/${ID}/assets?name=${ZIP}" \ curl -sf -X POST "${API}/releases/${ID}/assets?name=${ZIP}" \
-H "Authorization: token ${GITEA_TOKEN}" \ -H "$AUTH" -F "attachment=@${ZIP}"
-F "attachment=@${ZIP}"
echo echo
echo "✅ 发布完成: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${GITHUB_REF_NAME}" echo "✅ 发布完成: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG}"
+13 -2
View File
@@ -37,7 +37,8 @@
| 服务商 | 获取 Key | 推荐模型 | 备注 | | 服务商 | 获取 Key | 推荐模型 | 备注 |
|---|---|---|---| |---|---|---|---|
| DeepSeek | platform.deepseek.com | `deepseek-chat` | 便宜好用,国内直连 | | DeepSeek | platform.deepseek.com | `deepseek-chat` | 便宜好用,国内直连 |
| 智谱 GLM | open.bigmodel.cn | `glm-4-flash` | 免费额度 | | 智谱 GLM(普通 API) | open.bigmodel.cn | `glm-4-flash` | 免费额度,按量计费 |
| 智谱 GLM Coding Plan | open.bigmodel.cn | `glm-5.3` | 订阅套餐,走专属接入点不按 token 扣费 |
| 月之暗面 Kimi | platform.moonshot.cn | `moonshot-v1-8k` | 长上下文 | | 月之暗面 Kimi | platform.moonshot.cn | `moonshot-v1-8k` | 长上下文 |
| 通义千问 | bailian.aliyun.com | `qwen-plus` | 阿里云百炼 | | 通义千问 | bailian.aliyun.com | `qwen-plus` | 阿里云百炼 |
| Google Gemini | aistudio.google.com | `gemini-2.0-flash` | 有免费额度 | | Google Gemini | aistudio.google.com | `gemini-2.0-flash` | 有免费额度 |
@@ -82,7 +83,17 @@ fanyi/
| `ci.yml` | push 到 `main` / PR | manifest 校验 + JS 语法检查 | | `ci.yml` | push 到 `main` / PR | manifest 校验 + JS 语法检查 |
| `release.yml` | 推送 `vX.Y.Z` 标签 | 校验版本一致 → 打包 zip → 生成 Changelog → 创建 Release 并上传附件 | | `release.yml` | 推送 `vX.Y.Z` 标签 | 校验版本一致 → 打包 zip → 生成 Changelog → 创建 Release 并上传附件 |
发版三步: ### 方式一:网页触发(推荐)
打开仓库 **Actions → Release workflow → Run workflow**,填入版本号(如 `1.0.2`),点 Run 即可。流水线会自动:
1. 把 `manifest.json` 的 `version` 同步为该版本号并提交(提交带 `[skip ci]`,避免重复触发)
2. 创建并推送 `vX.Y.Z` 标签
3. 打包 zip、生成 changelog、创建 Release 并上传附件
可选填「发布说明」,留空则自动生成;重复触发同一版本是幂等的,不会重复发布。
### 方式二:本地打 tag
```bash ```bash
# 1. 更新 manifest.json 中的 version 字段 # 1. 更新 manifest.json 中的 version 字段
+2 -1
View File
@@ -21,7 +21,8 @@ const PRESETS = {
deepseek: { name: 'DeepSeek', apiType: 'openai', baseURL: 'https://api.deepseek.com/v1', model: 'deepseek-chat' }, deepseek: { name: 'DeepSeek', apiType: 'openai', baseURL: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },
openai: { name: 'OpenAI', apiType: 'openai', baseURL: 'https://api.openai.com/v1', model: 'gpt-4o-mini' }, openai: { name: 'OpenAI', apiType: 'openai', baseURL: 'https://api.openai.com/v1', model: 'gpt-4o-mini' },
kimi: { name: '月之暗面 Kimi', apiType: 'openai', baseURL: 'https://api.moonshot.cn/v1', model: 'moonshot-v1-8k' }, kimi: { name: '月之暗面 Kimi', apiType: 'openai', baseURL: 'https://api.moonshot.cn/v1', model: 'moonshot-v1-8k' },
zhipu: { name: '智谱 GLM', apiType: 'openai', baseURL: 'https://open.bigmodel.cn/api/paas/v4', model: 'glm-4-flash' }, zhipu: { name: '智谱 GLM(普通 API·按量计费)', apiType: 'openai', baseURL: 'https://open.bigmodel.cn/api/paas/v4', model: 'glm-4-flash' },
'zhipu-coding': { name: '智谱 GLM Coding Plan(订阅)', apiType: 'openai', baseURL: 'https://open.bigmodel.cn/api/coding/paas/v4', model: 'glm-5.3' },
qwen: { name: '通义千问', apiType: 'openai', baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-plus' }, qwen: { name: '通义千问', apiType: 'openai', baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-plus' },
siliconflow: { name: '硅基流动', apiType: 'openai', baseURL: 'https://api.siliconflow.cn/v1', model: 'Qwen/Qwen2.5-7B-Instruct' }, siliconflow: { name: '硅基流动', apiType: 'openai', baseURL: 'https://api.siliconflow.cn/v1', model: 'Qwen/Qwen2.5-7B-Instruct' },
gemini: { name: 'Google Gemini', apiType: 'gemini', baseURL: 'https://generativelanguage.googleapis.com', model: 'gemini-2.0-flash' }, gemini: { name: 'Google Gemini', apiType: 'gemini', baseURL: 'https://generativelanguage.googleapis.com', model: 'gemini-2.0-flash' },
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "译泡 · PopLingo — AI 划词翻译", "name": "译泡 · PopLingo — AI 划词翻译",
"version": "1.0.0", "version": "1.0.2",
"description": "划词即译的大模型翻译气泡:DeepSeek / OpenAI / Kimi / 智谱 / 通义 / Gemini / Claude / Ollama,流式输出。", "description": "划词即译的大模型翻译气泡:DeepSeek / OpenAI / Kimi / 智谱 / 通义 / Gemini / Claude / Ollama,流式输出。",
"minimum_chrome_version": "116", "minimum_chrome_version": "116",
"permissions": [ "permissions": [
+1 -1
View File
@@ -66,7 +66,7 @@
</div> </div>
<div class="tips"> <div class="tips">
💡 国内推荐 <b>DeepSeek</b>(便宜好用)或 <b>智谱 GLM-4-Flash</b>(免费);本地部署选 <b>Ollama</b>(无需 Key)。Key 仅保存在你的浏览器账号同步存储中。 💡 国内推荐 <b>DeepSeek</b>(按量便宜好用)或 <b>智谱 GLM-4-Flash</b>(免费);订阅了 <b>GLM Coding Plan</b> 的请选对应接入点(走套餐额度,不按 token 扣费);本地部署选 <b>Ollama</b>(无需 Key)。Key 仅保存在浏览器账号同步存储中。
</div> </div>
</section> </section>
+2 -1
View File
@@ -6,7 +6,8 @@ const PRESETS = {
deepseek: { name: 'DeepSeek · 深度求索', apiType: 'openai', baseURL: 'https://api.deepseek.com/v1', model: 'deepseek-chat' }, deepseek: { name: 'DeepSeek · 深度求索', apiType: 'openai', baseURL: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },
openai: { name: 'OpenAI', apiType: 'openai', baseURL: 'https://api.openai.com/v1', model: 'gpt-4o-mini' }, openai: { name: 'OpenAI', apiType: 'openai', baseURL: 'https://api.openai.com/v1', model: 'gpt-4o-mini' },
kimi: { name: '月之暗面 Kimi', apiType: 'openai', baseURL: 'https://api.moonshot.cn/v1', model: 'moonshot-v1-8k' }, kimi: { name: '月之暗面 Kimi', apiType: 'openai', baseURL: 'https://api.moonshot.cn/v1', model: 'moonshot-v1-8k' },
zhipu: { name: '智谱 GLM(Flash 免费)', apiType: 'openai', baseURL: 'https://open.bigmodel.cn/api/paas/v4', model: 'glm-4-flash' }, zhipu: { name: '智谱 GLM(普通 API·按量计费)', apiType: 'openai', baseURL: 'https://open.bigmodel.cn/api/paas/v4', model: 'glm-4-flash' },
'zhipu-coding': { name: '智谱 GLM Coding Plan(订阅套餐专用接入点)', apiType: 'openai', baseURL: 'https://open.bigmodel.cn/api/coding/paas/v4', model: 'glm-5.3' },
qwen: { name: '通义千问(阿里云百炼)', apiType: 'openai', baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-plus' }, qwen: { name: '通义千问(阿里云百炼)', apiType: 'openai', baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-plus' },
siliconflow: { name: '硅基流动 SiliconFlow', apiType: 'openai', baseURL: 'https://api.siliconflow.cn/v1', model: 'Qwen/Qwen2.5-7B-Instruct' }, siliconflow: { name: '硅基流动 SiliconFlow', apiType: 'openai', baseURL: 'https://api.siliconflow.cn/v1', model: 'Qwen/Qwen2.5-7B-Instruct' },
gemini: { name: 'Google Gemini', apiType: 'gemini', baseURL: 'https://generativelanguage.googleapis.com', model: 'gemini-2.0-flash' }, gemini: { name: 'Google Gemini', apiType: 'gemini', baseURL: 'https://generativelanguage.googleapis.com', model: 'gemini-2.0-flash' },
+1 -1
View File
@@ -3,7 +3,7 @@
const LANGS = ['简体中文', '繁體中文', 'English', '日本語', '한국어', 'Français', 'Deutsch', 'Español', 'Português', 'Italiano', 'Русский', 'العربية', 'हिन्दी', 'ไทย', 'Việt Nam', 'Indonesia']; const LANGS = ['简体中文', '繁體中文', 'English', '日本語', '한국어', 'Français', 'Deutsch', 'Español', 'Português', 'Italiano', 'Русский', 'العربية', 'हिन्दी', 'ไทย', 'Việt Nam', 'Indonesia'];
const PRESETS = { const PRESETS = {
deepseek: 'DeepSeek', openai: 'OpenAI', kimi: '月之暗面 Kimi', zhipu: '智谱 GLM', deepseek: 'DeepSeek', openai: 'OpenAI', kimi: '月之暗面 Kimi', zhipu: '智谱 GLM(普通 API)', 'zhipu-coding': '智谱 GLM Coding Plan',
qwen: '通义千问', siliconflow: '硅基流动', gemini: 'Google Gemini', qwen: '通义千问', siliconflow: '硅基流动', gemini: 'Google Gemini',
anthropic: 'Anthropic Claude', ollama: 'Ollama(本地)', custom: '自定义(OpenAI 兼容)' anthropic: 'Anthropic Claude', ollama: 'Ollama(本地)', custom: '自定义(OpenAI 兼容)'
}; };