23 Commits

Author SHA1 Message Date
huangjianwu
26e23d0f2c Release v2.1.3
修 issue #282 (DeepSeek 等非多模态供应商被 400 拒绝)。详见 CHANGELOG.md。
2026-05-07 14:14:33 +08:00
huangjianwu
234e3b9d2a docs: v2.1.3 CHANGELOG + README 版本
修 issue #282(DeepSeek 等非多模态供应商被 400 拒绝)。详见 CHANGELOG.md。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:14:22 +08:00
Jianwu Huang
1d93d1c5f5 Merge pull request #345 from voidborne-d/fix/backend-deepseek-content-format
fix(backend): UniversalGPT.create_messages emit string content when no images
2026-05-07 14:12:34 +08:00
huangjianwu
c19d462505 Merge branch 'release/2.1.2' back into develop 2026-05-07 14:06:35 +08:00
huangjianwu
64882e6a77 Release v2.1.2
补 v2.1.1 ghcr.io 镜像构建失败。详见 CHANGELOG.md。
2026-05-07 14:06:26 +08:00
huangjianwu
f32a6944d1 docs: v2.1.2 CHANGELOG + README 版本
补 v2.1.1 ghcr.io 镜像构建失败。详见 CHANGELOG.md。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:06:16 +08:00
Jianwu Huang
c5c84a8ec7 Merge pull request #349 from JefferyHcool/fix/docker-frontend-build
fix(docker): 修复 Tag push 触发的镜像构建失败
2026-05-07 14:04:38 +08:00
huangjianwu
c4413c66a1 fix(docker): 修复 Tag push 触发的镜像构建失败
ghcr.io 镜像推送在 v2.1.1 tag 上失败,停在 frontend-builder 第 7/7 步
'pnpm run build':vite loadConfigFromBundledFile 1.5s 内挂掉,没具体行号——
典型现象是 vite.config.ts 顶部 import 的某个 plugin(@tailwindcss/vite)的
native binding 在容器里 require 失败。

三处修复:
1. Dockerfile.complete + BillNote_frontend/Dockerfile:node:18-alpine → node:20-alpine
   · Tailwind v4 已不再支持 Node 18(package 现实需要 20+)
   · Vite 6 也建议 Node 20+
2. Dockerfile.complete 的 frontend 阶段:复制 pnpm-lock.yaml + 改用 --frozen-lockfile
   · 之前没传 lockfile,每次 pnpm install 重解析 semver,有可能拉到比本地更新的 native dep
3. BillNote_frontend/pnpm-lock.yaml 强制入库(git add -f)
   · 之前根 .gitignore 有条诡异的 'BiliNote/pnpm-lock.yaml'(拼错的路径),
     虽然没真匹配上这个文件,但 lockfile 历史上一直没被提交,导致 CI 与本地依赖图持续漂移
   · lockfile 里 @tailwindcss/oxide 同时锁了 musl 与 gnu 变体,alpine 跑没问题

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:01:26 +08:00
Jianwu Huang
26ee15ce28 Merge pull request #347 from JefferyHcool/docs/readme-wechat-qr
docs(readme): 联系和加入社区段落补上微信群二维码
2026-05-07 13:59:47 +08:00
huangjianwu
29fa3d9540 docs(readme): 联系和加入社区段落补上微信群二维码
之前只写"年会恢复更新以后放出最新社区地址",现在直接挂 doc/wechat.png 上去。
GitHub 渲染相对路径图片时按 raw.githubusercontent.com 自动转,无需 CDN。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:57:40 +08:00
huangjianwu
61cb4ec9fa Merge branch 'release/2.1.1' back into develop 2026-05-07 13:55:01 +08:00
huangjianwu
a46880f169 Release v2.1.1
工程化与文档收尾,无运行时行为变化。详见 CHANGELOG.md。
2026-05-07 13:54:50 +08:00
huangjianwu
c187dce5cb docs: v2.1.1 CHANGELOG + README 版本
工程化与文档收尾,无运行时行为变化。详见 CHANGELOG.md。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:54:23 +08:00
Jianwu Huang
424c7f84e2 Merge pull request #346 from JefferyHcool/feature/repo-tooling
chore(repo): 仓库工具链——issue/PR 模板 + commitlint CI + 插件发版工作流
2026-05-07 13:53:04 +08:00
Jianwu Huang
f6a9af4658 Merge pull request #344 from JefferyHcool/chore/update-wechat-qr
chore(docs): 更新群聊微信二维码
2026-05-07 13:51:47 +08:00
voidborne-d
3ff7086491 fix(backend): UniversalGPT.create_messages emit string content when no images
DeepSeek deepseek-chat 等非多模态模型只接受 ``content`` 为字符串。旧实现在
没有 ``video_img_urls`` 输入时也把 ``content`` 拼成
``[{"type":"text","text":...}]`` 多模态数组,导致 DeepSeek API 返回
``Failed to deserialize the JSON body into the target type: messages[0]:
unknown variant `image_url`, expected `text```,整个笔记生成流程随之崩溃。

修复方式:``create_messages`` 在没有截图时退回 string content;有截图时维持
原多模态数组形态,多模态模型功能不退化。同时把 ``_build_merge_messages`` 也
改为 string content —— 合并阶段从不带图片,旧的数组形态会让长视频 chunk
之后的合并阶段同样命中 DeepSeek 400。

新增 ``backend/tests/test_universal_gpt_content_format.py`` (6 cases):

- 无图片 / 显式空 image 列表都走 string content
- 有图片仍输出多模态数组(含 ``image_url`` + ``detail: auto``)
- 纯文本响应里完全不含 ``image_url`` 字段
- ``_build_merge_messages`` 用 string content + 仍带入 partials 文本

红基线:在不打补丁的 ``universal_gpt.py`` 上跑这 6 个 case,3 个 string-
content 断言会失败(命中 issue #282 的同一根因),打补丁后 6/6 通过。

Closes #282
2026-05-07 13:50:59 +08:00
huangjianwu
f583f3cc8c chore(frontend): about 页移除 QQ 群联系方式,仅保留微信群
QQ 群已不再活跃维护,关联展示也容易让用户搜不到。保留微信群入口即可。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:50:23 +08:00
huangjianwu
2e7fe8d3a8 chore(frontend): 关于页二维码改为 import 本地资源,不再依赖 CDN
之前 about 页直接拉腾讯云 COS 上的 wechat.png,每次换码都要手动重新上传 CDN
+ 刷缓存。改成 import @/assets/wechat.png,由 Vite 打包,更新时只需替换文件。

doc/wechat.png 仍保留作为源 / 备份。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:49:25 +08:00
huangjianwu
1e055c3068 chore(docs): 更新群聊微信二维码
doc/wechat.png 替换为最新版(396×396)。

⚠️ 前端 SettingPage/about.tsx:207 实际渲染的是腾讯云 COS 上的同名文件
   https://common-1304618721.cos.ap-chengdu.myqcloud.com/wechat.png
仓库这边只是源文件,CDN 那份需要项目维护者手动重新上传:
- 登录腾讯云 COS 控制台
- 找到 common-1304618721 桶
- 把根目录的 wechat.png 替换为 doc/wechat.png 这一份

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:47:23 +08:00
huangjianwu
b2af0e4e53 chore(repo): 仓库工具链——issue/PR 模板 + commitlint CI + 插件发版工作流
把 CONTRIBUTING.md 里写的规范落到 GitHub 工程化层。

Issue / PR 模板:
- .github/ISSUE_TEMPLATE/{config,bug_report,feature_request}.yml
  · yml 表单形式,跟随当前工作区分类(backend / frontend / extension / Tauri)
  · bug_report 强制选版本 + 部署方式 + 复现步骤;提交前自查不夹带 secrets
  · config.yml 禁用空白 issue,引导 Discussions
- .github/pull_request_template.md:把 CONTRIBUTING §5.2 的 PR 正文要求落成 checklist
- 删旧版 .md 模板(含中文文件名那条),避免新老两套并存

Commitlint:
- .commitlintrc.json:extend conventional + 自定义 type 白名单(feat/fix/docs/style/refactor/perf/test/build/ci/chore/ui/revert)
- .github/workflows/commitlint.yml:用 wagoid/commitlint-github-action@v6,PR + push develop/master 时校验
  · subject-case / subject-full-stop 关掉,兼容中文 subject
  · header-max-length 100 字符 warn 级别,不阻塞合并

插件发版工作流:
- .github/workflows/release-extension.yml:v* tag push 时
  · cd BillNote_extension && pnpm install + build
  · pack:zip / pack:xpi / pack:crx(crx 缺 key 自动跳过)
  · 产物重命名带版本后缀,挂到对应 GitHub Release
- 末尾保留 publish-chrome / publish-edge / publish-firefox 三段注释,配齐 secrets 即可启用商店自动发布
- RELEASING.md:发版执行手册,覆盖 release/* 流程 + 各商店人工上传步骤 + 自动发布所需 secrets

CONTRIBUTING.md 关联文档区指到新增的 RELEASING.md,commit 章节加 commitlint 落地说明。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:45:32 +08:00
Jianwu Huang
3d0838ba72 Merge pull request #343 from JefferyHcool/feature/contributing-doc
docs(contributing): 新增贡献指南,落地简化 Git Flow 分支管理
2026-05-07 13:26:23 +08:00
huangjianwu
ee58a65bcd docs(contributing): 新增贡献指南,落地简化 Git Flow 分支管理
落地分支模型:
- master:稳定主干,仅接受 release/hotfix 合入,禁止直接提交
- develop:长期开发集成分支,常规开发都从这里起
- feature/* / fix/*:短生命周期,基于 develop
- release/*:基于 develop,发版后合入 master + 回灌 develop
- hotfix/*:基于 master,处理线上紧急故障

同时收录:
- 多工作区命名 scope 约定(extension / frontend / backend / bilibili / ...)
- commit message 格式(type(scope): subject)+ PR 标题/正文要求
- 合并方式:feature/fix → develop 用 Squash;release/hotfix → master 用 --no-ff
- simple-git-hooks 残留 .git/hooks/pre-commit 的清理办法
- 语义化版本 + CHANGELOG.md 更新流程
- 禁止事项 + 历史分支迁移要求

适配自 emplai-ui doc/前端仓库分支管理办法.md,按本仓库实际情况调整:
master 不改名为 main、补充多工作区 scope、把发版与 tag 流程串联到 README/CHANGELOG。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:13:32 +08:00
huangjianwu
cfc3053be8 Merge upstream master (PR #341) into develop 2026-05-07 13:10:28 +08:00
21 changed files with 11952 additions and 102 deletions

28
.commitlintrc.json Normal file
View File

@@ -0,0 +1,28 @@
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"type-enum": [
2,
"always",
[
"feat",
"fix",
"docs",
"style",
"refactor",
"perf",
"test",
"build",
"ci",
"chore",
"ui",
"revert"
]
],
"subject-case": [0],
"subject-full-stop": [0],
"header-max-length": [1, "always", 100],
"body-max-line-length": [0],
"footer-max-line-length": [0]
}
}

View File

@@ -1,49 +0,0 @@
---
name: Bug report
about: 上报一些bug
title: "[BUG]"
labels: bug
assignees: JefferyHcool
---
---
name: 🐛 Bug 反馈
about: 提交一个 Bug 报告,帮助我们改进
title: "[Bug] "
labels: bug
assignees: ''
---
**版本说明**
请说明的你的版本号
**部署方式**
使用的是什么方式部署代码环境部署docker部署桌面端在线预览
**描述问题**
清晰、简明地描述你遇到的问题是什么。
**复现步骤**
复现该问题的步骤:
1. 进入页面 '...'
2. 点击 '...'
3. 滚动到 '...'
4. 出现错误
**预期行为**
清晰、简明地描述你本来预期发生的行为。
**截图**
如果适用,请添加截图以帮助说明问题。
**桌面端(请补充以下信息)**
- 操作系统:例如 Windows / macOS / Ubuntu
- 浏览器:例如 Chrome、Safari
**其他补充信息**
请补充任何其他相关信息。

93
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,93 @@
name: 🐛 Bug 报告
description: 报告一个可复现的问题
title: "[Bug] "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
感谢反馈。请尽量提供完整的复现路径与日志,便于排查。
⚠️ **不要**贴 API key、SESSDATA、密钥等敏感信息。
- type: dropdown
id: workspace
attributes:
label: 受影响的工作区
multiple: true
options:
- 后端 (backend/)
- Web 前端 (BillNote_frontend/)
- 浏览器插件 (BillNote_extension/)
- Tauri 桌面端
- 文档 / 其他
validations:
required: true
- type: input
id: version
attributes:
label: 版本
description: BiliNote 版本号README 顶部,例如 v2.1.0
placeholder: v2.1.0
validations:
required: true
- type: dropdown
id: deploy
attributes:
label: 部署方式
options:
- 源码运行
- Docker (docker-compose.yml)
- Docker GPU (docker-compose.gpu.yml)
- 桌面端安装包 (Tauri Release)
- 其他
validations:
required: true
- type: textarea
id: repro
attributes:
label: 复现步骤
description: 一步步说明如何触发问题
placeholder: |
1. 打开 ...
2. 点击 ...
3. 看到 ...
validations:
required: true
- type: textarea
id: expected
attributes:
label: 期望行为
validations:
required: true
- type: textarea
id: actual
attributes:
label: 实际行为
description: 含错误信息、截图、录屏均可
validations:
required: true
- type: textarea
id: env
attributes:
label: 运行环境
description: 操作系统、Python 版本、Node 版本、浏览器(如适用)
placeholder: |
- OS: macOS 14.5 / Windows 11 / Ubuntu 22.04
- Python: 3.11.6
- Node: 20.18.0
- Browser: Chrome 124如涉及插件/前端)
- GPU: 无 / NVIDIA 4070如涉及 fast-whisper / video understanding
- type: textarea
id: logs
attributes:
label: 日志 / 堆栈
description: 后端 console、前端 DevTools、扩展 background 页都可以贴
render: text
- type: checkboxes
id: pre-checks
attributes:
label: 提交前自查
options:
- label: 我已搜索过 [Issues](https://github.com/JefferyHcool/BiliNote/issues?q=),确认不是重复问题
required: true
- label: 我提供的日志中**不**包含 API key、cookie、SESSDATA 等敏感信息
required: true

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 📖 文档与常见问题
url: https://docs.bilinote.app/
about: 安装与配置遇到问题,先看一下文档
- name: 💬 提问 / 讨论
url: https://github.com/JefferyHcool/BiliNote/discussions
about: 用法咨询、想法征集请发到 Discussions不是 bug 才用 Issues

View File

@@ -0,0 +1,40 @@
name: ✨ 功能建议
description: 提议新功能或改进
title: "[Feature] "
labels: ["enhancement"]
body:
- type: textarea
id: problem
attributes:
label: 想解决什么问题?
description: 描述你遇到的实际场景或痛点。
validations:
required: true
- type: textarea
id: proposal
attributes:
label: 建议方案
description: 期望的功能或交互。可附草图 / 示例。
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: 备选方案
description: 你考虑过哪些其他做法?为什么没采用?
- type: dropdown
id: workspace
attributes:
label: 涉及的工作区
multiple: true
options:
- 后端 (backend/)
- Web 前端 (BillNote_frontend/)
- 浏览器插件 (BillNote_extension/)
- Tauri 桌面端
- 不确定
- type: textarea
id: extra
attributes:
label: 其他补充
description: 关联 issue、参考资料、产品截图等

View File

@@ -1,29 +0,0 @@
---
name: 新增功能建议
about: 一些新的功能建议
title: "[FEATHURE]"
labels: enhancement
assignees: JefferyHcool
---
---
name: ✨ 功能请求
about: 提出一个新的功能建议
title: "[Feature] "
labels: enhancement
assignees: ''
---
**这个功能请求是否与某个问题相关?请描述**
清晰简要地描述问题是什么。例如:每次遇到 [...] 都让我感到很沮丧。
**描述你希望实现的解决方案**
清晰简要地描述你希望发生的事情。
**描述你考虑过的备选方案**
清晰简要地描述你考虑过的其他解决方案或功能。
**其他补充信息**
请在此添加关于功能请求的其他上下文或截图。

39
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,39 @@
<!--
PR 标题请遵循 type(scope): subject 格式,例如:
feat(extension): 侧边栏接入思维导图
fix(bilibili): 修正字幕优先链路在未登录态下的回退
分支命名 / 提交规范见 CONTRIBUTING.md。
-->
## 改动概述
<!-- 一句话说清这个 PR 做了什么 -->
## 为什么
<!-- 背景、关联 issueFixes #xxx / Refs #xxx)、用户场景 -->
## 做了什么
<!-- 关键文件、关键决策。可贴关键片段或截图 -->
## 测试方式
- [ ] `pnpm typecheck && pnpm build`(前端 / 插件)通过
- [ ] `python -m py_compile <文件>` 或本地 backend 启动验证(后端)通过
- [ ] 手动验证步骤:
<!-- 描述如何复现验证UI 改动请附截图 / 录屏 -->
## 回归风险
<!-- 影响面、可能受波及的功能、是否需要前后端 / 配置 同步部署 -->
## Checklist
- [ ] 分支命名遵循 [CONTRIBUTING.md §3](../CONTRIBUTING.md#3-分支命名)`feature/*` / `fix/*` / `release/*` / `hotfix/*`
- [ ] base 分支正确(常规改动 → `develop`;线上紧急 → `master`;发版 → 见 §4.3
- [ ] Commit message 遵循 `type(scope): subject` 格式([CONTRIBUTING.md §5.1](../CONTRIBUTING.md#51-commit-message-格式)
- [ ] 已自测核心流程
- [ ] 已更新相关文档(`README.md` / `CHANGELOG.md` / `CLAUDE.md` / 模块 README如适用
- [ ] 未夹带 secrets / `.env` / 大型二进制
- [ ] 单 PR 不跨多个工作区做无关改动

32
.github/workflows/commitlint.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Commit Lint
on:
pull_request:
types: [opened, synchronize, reopened, edited]
push:
branches:
- develop
- master
permissions:
contents: read
pull-requests: read
jobs:
commitlint:
name: Lint commit messages
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Run commitlint
uses: wagoid/commitlint-github-action@v6
with:
configFile: .commitlintrc.json
# PR 上检查 base..head 之间所有 commitpush 上只校验最新 commit
firstParent: false
failOnWarnings: false
helpURL: https://github.com/JefferyHcool/BiliNote/blob/develop/CONTRIBUTING.md#5-提交规范

115
.github/workflows/release-extension.yml vendored Normal file
View File

@@ -0,0 +1,115 @@
name: Release Extension
# 在 v* tag push 时触发,构建插件并把产物挂到对应 GitHub Release。
# 商店上传仍走人工(详见 RELEASING.md如果将来配齐了商店 API secrets
# 把本文件末尾注释的 publish-* job 解开就是自动发布。
on:
push:
tags:
- 'v*'
permissions:
contents: write
jobs:
build:
name: Build & attach to release
runs-on: ubuntu-latest
defaults:
run:
working-directory: BillNote_extension
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
cache-dependency-path: BillNote_extension/pnpm-lock.yaml
- name: Install
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
- name: Pack zip (Chrome / Edge upload format)
run: pnpm pack:zip
- name: Pack xpi (Firefox Add-ons)
run: pnpm pack:xpi
- name: Pack crx (self-host sideload)
# crx 需要稳定 key.pem 才能保持插件 ID 不变CI 没有就跳过,不阻塞主流程。
# 想生成稳定 crx把 key 存到 secret EXTENSION_CRX_KEY下面解开几行。
run: |
# if [ -n "${{ secrets.EXTENSION_CRX_KEY }}" ]; then
# echo "${{ secrets.EXTENSION_CRX_KEY }}" > key.pem
# pnpm pack:crx
# else
pnpm pack:crx || true
# fi
continue-on-error: true
- name: Rename artifacts with version suffix
run: |
VERSION="${GITHUB_REF#refs/tags/v}"
[ -f extension.zip ] && mv extension.zip "bilinote-extension-${VERSION}.zip"
[ -f extension.xpi ] && mv extension.xpi "bilinote-extension-${VERSION}.xpi"
[ -f extension.crx ] && mv extension.crx "bilinote-extension-${VERSION}.crx"
ls -la *.zip *.xpi *.crx 2>/dev/null || true
- name: Attach to GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
BillNote_extension/bilinote-extension-*.zip
BillNote_extension/bilinote-extension-*.xpi
BillNote_extension/bilinote-extension-*.crx
fail_on_unmatched_files: false
generate_release_notes: false
# ---------- 商店自动发布(默认禁用,配齐 secrets 后可启用) ----------
#
# publish-chrome:
# needs: build
# runs-on: ubuntu-latest
# steps:
# - uses: actions/download-artifact@v4
# - uses: mnao305/chrome-extension-upload@v5
# with:
# file-path: BillNote_extension/bilinote-extension-${{ github.ref_name }}.zip
# extension-id: ${{ secrets.CHROME_EXTENSION_ID }}
# client-id: ${{ secrets.CHROME_CLIENT_ID }}
# client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
# refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
#
# publish-edge:
# needs: build
# runs-on: ubuntu-latest
# steps:
# - uses: wdzeng/edge-addon@v2
# with:
# product-id: ${{ secrets.EDGE_PRODUCT_ID }}
# zip-path: BillNote_extension/bilinote-extension-${{ github.ref_name }}.zip
# client-id: ${{ secrets.EDGE_CLIENT_ID }}
# api-key: ${{ secrets.EDGE_API_KEY }}
#
# publish-firefox:
# needs: build
# runs-on: ubuntu-latest
# steps:
# - uses: trmcnvn/firefox-addon@v3
# with:
# uuid: ${{ secrets.FIREFOX_ADDON_UUID }}
# xpi: BillNote_extension/bilinote-extension-${{ github.ref_name }}.xpi
# api-key: ${{ secrets.FIREFOX_API_KEY }}
# api-secret: ${{ secrets.FIREFOX_API_SECRET }}

View File

@@ -1,5 +1,6 @@
# === 前端构建阶段 ===
FROM node:18-alpine AS builder
# Tailwind v4 / Vite 6 需要 Node 20+alpine + pnpm 会按 lockfile 拉 musl native binary。
FROM node:20-alpine AS builder
RUN corepack enable && corepack prepare pnpm@latest --activate

10810
BillNote_frontend/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -5,6 +5,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Github, Star, ExternalLink, Download } from 'lucide-react'
import { ScrollArea } from '@/components/ui/scroll-area.tsx'
import logo from '@/assets/icon.svg'
import wechatQr from '@/assets/wechat.png'
export default function AboutPage() {
const images = [
@@ -197,14 +198,10 @@ export default function AboutPage() {
<h2 className="mb-8 text-center text-3xl font-bold"></h2>
<div className="mx-auto max-w-3xl">
<div className="flex flex-col items-center justify-center gap-8">
<div className="text-center">
<h3 className="mb-3 text-xl font-semibold">BiliNote QQ </h3>
<p className="text-lg font-medium">785367111</p>
</div>
<div className="text-center">
<h3 className="mb-3 text-xl font-semibold">BiliNote </h3>
<div className="bg-muted mx-auto flex h-52 w-52 items-center justify-center rounded-md">
<img src={'https://common-1304618721.cos.ap-chengdu.myqcloud.com/wechat.png'} />
<img src={wechatQr} alt="BiliNote 交流微信群" className="h-full w-full object-contain" />
</div>
</div>
</div>

View File

@@ -2,6 +2,52 @@
本项目所有重要变更记录于此。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
## [2.1.3] - 2026-05-07
### Fixed
- DeepSeek 等非多模态供应商被 400 拒绝issue #282`UniversalGPT.create_messages``_build_merge_messages` 此前**无条件**把 content 拼成 OpenAI 多模态数组 `[{"type":"text",...}]`DeepSeek `deepseek-chat` 等模型不识别 `image_url` 变体直接报 `invalid_request_error``GPTFactory.from_config` 一律实例化 `UniversalGPT`,所以问题覆盖**所有**通过模型设置页接入的非多模态供应商,不止 DeepSeek。
- 现按 `video_img_urls` 是否非空切换 content 形态:有图保留多模态数组(视觉模型不退化),无图退回 string。合并阶段历来不带图统一改 string。
- 与同包内 `deepseek_gpt.py` / `openai_gpt.py` / `qwen_gpt.py` 的 message builder 行为对齐。
- 新增 `backend/tests/test_universal_gpt_content_format.py` 6 个 case 回归覆盖(含 `image_url` 字面 not-in JSON 断言)。
感谢 @voidborne-d 的修复(#345)。
## [2.1.2] - 2026-05-07
补 v2.1.1 上 ghcr.io 镜像构建失败的坑。
### Fixed
- Docker 镜像构建失败v2.1.1 tag 触发的 ghcr.io 推送在 frontend-builder 第 7/7 步 `pnpm run build` 挂掉vite `loadConfigFromBundledFile` 加载 `@tailwindcss/vite` plugin 时 1.5s 内异常退出)。
- `Dockerfile.complete``BillNote_frontend/Dockerfile``node:18-alpine``node:20-alpine`Tailwind v4 已不再支持 Node 18Vite 6 也推荐 Node 20+
- `Dockerfile.complete` 的 frontend 阶段同时复制 `pnpm-lock.yaml` 并改用 `--frozen-lockfile`,杜绝每次构建重解析 semver 拉到比本地新的 native dep
- `BillNote_frontend/pnpm-lock.yaml` 强制入库(之前一直未提交,导致 CI / 本地依赖图持续漂移)
- README 联系社区段补上微信群二维码(之前只写"年会恢复更新以后放出最新社区地址"
## [2.1.1] - 2026-05-07
工程化与文档收尾,无运行时行为变化。
### Added
- [`CONTRIBUTING.md`](./CONTRIBUTING.md) — 贡献指南,落地简化 Git Flowmaster + develop + 短生命周期分支)+ 提交规范 + 合并规范
- [`RELEASING.md`](./RELEASING.md) — 发版手册Release Manager 视角),含 release/* 流程 + 各商店人工上传步骤 + 自动发布所需 secrets
- `.github/ISSUE_TEMPLATE/{config,bug_report,feature_request}.yml` — 表单形式的 issue 模板,按工作区分类
- `.github/pull_request_template.md` — PR 模板,把 CONTRIBUTING §5.2 落成 checklist
- `.commitlintrc.json` + `.github/workflows/commitlint.yml` — commitlint CIPR + push develop/master 时校验,自定义 type 白名单,兼容中文 subject
- `.github/workflows/release-extension.yml``v*` tag push 时自动构建插件 .zip / .xpi / .crx 并挂到对应 GitHub Release商店自动发布以注释形式预留
### Changed
- 关于页二维码改为 `import @/assets/wechat.png`,不再依赖腾讯云 COS CDN更新只需替换文件 + 跑构建
- 群聊 QR 替换为最新版本(`doc/wechat.png` + `BillNote_frontend/src/assets/wechat.png`
### Removed
- 关于页 QQ 群联系方式(号 785367111已不再活跃维护
- 旧版 `.md` 格式 issue 模板(被新 yml 表单模板取代)
## [2.1.0] - 2026-05-07
本次发布的主线是**浏览器插件**和 **B 站字幕优先链路**。配合一些后端 / 前端体验修复。

340
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,340 @@
# 贡献指南
欢迎为 BiliNote 贡献代码。本文档约定分支管理、提交规范、合并流程。新贡献者请通读一遍后再开 PR。
> 关联文档
> - [README.md](./README.md):项目概览、快速开始
> - [CLAUDE.md](./CLAUDE.md):仓库结构 + 各 workspace 开发命令
> - [CHANGELOG.md](./CHANGELOG.md):版本变更记录
> - [RELEASING.md](./RELEASING.md)发版执行手册Release Manager 视角)
---
## 1. 仓库结构与工作区
本仓库为多工作区单体仓:
| 路径 | 内容 | 主要命令 |
|---|---|---|
| `backend/` | Python 3.11 + FastAPI | `pip install -r requirements.txt && python main.py` |
| `BillNote_frontend/` | React 19 + Vite | `pnpm install && pnpm dev` |
| `BillNote_extension/` | Vue 3 + Vite + WebExtension MV3 | `pnpm install && pnpm dev` |
详细结构与开发命令见 [CLAUDE.md](./CLAUDE.md)。提交时**单 PR 不要跨多个工作区做无关改动**,便于评审与回滚。
---
## 2. 分支模型
采用简化 Git Flow稳定主干 `master` + 长期开发集成 `develop` + 短生命周期业务分支。
| 分支类型 | 命名 | 长期保留 | 创建来源 | 合并去向 | 用途 |
|---|---|---|---|---|---|
| 生产主干 | `master` | ✅ | 仓库默认分支 | — | 始终保持可发布状态;只接收 `release/*``hotfix/*` 合入 |
| 开发主干 | `develop` | ✅ | `master` | `release/*` 回灌后 | 日常需求集成;常规开发都从这里起 |
| 功能分支 | `feature/*` | ❌ | `develop` | `develop` | 新功能 / 需求开发 |
| 修复分支 | `fix/*` | ❌ | `develop` | `develop` | 开发期间发现的缺陷修复(非线上问题) |
| 发布分支 | `release/*` | ❌ | `develop` | `master` + `develop` | 版本冻结、回归、发版准备 |
| 热修复 | `hotfix/*` | ❌ | `master` | `master` + `develop` | 线上紧急问题 |
### 基本原则
- `master` 只保存**已发布代码**,不接受日常开发提交。
- `develop` 是**唯一**长期开发集成分支。
- 所有业务开发**必须**用短生命周期分支,禁止长期占用个人开发分支。
- 一个分支只承载一个需求或一类明确的修复事项。
---
## 3. 分支命名
### 命名格式
```
feature/<scope>-<事项>
fix/<scope>-<事项>
release/<版本号>
hotfix/<scope>-<事项>
```
`<scope>` 优先用代码 scope与 commit message scope 对齐,见 §5常用
- `extension` — 浏览器插件(`BillNote_extension/`
- `frontend` — Web 前端(`BillNote_frontend/`
- `backend` — Python 后端(`backend/`
- `bilibili` / `youtube` / `douyin` / `kuaishou` — 平台特定改动
- `transcriber` — 音频转写
- `gpt` / `chat` — LLM / RAG
- `docker` / `ci` — 构建与发布
- `docs` — 文档
### 命名示例
```bash
# 功能开发
feature/extension-side-panel
feature/youtube-subtitle-innertube
feature/backend-rag-chat
# 开发期修复
fix/extension-task-status-unwrap
fix/bilibili-cookie-injection
fix/mlx-whisper-repo-id
# 发版
release/2.1.0
release/2.2.0
# 线上热修
hotfix/backend-cors-regex
hotfix/frontend-provider-switch
```
### 命名要求
- 全小写字母 / 数字 / 中划线
- `<事项>` 要表达**具体行为**,避免 `test` / `update` / `temp` / `wip` 这类无意义名
- `release/<版本号>` 必须与实际 tag 一致(如 `release/2.1.0``v2.1.0`
---
## 4. 标准协作流程
### 4.1 日常需求开发
```bash
git checkout develop
git pull origin develop
git checkout -b feature/<scope>-<事项>
# … 开发 + 自测 + commit …
git push -u origin feature/<scope>-<事项>
# 在 GitHub 上发起 PRbase = developcompare = 你的分支
```
合并通过且 PR closed 后,**删除本地与远端分支**
```bash
git branch -d feature/<scope>-<事项>
git push origin --delete feature/<scope>-<事项>
```
### 4.2 开发期缺陷修复
提测或联调中发现问题时,从 `develop``fix/*`。**不要在原 `feature/*` 上长期叠加零散修复**。
适用场景:
- 已合入 `develop` 后被测试打回的问题
- 多个功能集成后暴露的兼容性问题
- 非线上环境问题
### 4.3 版本发布
```bash
# 1. 从 develop 切 release
git checkout develop && git pull origin develop
git checkout -b release/<版本号>
# 2. 在 release 分支上:更新 README 版本号、写 CHANGELOG.md、必要的小修
git commit -am "docs: <版本号> CHANGELOG + README 版本"
git push -u origin release/<版本号>
# 3. 进入冻结期PR base=master 合并;同时 PR base=develop 回灌
# 4. master 上打 tag
git checkout master && git pull
git tag -a v<版本号> -m "BiliNote v<版本号>" && git push origin v<版本号>
# 5. release 分支已合入两边,删除
git push origin --delete release/<版本号>
```
要点:
- **冻结期内 `release/*` 不再合入新需求**,只允许修复发布缺陷。
- 发版窗口期内的新需求继续基于 `develop` 开发,**不进入当前 `release/*`**。
- 发布完成后必须把 `release/*` 同步回 `develop`,避免漏修。
### 4.4 线上紧急修复
```bash
git checkout master && git pull
git checkout -b hotfix/<scope>-<事项>
# … 修 + commit …
# PR base=master合入后立刻打 patch tag如 v2.1.1)发版
# 同一改动同时 PR base=develop 回灌
```
要点:
- `hotfix/*` 仅处理**线上阻断性 / 高优先级**缺陷。
- 非紧急问题不得绕过 `develop` 直接走热修流程。
- 若当前存在 `release/*` 即将发布,需评估是否同步到对应 release避免修复丢失。
---
## 5. 提交规范
### 5.1 Commit message 格式
> CI 已接入 [commitlint](https://commitlint.js.org)[`.commitlintrc.json`](./.commitlintrc.json) + [`.github/workflows/commitlint.yml`](./.github/workflows/commitlint.yml))。
> PR 上所有 commit 都会被校验type 不在白名单时合并按钮会被卡。
```
type(scope): subject
```
例:
```
feat(extension): 侧边栏接入思维导图markmap与 RAG 问答
fix(bilibili): 修正字幕优先链路在未登录态下的回退
docs(contributing): 新增贡献指南
chore(ci): 优化 docker 构建缓存
```
#### type
| type | 说明 |
|---|---|
| `feat` | 新功能 |
| `fix` | 缺陷修复 |
| `docs` | 文档变更 |
| `style` | 代码风格调整(不影响行为) |
| `refactor` | 重构(非 feat / 非 fix |
| `perf` | 性能优化 |
| `test` | 测试增删 |
| `build` | 构建系统 / 依赖变更 |
| `ci` | CI 配置变更 |
| `chore` | 杂项(不归入以上类别) |
| `ui` | 界面 / 交互层调整(仓库内既有用法) |
| `revert` | 回滚 |
#### scope
与 §3 的分支 scope 保持一致:`extension` / `frontend` / `backend` / `bilibili` / `youtube` / `douyin` / `kuaishou` / `transcriber` / `gpt` / `chat` / `docker` / `ci` / `docs` 等。
#### subject
- 用中文或英文都可以,**保持一种风格**。
- 用现在时陈述("新增 X" / "修复 Y" / "Add X" / "Fix Y")。
- 首字母不大写,结尾不加句号。
- 单行控制在 72 字符以内;如需详细说明,正文与标题之间空一行。
### 5.2 PR 标题与正文
- **PR 标题**沿用 commit message 格式,描述本次 PR 的总体改动。
- **PR 正文**应包含:
- 改动的"为什么"(背景 / issue / 用户场景)
- 改动的"做了什么"(关键文件、关键决策)
- **测试方式**(如何验证、覆盖了哪些 case
- **回归风险**与影响面
- 是否需要后端 / 前端 / 配置同步部署
---
## 6. 合并规范
### 6.1 合并前要求
- 合并前必须**先同步**目标分支最新代码(`git pull --rebase` 或在 PR 上点 "Update branch")。
- 合并前必须完成**自测**,确保核心流程可用。
- 后端改动需 `python -m py_compile` 至少通过;前端 / 插件改动需 `pnpm typecheck && pnpm build` 通过。
- **冲突由分支负责人解决**,不得留给评审人或发版人员。
### 6.2 评审
- 默认通过 PR 合并,**不允许**绕过 PR 直接 push 到 `master``develop`
- 评审人至少关注:业务影响、回归风险、是否夹带无关改动、目录归属是否合理。
- 修文档 / 改注释这种小变更允许 1 人评审通过;改业务逻辑 / 协议 / 共享模块至少 2 人评审。
### 6.3 合并方式
- `feature/*` / `fix/*` 合入 `develop`:推荐 **Squash and merge**,保持 develop 历史线性。
- `release/*` 合入 `master` 与回灌 `develop`:使用 **Merge commit (--no-ff)**,保留发版结构。
- `hotfix/*` 同上 release。
### 6.4 合并后
- 短期分支合并完成后**必须删除**(远端 + 本地)。
- 已完成的分支不得继续承接新需求;如需后续迭代请重新基于最新目标分支开新分支。
---
## 7. Git 钩子注意事项
`BillNote_extension/` 早期使用 `simple-git-hooks``postinstall`,会在仓库根目录 `.git/hooks/pre-commit` 注入 `pnpm lint-staged`,但仓库根没有 `package.json`,导致**任何 commit 都被钩子卡死**。**已在 v2.1.0 起移除**该 postinstall 配置。
如果你机器上还残留旧版本装下来的 hook
```bash
# 一次性清理
rm .git/hooks/pre-commit
# 或临时绕过本次 commit
SKIP_SIMPLE_GIT_HOOKS=1 git commit -m "..."
```
---
## 8. 版本号与 CHANGELOG
- 版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)`MAJOR.MINOR.PATCH`
- `MAJOR`:破坏性 API 变更或重大重构
- `MINOR`:新增特性、向后兼容
- `PATCH`bug 修复、向后兼容
- 每次发版**必须更新 [CHANGELOG.md](./CHANGELOG.md)**,按 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 格式分类Added / Changed / Fixed / Removed / Security / Internal
- 发版同时更新 `README.md` 顶部的版本号与"v\<版本号\> 新增"摘要段。
- `master` 上打 tag 形如 `vX.Y.Z`,注释中包含本次发布主线 + 引用 `CHANGELOG.md`
---
## 9. 禁止事项
- ❌ 直接在 `master` 上开发、提交、修复普通问题
- ❌ 新增长期 `dev-*` / `wip-*` / `<姓名>-dev` 个人分支作为日常协作分支
- ❌ 一个分支同时承载多个需求 / 多个缺陷 / 跨版本内容
- ❌ 未评审、未自测、未通过基础校验直接合并
-`release/*` 冻结后继续混入新需求
-`hotfix/*` 只合入 `master` 而不回灌 `develop`
- ❌ 提交 / 仓库内包含密钥、API key、`.env` 等敏感文件
- ❌ 提交 `node_modules/` / `dist/` / `extension/dist/` / `__pycache__/` / 大型二进制(参考各级 `.gitignore`
---
## 10. 历史分支迁移
仓库历史上存在多条已合并但未删除的分支(见 `git branch -a`)。即日起:
- 不再创建 `dev-*` / `<姓名>-*` 个人分支
- 已合入主干的旧分支,由发版人统一清理
- 未完成需求应尽快迁移到符合 §3 命名规范的新分支
---
## 11. 推荐流程图
```text
master ← hotfix/* (线上紧急修复)
↑ ↑
│ │
release/* ← develop ← feature/* (功能开发)
│ ↑
└───────────┘
fix/* (开发期修复)
回灌
```
---
## 12. 执行口径速查
| 场景 | 流程 |
|---|---|
| 新功能 | `develop``feature/*``develop` |
| 提测后发现问题 | `develop``fix/*``develop` |
| 版本发布 | `develop``release/*``master` + `develop`;打 tag |
| 线上紧急故障 | `master``hotfix/*``master` + `develop`;打 patch tag |
| 发版期内新需求 | 基于 `develop``feature/*`**不**进入当前 `release/*` |
---
如有改进建议,欢迎开 PR 修订本文档(`docs(contributing): ...`)。

View File

@@ -26,15 +26,18 @@ RUN pip install --no-cache-dir -i ${PIP_INDEX} -r requirements.txt
COPY ./backend /tmp/backend
# === 阶段2构建 Frontend ===
FROM node:18-alpine AS frontend-builder
# Node 18-alpine 跑不动 Tailwind v4 / Vite 6前者要求 Node 20+,后者推荐 Node 20+
# 升到 node:20-alpine。alpine 走 muslpnpm 会按 lockfile 拉 *-linux-x64-musl native binary。
FROM node:20-alpine AS frontend-builder
RUN corepack enable && corepack prepare pnpm@latest --activate
WORKDIR /tmp/frontend
# 先复制 package.json 利用依赖层缓存
COPY ./BillNote_frontend/package.json ./
RUN pnpm install
# 先复制 package.json + lockfile 利用依赖层缓存
# --frozen-lockfile 保证 CI 与本地开发依赖版本一致,杜绝 semver 漂移引入的破坏性升级
COPY ./BillNote_frontend/package.json ./BillNote_frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY ./BillNote_frontend /tmp/frontend

View File

@@ -3,7 +3,7 @@
<p align="center">
<img src="./doc/icon.svg" alt="BiliNote Banner" width="50" height="50" />
</p>
<h1 align="center" > BiliNote v2.1.0</h1>
<h1 align="center" > BiliNote v2.1.3</h1>
</div>
<p align="center"><i>AI 视频笔记生成工具 让 AI 为你的视频做笔记</i></p>
@@ -53,6 +53,23 @@ BiliNote 是一个开源的 AI 视频笔记助手支持通过哔哩哔哩、Y
- 笔记顶部视频封面 Banner 展示
- 工作区和生成历史面板支持折叠/展开
### v2.1.3 修订
- 修复 DeepSeek 等非多模态供应商被 400 拒绝的问题issue #282`UniversalGPT` 的 message builder 按是否带图切换 string / 多模态数组形态
- 感谢 @voidborne-d (#345)
### v2.1.2 修订
- 修复 v2.1.1 触发的 ghcr.io Docker 镜像构建失败Node 18 + Tailwind v4 不兼容、缺 lockfile
- README 补上微信群二维码
### v2.1.1 修订
- 工程化与文档收尾CONTRIBUTING.md / RELEASING.md / issue + PR 模板 / commitlint CI / 插件发版工作流
- 关于页群聊二维码:换成最新版,改为 import 本地资源,不再依赖 CDN
- 关于页移除 QQ 群入口(仅保留微信群)
- 详见 [CHANGELOG.md](./CHANGELOG.md)
### v2.1.0 新增
- 浏览器插件Chrome / Edge / Firefox MV3—— 工具栏 popup、视频页悬浮按钮、右键菜单、侧边栏Markdown / 思维导图 / AI 问答)四件套
@@ -198,7 +215,12 @@ docker-compose -f docker-compose.gpu.yml up -d
- [ ] 笔记导出为 PDF / Word / Notion
### Contact and Join-联系和加入社区
年会恢复更新以后放出最新社区地址
扫码加入 BiliNote 交流微信群(如二维码失效,请到 [Issues](https://github.com/JefferyHcool/BiliNote/issues) 反馈):
<p align="center">
<img src="./doc/wechat.png" alt="BiliNote 交流微信群" width="240" />
</p>

158
RELEASING.md Normal file
View File

@@ -0,0 +1,158 @@
# 发版手册Release Manager
本文档面向**发版执行者**,覆盖从 `develop` 切发版到产物上架商店的完整步骤。日常分支与提交规范见 [CONTRIBUTING.md](./CONTRIBUTING.md)。
---
## 流程总览
```
develop ──→ release/X.Y.Z ──→ PR ─→ master ──→ 打 tag vX.Y.Z
│ │ │
└──→ PR 回灌 ──→ develop └──→ CI 自动构建插件产物 + 挂到 GitHub Release
人工上传商店Chrome/Edge/Firefox
```
---
## 1. 切发布分支
```bash
git checkout develop && git pull origin develop
git checkout -b release/X.Y.Z
```
版本号遵循 [SemVer](https://semver.org/lang/zh-CN/)`MAJOR.MINOR.PATCH`
## 2. 写 CHANGELOG更新版本号
`release/X.Y.Z` 上:
- 编辑 [`CHANGELOG.md`](./CHANGELOG.md),新增 `## [X.Y.Z] - YYYY-MM-DD` 段,按 Keep a Changelog 分类Added / Changed / Fixed / Removed / Security / Internal
- 编辑 [`README.md`](./README.md) 顶部标题中的版本号 + 新增"vX.Y.Z 新增"摘要段
- 重大变更也同步更新 [`CLAUDE.md`](./CLAUDE.md)
```bash
git commit -am "docs: vX.Y.Z CHANGELOG + README 版本"
git push -u origin release/X.Y.Z
```
## 3. 合并到 master + 回灌 develop
在 GitHub 上发起两个 PR
| PR | base | 合并方式 |
|---|---|---|
| `release/X.Y.Z``master` | `master` | **Merge commit (--no-ff)** |
| `release/X.Y.Z``develop` | `develop` | **Merge commit (--no-ff)** |
`master` 分支保护要求 review 通过。回灌 `develop` 是为了把发版冻结期内的小修同步回来。
## 4. 打 tag
```bash
git checkout master && git pull origin master
git tag -a vX.Y.Z -m "BiliNote vX.Y.Z
主线:
- ...
详见 CHANGELOG.md"
git push origin vX.Y.Z
```
push tag **会自动触发 [`.github/workflows/release-extension.yml`](.github/workflows/release-extension.yml)**:构建插件并把 `.zip` / `.xpi` / `.crx` 挂到对应 GitHub Release。
## 5. 创建 GitHub Release如果还没有
CI 默认会创建 / 更新 `vX.Y.Z` 对应的 Release。如果你想自己写 release notes
1. 打开 https://github.com/JefferyHcool/BiliNote/releases/new
2. Tag: 选 `vX.Y.Z`
3. Title: `vX.Y.Z`
4. Body: 直接贴 [`CHANGELOG.md`](./CHANGELOG.md) 的对应段
5. CI 跑完后 Release 页面会自动出现 `bilinote-extension-X.Y.Z.zip` / `.xpi` / `.crx`
## 6. 上传到各商店(人工)
商店审核普遍 1-3 个工作日。建议**先上 Chrome → Edge → Firefox**Edge 接受同一份 zip
### Chrome Web Store
1. https://chrome.google.com/webstore/devconsole
2. 选 BiliNote → 左侧 **Package****Upload new package**
3. 上传 `bilinote-extension-X.Y.Z.zip`
4. 检查 listing描述 / 图标 / 截图无变化可保持),点 **Submit for review**
### Microsoft Edge Add-ons
1. https://partner.microsoft.com/dashboard/microsoftedge
2. 选 BiliNote → **New submission**
3. 上传同一份 `.zip`Edge Add-ons 与 Chrome 完全兼容 MV3
4. 提交审核
### Firefox Add-ons (AMO)
1. https://addons.mozilla.org/developers/
2. 选 BiliNote → **Upload New Version**
3. 上传 `bilinote-extension-X.Y.Z.xpi`
4. 选择"在 AMO 公开"或"自托管"
5. 提交审核
### 桌面端 (Tauri)
仓库已有 GitHub Actions 在 `v*` tag 时构建桌面端安装包并自动挂到 GitHub Release无需额外操作。
## 7. 清理
```bash
# release 分支已合到 master 与 develop删掉
git push origin --delete release/X.Y.Z
git branch -d release/X.Y.Z
```
---
## 自动发布到商店(可选)
`.github/workflows/release-extension.yml` 末尾有三段商店自动发布的 job 注释。要启用:
1. 在 https://github.com/JefferyHcool/BiliNote/settings/secrets/actions 加 secrets
| 商店 | 需要的 secret |
|---|---|
| Chrome | `CHROME_EXTENSION_ID``CHROME_CLIENT_ID``CHROME_CLIENT_SECRET``CHROME_REFRESH_TOKEN` |
| Edge | `EDGE_PRODUCT_ID``EDGE_CLIENT_ID``EDGE_API_KEY` |
| Firefox | `FIREFOX_ADDON_UUID``FIREFOX_API_KEY``FIREFOX_API_SECRET` |
2. 解开 workflow 文件末尾的 `publish-chrome` / `publish-edge` / `publish-firefox` job 注释。
3. 推 tag 时即自动发布。
> Chrome 各 secret 的获取方式:[chrome-webstore-upload-cli 文档](https://github.com/fregante/chrome-webstore-upload-cli/blob/main/How%20to%20generate%20Google%20API%20keys.md)
> Edge[Edge Add-ons API](https://learn.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/api/using-addons-api)
> Firefoxhttps://addons.mozilla.org/en-US/developers/addon/api/key/
---
## 紧急 hotfix 发版
线上紧急问题不走 `release/*`,走 `hotfix/*`
```bash
git checkout master && git pull
git checkout -b hotfix/<scope>-<事项>
# … 修复 ...
# PR base=master 合入;同时 PR base=develop 回灌
```
合入 master 后通常打 patch tag`v2.1.1`CI 流程同上。
---
## 历史发布快查
| Version | Date | Tag |
|---|---|---|
| 2.1.0 | 2026-05-07 | [`v2.1.0`](https://github.com/JefferyHcool/BiliNote/releases/tag/v2.1.0) |
| 2.0.0 | (上游 web 端 v2.0.0) | [`v2.0.0`](https://github.com/JefferyHcool/BiliNote/releases/tag/v2.0.0) |

View File

@@ -53,20 +53,26 @@ class UniversalGPT(GPT):
extras=kwargs.get('extras'),
)
# ⛳ 组装 content 数组,支持 text + image_url 混合
content: List[dict] = [{"type": "text", "text": content_text}]
video_img_urls = kwargs.get('video_img_urls', [])
for url in video_img_urls:
content.append({
"type": "image_url",
"image_url": {
"url": url,
"detail": "auto"
}
})
content: list[dict] | str
if video_img_urls:
# 有截图时走 OpenAI 多模态 content 数组text + image_url
content = [{"type": "text", "text": content_text}]
for url in video_img_urls:
content.append({
"type": "image_url",
"image_url": {
"url": url,
"detail": "auto"
}
})
else:
# 纯文本场景退回 string contentDeepSeek deepseek-chat 等非多模态模型
# 不识别 [{"type":"text",...}] 数组形态,会返回 invalid_request_error
# issue #282。OpenAI 规范本身也允许 content 为 string。
content = content_text
# 正确格式:整体包在一个 message 里role + content array
messages = [{
"role": "user",
"content": content
@@ -83,9 +89,10 @@ class UniversalGPT(GPT):
def _build_merge_messages(self, partials: list) -> list:
merge_text = MERGE_PROMPT + "\n\n" + "\n\n---\n\n".join(partials)
# 合并阶段没有图片,直接用 string content 兼容非多模态模型issue #282
return [{
"role": "user",
"content": [{"type": "text", "text": merge_text}]
"content": merge_text
}]
def _checkpoint_path(self, checkpoint_key: str) -> Path:

View File

@@ -0,0 +1,189 @@
"""issue #282 回归测试UniversalGPT 拼装 content 时按是否有图片切换 string / array 形态。
DeepSeek deepseek-chat 等非多模态模型只接受 ``content`` 为字符串,旧实现无条件
emit ``[{"type":"text","text":...}]`` 导致 ``invalid_request_error``。
"""
import importlib.util
import pathlib
import sys
import types
import unittest
def _install_stubs():
app_mod = types.ModuleType("app")
gpt_pkg = types.ModuleType("app.gpt")
models_pkg = types.ModuleType("app.models")
base_mod = types.ModuleType("app.gpt.base")
class _GPT:
pass
base_mod.GPT = _GPT
prompt_builder_mod = types.ModuleType("app.gpt.prompt_builder")
def _generate_base_prompt(**_kwargs):
return "PROMPT_BODY"
prompt_builder_mod.generate_base_prompt = _generate_base_prompt
prompt_mod = types.ModuleType("app.gpt.prompt")
prompt_mod.BASE_PROMPT = ""
prompt_mod.AI_SUM = ""
prompt_mod.SCREENSHOT = ""
prompt_mod.LINK = ""
prompt_mod.MERGE_PROMPT = "MERGE_HEAD"
utils_mod = types.ModuleType("app.gpt.utils")
def _fix_markdown(text):
return text
utils_mod.fix_markdown = _fix_markdown
request_chunker_mod = types.ModuleType("app.gpt.request_chunker")
class _RequestChunker:
def __init__(self, *_args, **_kwargs):
pass
def group_texts_by_budget(self, texts, _builder, **_kwargs):
return [texts]
request_chunker_mod.RequestChunker = _RequestChunker
gpt_model_mod = types.ModuleType("app.models.gpt_model")
class _GPTSource:
pass
gpt_model_mod.GPTSource = _GPTSource
transcriber_model_mod = types.ModuleType("app.models.transcriber_model")
class _TranscriptSegment:
def __init__(self, **kwargs):
self.start = kwargs.get("start", 0)
self.end = kwargs.get("end", 0)
self.text = kwargs.get("text", "")
transcriber_model_mod.TranscriptSegment = _TranscriptSegment
sys.modules.setdefault("app", app_mod)
sys.modules.setdefault("app.gpt", gpt_pkg)
sys.modules.setdefault("app.models", models_pkg)
sys.modules["app.gpt.base"] = base_mod
sys.modules["app.gpt.prompt_builder"] = prompt_builder_mod
sys.modules["app.gpt.prompt"] = prompt_mod
sys.modules["app.gpt.utils"] = utils_mod
sys.modules["app.gpt.request_chunker"] = request_chunker_mod
sys.modules["app.models.gpt_model"] = gpt_model_mod
sys.modules["app.models.transcriber_model"] = transcriber_model_mod
def _load_universal_gpt_class():
_install_stubs()
root = pathlib.Path(__file__).resolve().parents[1]
module_path = root / "app" / "gpt" / "universal_gpt.py"
spec = importlib.util.spec_from_file_location(
"universal_gpt_content_format", module_path
)
if spec is None or spec.loader is None:
raise ImportError("universal_gpt module spec not found")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module.UniversalGPT
UniversalGPT = _load_universal_gpt_class()
class _DummyClient:
"""create_messages 不会真的调用 client给个空壳即可。"""
def _make_gpt():
return UniversalGPT(_DummyClient(), model="deepseek-chat")
class TestCreateMessagesContentFormat(unittest.TestCase):
"""覆盖 create_messages 在不同 video_img_urls 输入下的输出形态。"""
def test_no_images_emits_string_content(self):
"""无图片时 content 为 strDeepSeek / 非多模态模型可解析)。"""
gpt = _make_gpt()
messages = gpt.create_messages(segments=[])
self.assertEqual(len(messages), 1)
self.assertEqual(messages[0]["role"], "user")
self.assertIsInstance(messages[0]["content"], str)
self.assertEqual(messages[0]["content"], "PROMPT_BODY")
def test_empty_image_list_emits_string_content(self):
"""显式传入空列表也要走纯文本分支,避免图片字段误触发。"""
gpt = _make_gpt()
messages = gpt.create_messages(segments=[], video_img_urls=[])
self.assertIsInstance(messages[0]["content"], str)
def test_with_images_emits_multimodal_array(self):
"""有图片时保留多模态 array 形态,确保多模态模型功能不退化。"""
gpt = _make_gpt()
messages = gpt.create_messages(
segments=[],
video_img_urls=["https://example.com/a.jpg", "https://example.com/b.jpg"],
)
content = messages[0]["content"]
self.assertIsInstance(content, list)
self.assertEqual(len(content), 3) # 1 text + 2 images
self.assertEqual(content[0], {"type": "text", "text": "PROMPT_BODY"})
self.assertEqual(content[1]["type"], "image_url")
self.assertEqual(content[1]["image_url"]["url"], "https://example.com/a.jpg")
self.assertEqual(content[1]["image_url"]["detail"], "auto")
self.assertEqual(content[2]["image_url"]["url"], "https://example.com/b.jpg")
def test_no_image_url_field_when_no_images(self):
"""纯文本响应里不应该出现 image_url 关键字 —— 这是触发 DeepSeek 400 的根因。"""
gpt = _make_gpt()
messages = gpt.create_messages(segments=[])
import json
serialized = json.dumps(messages, ensure_ascii=False)
self.assertNotIn("image_url", serialized)
class TestBuildMergeMessagesContentFormat(unittest.TestCase):
"""合并阶段从不带图片,应该统一走 string content 路径。"""
def test_merge_messages_use_string_content(self):
"""否则长视频 chunk 后的合并阶段还会复现 issue #282 错误。"""
gpt = _make_gpt()
messages = gpt._build_merge_messages(["partial-A", "partial-B"])
self.assertEqual(len(messages), 1)
self.assertEqual(messages[0]["role"], "user")
self.assertIsInstance(messages[0]["content"], str)
self.assertIn("MERGE_HEAD", messages[0]["content"])
self.assertIn("partial-A", messages[0]["content"])
self.assertIn("partial-B", messages[0]["content"])
def test_merge_messages_no_image_url_field(self):
gpt = _make_gpt()
messages = gpt._build_merge_messages(["x"])
import json
serialized = json.dumps(messages, ensure_ascii=False)
self.assertNotIn("image_url", serialized)
if __name__ == "__main__":
unittest.main()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 623 KiB

After

Width:  |  Height:  |  Size: 11 KiB