Skip to content

Commit 1e8f6c8

Browse files
chore: resolve merge conflicts in review service and story tree page
2 parents 8c38283 + f50514e commit 1e8f6c8

38 files changed

Lines changed: 1522 additions & 236 deletions

.github/workflows/desktop-build.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ name: Desktop Build
33
on:
44
push:
55
branches: [ main, master, feat/desktop-branch ]
6+
tags:
7+
- 'v*.*.*-*' # prerelease: v0.1.0-beta.1
8+
- 'v[0-9]*.[0-9]*.[0-9]*' # release: v0.1.0 (纯版本号)
69
paths:
710
- 'desktop/**'
811
- '.github/workflows/desktop-build.yml'
@@ -102,3 +105,61 @@ jobs:
102105
if-no-files-found: error
103106
retention-days: 14
104107

108+
prerelease:
109+
name: Publish prerelease
110+
runs-on: ubuntu-latest
111+
needs: build
112+
if: |
113+
startsWith(github.ref, 'refs/tags/v') &&
114+
contains(github.ref_name, '-')
115+
permissions:
116+
contents: write
117+
steps:
118+
- name: Checkout repository
119+
uses: actions/checkout@v4
120+
121+
- name: Download artifacts
122+
uses: actions/download-artifact@v4
123+
with:
124+
path: dist
125+
126+
- name: Create prerelease
127+
env:
128+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
129+
run: |
130+
gh release create "${GITHUB_REF_NAME}" \
131+
--prerelease \
132+
--title "${GITHUB_REF_NAME}" \
133+
--notes "Prerelease ${GITHUB_REF_NAME}"
134+
# 处理带空格的文件名
135+
find dist -type f -print0 | xargs -0 -I {} gh release upload "${GITHUB_REF_NAME}" "{}" --clobber
136+
137+
release:
138+
name: Publish release
139+
runs-on: ubuntu-latest
140+
needs: build
141+
if: |
142+
startsWith(github.ref, 'refs/tags/v') &&
143+
!contains(github.ref_name, '-')
144+
permissions:
145+
contents: write
146+
steps:
147+
- name: Checkout repository
148+
uses: actions/checkout@v4
149+
with:
150+
fetch-depth: 0
151+
152+
- name: Download artifacts
153+
uses: actions/download-artifact@v4
154+
with:
155+
path: dist
156+
157+
- name: Create release
158+
env:
159+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
160+
run: |
161+
gh release create "${GITHUB_REF_NAME}" \
162+
--title "${GITHUB_REF_NAME}" \
163+
--notes "Release ${GITHUB_REF_NAME}"
164+
# 处理带空格的文件名
165+
find dist -type f -print0 | xargs -0 -I {} gh release upload "${GITHUB_REF_NAME}" "{}" --clobber

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
push:
55
branches: [ main, master ]
66
tags:
7-
- 'v*'
7+
- 'android-v*'
88
pull_request:
99
branches: [ main, master ]
1010
workflow_dispatch:

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ signing.properties
2929
/desktop/tests
3030
desktop/src/data
3131
desktop/.venv310
32+
/desktop/release
33+
/desktop/backend/build/
34+
/desktop/backend/dist/
3235
.venv
3336
/desktop/memory-service/.venv
3437
/desktop/memory-service/__pycache__
@@ -52,4 +55,4 @@ test.mp4
5255

5356
# Desktop docs (local only)
5457
desktop/docs/
55-
**/node_modules
58+
**/node_modules

desktop/README.md

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,70 @@ pnpm dev
116116

117117
---
118118

119+
## 📦 桌面端发布规则(GitHub Actions)
120+
121+
桌面端发布使用 tag 触发,并区分 **预发布****正式发布**
122+
123+
- **预发布(Prerelease)**`vX.Y.Z-xxx`
124+
例:`v0.1.0-beta.1`
125+
- **正式发布(Release)**`vX.Y.Z`
126+
例:`v0.1.0`
127+
128+
Android 的发布已独立为 `android-v*` 标签,避免干扰桌面端发布。
129+
130+
示例:
131+
132+
```bash
133+
# 预发布(会生成 GitHub Prerelease)
134+
git tag v0.1.0-beta.1
135+
git push origin v0.1.0-beta.1
136+
137+
# 正式发布(会生成 GitHub Release)
138+
git tag v0.1.0
139+
git push origin v0.1.0
140+
141+
# Android 发布(仅 Android 流程触发)
142+
git tag android-v0.1.0
143+
git push origin android-v0.1.0
144+
```
145+
146+
> 说明:桌面端产物为 macOS `.dmg` 与 Windows `.exe`,不会生成 APK。
147+
148+
---
149+
150+
## 🧰 模型下载与缓存目录(HF / ModelScope)
151+
152+
应用内的语音识别模型(尤其是 FunASR ONNX)会在首次使用/点击下载时自动拉取,并缓存到本机磁盘。为了方便管理、并兼容 Windows / macOS 的默认目录差异,项目默认把缓存放到 Electron 的 `userData` 目录下(不同系统会自动选择合适位置)。
153+
154+
如果你希望把模型统一下载到自己指定的盘符/目录(例如放到大硬盘、NAS 挂载目录等),推荐通过环境变量覆盖:
155+
156+
- `ASR_CACHE_BASE`:ASR 缓存根目录(推荐只改这个)
157+
- `HF_HOME`:HuggingFace 缓存根目录(高级用法)
158+
- `ASR_CACHE_DIR`:HuggingFace hub 目录(高级用法)
159+
- `MODELSCOPE_CACHE`:ModelScope 缓存根目录(注意:实际会写到 `<MODELSCOPE_CACHE>/hub`
160+
161+
示例(macOS/Linux):
162+
163+
```bash
164+
ASR_CACHE_BASE=/data/livegalgame/asr-cache pnpm dev
165+
```
166+
167+
示例(Windows PowerShell):
168+
169+
```powershell
170+
$env:ASR_CACHE_BASE="D:\\LiveGalGame\\asr-cache"; pnpm dev
171+
```
172+
173+
如果你想手动使用 ModelScope CLI 把某个模型下载到指定位置(不走应用内下载),确实可以用:
174+
175+
```bash
176+
modelscope download --model 'Qwen/Qwen2-7B' --local_dir /data/models/Qwen2-7B
177+
```
178+
179+
但应用内的 FunASR 模型下载是由 `funasr_onnx` 触发的(不是直接下载单个 Qwen 模型),因此更推荐用上面的环境变量来统一管理缓存位置。
180+
181+
---
182+
119183
## 🔧 开发者指南
120184

121185
如果你想参与开发或了解技术细节,请查看项目源码:
@@ -126,4 +190,3 @@ pnpm dev
126190
- `src/db/` - 本地数据存储
127191

128192
欢迎提交 PR!有问题请加 QQ 群:**1074602400**
129-

desktop/scripts/download_funasr_model.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,38 @@ def main():
3939

4040
# 设置环境变量
4141
# FunASR 默认下载到 ~/.cache/modelscope/hub
42-
# 如果指定了 cache-dir,我们尝试通过 MODELSCOPE_CACHE 环境变量来控制
42+
# MODELSCOPE_CACHE 语义通常是 "base dir",实际下载会落到 <base>/hub。
43+
# 但历史上我们也可能传入了 ".../hub"。这里做兼容归一化,确保 Win/mac/Linux 都稳定落盘。
44+
cache_base = None
45+
cache_hub = None
4346
if args.cache_dir:
44-
os.environ["MODELSCOPE_CACHE"] = args.cache_dir
45-
os.environ["MODELSCOPE_CACHE_HOME"] = args.cache_dir
47+
raw = os.path.abspath(args.cache_dir)
48+
if os.path.basename(raw).lower() == "hub":
49+
cache_base = os.path.dirname(raw)
50+
cache_hub = raw
51+
else:
52+
cache_base = raw
53+
cache_hub = os.path.join(raw, "hub")
4654
else:
47-
cache = os.environ.get("ASR_CACHE_DIR")
55+
# 兼容旧逻辑:若仅提供 ASR_CACHE_DIR(通常是 HF 的 hub),尝试回退到其父目录作为 base
56+
cache = os.environ.get("MODELSCOPE_CACHE") or os.environ.get("MODELSCOPE_CACHE_HOME") or os.environ.get("ASR_CACHE_DIR")
4857
if cache:
49-
os.environ["MODELSCOPE_CACHE"] = cache
50-
os.environ["MODELSCOPE_CACHE_HOME"] = cache
58+
raw = os.path.abspath(cache)
59+
if os.path.basename(raw).lower() == "hub":
60+
cache_base = os.path.dirname(raw)
61+
cache_hub = raw
62+
else:
63+
cache_base = raw
64+
cache_hub = os.path.join(raw, "hub")
65+
66+
if cache_base:
67+
os.environ["MODELSCOPE_CACHE"] = cache_base
68+
os.environ["MODELSCOPE_CACHE_HOME"] = cache_base
69+
try:
70+
os.makedirs(cache_base, exist_ok=True)
71+
os.makedirs(cache_hub, exist_ok=True)
72+
except Exception:
73+
pass
5174

5275
emit("manifest", modelId=args.model_id, message="准备下载 FunASR 模型...", totalBytes=0, fileCount=0)
5376

@@ -98,12 +121,18 @@ def main():
98121
emit("manifest", modelId=args.model_id, message=f"正在下载标点模型: {punc_model_dir} (4/4)")
99122
CT_Transformer(model_dir=punc_model_dir, quantize=use_quantize, intra_op_num_threads=1)
100123

101-
emit("completed", modelId=args.model_id, message="FunASR 模型下载完成", localDir=os.environ.get("MODELSCOPE_CACHE"))
124+
emit(
125+
"completed",
126+
modelId=args.model_id,
127+
message="FunASR 模型下载完成",
128+
localDir=cache_hub or os.environ.get("MODELSCOPE_CACHE") or "",
129+
cacheBase=os.environ.get("MODELSCOPE_CACHE") or "",
130+
cacheHub=cache_hub or "",
131+
)
102132

103133
except Exception as e:
104134
emit("error", modelId=args.model_id, message=str(e), traceback=traceback.format_exc())
105135
sys.exit(1)
106136

107137
if __name__ == "__main__":
108138
main()
109-

desktop/src/asr/asr-cache-env.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import fs from 'fs';
2+
import path from 'path';
3+
4+
function ensureDir(targetPath) {
5+
try {
6+
fs.mkdirSync(targetPath, { recursive: true });
7+
} catch {
8+
// ignore mkdir errors
9+
}
10+
}
11+
12+
export function computeAsrCachePaths({ userDataDir, asrCacheBase }) {
13+
const base = asrCacheBase || path.join(userDataDir, 'asr-cache');
14+
const hfHome = path.join(base, 'hf-home');
15+
const asrCacheDir = path.join(hfHome, 'hub');
16+
const msBase = path.join(base, 'modelscope');
17+
const msHub = path.join(msBase, 'hub');
18+
return {
19+
asrCacheBase: base,
20+
hfHome,
21+
asrCacheDir,
22+
modelscopeCacheBase: msBase,
23+
modelscopeCacheHub: msHub,
24+
};
25+
}
26+
27+
export function applyAsrCacheEnv({ userDataDir, asrCacheBase, force = false }) {
28+
const paths = computeAsrCachePaths({ userDataDir, asrCacheBase });
29+
30+
// 允许用户通过环境变量强制覆盖;否则用我们计算的默认值/GUI 配置值。
31+
if (force || !process.env.ASR_CACHE_BASE) {
32+
process.env.ASR_CACHE_BASE = paths.asrCacheBase;
33+
}
34+
35+
if (force || !process.env.HF_HOME) {
36+
process.env.HF_HOME = paths.hfHome;
37+
}
38+
39+
if (force || !process.env.ASR_CACHE_DIR) {
40+
process.env.ASR_CACHE_DIR = paths.asrCacheDir;
41+
}
42+
43+
if (force) {
44+
process.env.MODELSCOPE_CACHE = paths.modelscopeCacheBase;
45+
process.env.MODELSCOPE_CACHE_HOME = paths.modelscopeCacheBase;
46+
} else if (!process.env.MODELSCOPE_CACHE && !process.env.MODELSCOPE_CACHE_HOME) {
47+
process.env.MODELSCOPE_CACHE = paths.modelscopeCacheBase;
48+
process.env.MODELSCOPE_CACHE_HOME = paths.modelscopeCacheBase;
49+
} else if (!process.env.MODELSCOPE_CACHE_HOME && process.env.MODELSCOPE_CACHE) {
50+
process.env.MODELSCOPE_CACHE_HOME = process.env.MODELSCOPE_CACHE;
51+
}
52+
53+
// mkdir(不抛错)
54+
ensureDir(process.env.ASR_CACHE_BASE);
55+
ensureDir(process.env.HF_HOME);
56+
ensureDir(process.env.ASR_CACHE_DIR);
57+
if (process.env.MODELSCOPE_CACHE) {
58+
ensureDir(process.env.MODELSCOPE_CACHE);
59+
ensureDir(path.join(process.env.MODELSCOPE_CACHE, 'hub'));
60+
}
61+
62+
return computeAsrCachePaths({
63+
userDataDir,
64+
asrCacheBase: process.env.ASR_CACHE_BASE,
65+
});
66+
}

desktop/src/asr/model-cache.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,19 @@ export function cleanModelScopeLocks(cacheDir, maxAgeMs = 10 * 60 * 1000) {
8787
function getModelCacheCandidates() {
8888
const homeDir = os.homedir();
8989
const userDataDir = app.getPath('userData');
90+
const msEnv = process.env.MODELSCOPE_CACHE || process.env.MODELSCOPE_CACHE_HOME;
91+
const msBase = msEnv && path.basename(msEnv).toLowerCase() === 'hub' ? path.dirname(msEnv) : msEnv;
92+
const msHub = msBase ? path.join(msBase, 'hub') : (msEnv && path.basename(msEnv).toLowerCase() === 'hub' ? msEnv : null);
93+
const appMsBase = path.join(userDataDir, 'asr-cache', 'modelscope');
94+
const appMsHub = path.join(appMsBase, 'hub');
95+
9096
return [
91-
process.env.MODELSCOPE_CACHE,
97+
msHub,
98+
msBase,
9299
process.env.ASR_CACHE_DIR,
93100
process.env.HF_HOME ? path.join(process.env.HF_HOME, 'hub') : null,
94-
path.join(userDataDir, 'asr-cache', 'modelscope', 'hub'), // model-manager.js 下载位置
101+
appMsHub, // model-manager.js 默认下载位置(ModelScope hub)
102+
appMsBase, // model-manager.js 默认下载位置(ModelScope base)
95103
path.join(userDataDir, 'hf-home', 'hub'),
96104
path.join(userDataDir, 'ms-cache'),
97105
homeDir ? path.join(homeDir, '.cache', 'huggingface', 'hub') : null,
@@ -189,4 +197,3 @@ export function resolveFunasrModelScopeCache(preset) {
189197

190198
return { cacheDir: systemMsCache, found: false };
191199
}
192-

0 commit comments

Comments
 (0)