Files
get_wechat/scripts/build-desktop.ps1
yuanzhipeng eecbe4172e feat(api): 将万川平台配置迁移至后端存储
- 移除前端localStorage依赖,改用后端SQLite作为唯一数据源
- 新增getWanchuanConfig和saveWanchuanConfig函数用于配置读写
- 添加getBoundKnowledgeBase函数统一获取绑定知识库信息
- 支持桌面应用端口变化时正确读取配置

refactor(settings): 重构万川平台配置管理逻辑

- 移除localStorage配置存储,改为后端API调用
- 实现配置自动恢复和防抖保存机制
- 添加token过期自动重登功能
- 优化知识库选择和连接状态管理

fix(knowledge): 修复知识库上传异步问题

- 将getBoundKnowledgeBase调用改为await异步处理
- 统一各页面的知识库信息获取方式
- 修正上传接口datasetId使用逻辑

feat(electron): 添加chatlog.exe存在性检查

- 新增ensureChatlogExe函数验证执行文件存在
- 防止杀毒软件误删导致的ENONENT错误
- 提供用户友好的错误提示和解决方案
2026-06-24 10:13:20 +08:00

252 lines
9.7 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
param(
[string]$PythonLauncher = "py",
[string]$PythonVersion = "-3.12",
[switch]$SkipIcon,
[switch]$SkipBackend,
[switch]$SkipFrontend,
[switch]$SkipInstaller,
[switch]$Sign,
[string]$CertificateFile,
[string]$CertificatePassword,
[string]$PublisherName,
[string]$TimestampServer = "http://timestamp.digicert.com",
[string[]]$VoiceSmokeKeys = @(),
[switch]$ForceSign,
[switch]$CleanBackend
)
$ErrorActionPreference = "Stop"
$Root = Resolve-Path (Join-Path $PSScriptRoot "..")
$Frontend = Join-Path $Root "chatlab-web\frontend"
$Backend = Join-Path $Root "chatlog_fastAPI"
$Electron = Join-Path $Root "electron-launcher"
$Resources = Join-Path $Electron "build-resources"
$Release = Join-Path $Root "release"
function Invoke-Python312($Arguments) {
$pyCommand = Get-Command $PythonLauncher -ErrorAction SilentlyContinue
if ($pyCommand) {
try {
& $PythonLauncher $PythonVersion -V | Out-Null
if ($LASTEXITCODE -eq 0) {
& $PythonLauncher $PythonVersion @Arguments
return
}
} catch {
Write-Host "Python launcher $PythonLauncher $PythonVersion is not available, falling back to user Python312."
}
}
$fallback = Join-Path $env:LOCALAPPDATA "Programs\Python\Python312\python.exe"
if (-not (Test-Path $fallback)) {
throw "Python 3.12 was not found. Install it first or pass -PythonLauncher/-PythonVersion."
}
& $fallback @Arguments
}
function Reset-Dir($Path) {
if (Test-Path $Path) {
$resolved = Resolve-Path $Path
if (-not $resolved.Path.StartsWith($Root.Path)) {
throw "Refusing to remove path outside project: $($resolved.Path)"
}
Remove-Item -LiteralPath $resolved.Path -Recurse -Force
}
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
function Copy-Dir($Source, $Dest) {
if (-not (Test-Path $Source)) {
throw "Missing required source: $Source"
}
New-Item -ItemType Directory -Force -Path (Split-Path $Dest) | Out-Null
Copy-Item -LiteralPath $Source -Destination $Dest -Recurse -Force
}
function Set-OptionalEnv($Name, $Value) {
if ([string]::IsNullOrWhiteSpace($Value)) {
Remove-Item -Path "Env:$Name" -ErrorAction SilentlyContinue
} else {
Set-Item -Path "Env:$Name" -Value $Value
}
}
function Test-ForbiddenReleaseFile($File) {
$inBackendInternal = $File.FullName -match '[\\/]backend[\\/]_internal[\\/]'
return $File.Name -eq ".env" -or
$File.Name -like "knowledge*.db" -or
($File.FullName -match "\\__pycache__\\" -and -not $inBackendInternal) -or
$File.Name -like "*.pfx" -or
$File.Name -like "*.p12" -or
$File.Name -like "*.pvk" -or
$File.Name -like "*.cer" -or
$File.Name -like "*.crt" -or
$File.Name -like "*.key" -or
$File.FullName -match "\\certs\\"
}
Set-Location $Root
$resolvedCertificateFile = $null
if ($CertificateFile) {
if (-not (Test-Path -LiteralPath $CertificateFile)) {
throw "Certificate file was not found: $CertificateFile"
}
$resolvedCertificateFile = (Resolve-Path -LiteralPath $CertificateFile).Path
if ($resolvedCertificateFile.StartsWith($Root.Path)) {
throw "For safety, keep the code signing certificate outside the project folder: $resolvedCertificateFile"
}
}
$envCertificateFile = if ($resolvedCertificateFile) { $resolvedCertificateFile } else { $env:CHATLAB_PFX_FILE }
$envCertificateFile = if ($envCertificateFile) { $envCertificateFile.Trim() } else { "" }
$shouldSign = $Sign -or -not [string]::IsNullOrWhiteSpace($envCertificateFile)
if ($ForceSign -and -not $shouldSign) {
throw "-ForceSign requires -CertificateFile or CHATLAB_PFX_FILE."
}
if ($shouldSign) {
if (-not $envCertificateFile) {
throw "Signing was requested, but no certificate was provided. Use -CertificateFile or CHATLAB_PFX_FILE."
}
if (-not (Test-Path -LiteralPath $envCertificateFile)) {
throw "Certificate file was not found: $envCertificateFile"
}
$envCertificateFile = (Resolve-Path -LiteralPath $envCertificateFile).Path
if ($envCertificateFile.StartsWith($Root.Path)) {
throw "For safety, keep the code signing certificate outside the project folder: $envCertificateFile"
}
Set-OptionalEnv "CHATLAB_PFX_FILE" $envCertificateFile
if ($CertificatePassword) {
Set-OptionalEnv "CHATLAB_PFX_PASSWORD" $CertificatePassword
Set-OptionalEnv "WIN_CSC_KEY_PASSWORD" $CertificatePassword
Set-OptionalEnv "CSC_KEY_PASSWORD" $CertificatePassword
}
if ($PublisherName) { Set-OptionalEnv "CHATLAB_CERT_PUBLISHER_NAME" $PublisherName }
Set-OptionalEnv "CHATLAB_TIMESTAMP_SERVER" $TimestampServer
if ($ForceSign) { Set-OptionalEnv "CHATLAB_FORCE_SIGN" "1" }
Write-Host "Code signing enabled. Certificate: $envCertificateFile"
} else {
Remove-Item -Path "Env:CHATLAB_PFX_FILE" -ErrorAction SilentlyContinue
Remove-Item -Path "Env:CHATLAB_PFX_PASSWORD" -ErrorAction SilentlyContinue
Remove-Item -Path "Env:CHATLAB_CERT_PUBLISHER_NAME" -ErrorAction SilentlyContinue
Remove-Item -Path "Env:CHATLAB_FORCE_SIGN" -ErrorAction SilentlyContinue
Remove-Item -Path "Env:WIN_CSC_KEY_PASSWORD" -ErrorAction SilentlyContinue
Remove-Item -Path "Env:CSC_KEY_PASSWORD" -ErrorAction SilentlyContinue
Write-Host "Code signing disabled. Unsigned installer build is allowed."
}
if (-not $SkipIcon) {
& node (Join-Path $Root "scripts\make-icon.cjs")
}
$logoDest = Join-Path $Electron "build\company-logo.jpg"
$logoSrc = Join-Path $Frontend "public\company-logo.jpg"
if (Test-Path $logoSrc) {
Copy-Item -LiteralPath $logoSrc -Destination $logoDest -Force
Write-Host "company-logo.jpg synced to electron-launcher\build\"
}
if (-not $SkipFrontend) {
$dist = Join-Path $Frontend "dist"
if (Test-Path $dist) {
Remove-Item -LiteralPath (Resolve-Path $dist).Path -Recurse -Force
}
Push-Location $Frontend
& npm.cmd run build
Pop-Location
}
if (-not $SkipBackend) {
Push-Location $Backend
# 默认复用 PyInstaller 的分析缓存build\ 目录),代码未大改时增量打包可从 ~9 分钟降到 1-2 分钟。
# 仅在升级依赖或缓存异常时用 -CleanBackend 做一次全量重建。
$pyArgs = @("-m", "PyInstaller", "ChatLabBackend.spec", "--noconfirm")
if ($CleanBackend) {
$pyArgs += "--clean"
Write-Host "Backend: full clean rebuild (--clean)."
} else {
Write-Host "Backend: incremental build (reusing cache). Use -CleanBackend for a full rebuild."
}
Invoke-Python312 $pyArgs
Pop-Location
}
Reset-Dir $Resources
New-Item -ItemType Directory -Force -Path $Release | Out-Null
Copy-Item -LiteralPath (Join-Path $Root "chatlog.exe") -Destination (Join-Path $Resources "chatlog.exe") -Force
$previousErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = "Continue"
$chatlogVersionOutput = & (Join-Path $Resources "chatlog.exe") version 2>&1
$chatlogVersionExitCode = $LASTEXITCODE
$ErrorActionPreference = $previousErrorActionPreference
if ($chatlogVersionExitCode -ne 0) {
throw "chatlog.exe version check failed:`n$chatlogVersionOutput"
}
Write-Host "chatlog.exe version: $chatlogVersionOutput"
foreach ($voiceKey in $VoiceSmokeKeys) {
if ([string]::IsNullOrWhiteSpace($voiceKey)) { continue }
try {
$response = Invoke-WebRequest -Uri "http://127.0.0.1:5030/voice/$voiceKey" -UseBasicParsing -TimeoutSec 15
if ($response.StatusCode -ge 400 -or $response.RawContentLength -le 0) {
throw "HTTP $($response.StatusCode), length=$($response.RawContentLength)"
}
Write-Host "voice smoke passed: $voiceKey ($($response.RawContentLength) bytes)"
} catch {
throw "voice smoke failed for $voiceKey. Do not ship this installer until chatlog can read WeChat voice media. $($_.Exception.Message)"
}
}
Copy-Dir (Join-Path $Root "lib") (Join-Path $Resources "lib")
Copy-Dir (Join-Path $Frontend "dist") (Join-Path $Resources "frontend")
Copy-Dir (Join-Path $Backend "dist\ChatLabBackend") (Join-Path $Resources "backend")
Copy-Item -LiteralPath (Join-Path $Root "DISCLAIMER.md") -Destination (Join-Path $Resources "DISCLAIMER.md") -Force
Copy-Item -LiteralPath (Join-Path $Root "LICENSE") -Destination (Join-Path $Resources "LICENSE") -Force
$forbidden = Get-ChildItem -LiteralPath $Resources -Recurse -Force |
Where-Object { Test-ForbiddenReleaseFile $_ }
if ($forbidden) {
$names = ($forbidden | Select-Object -ExpandProperty FullName) -join "`n"
throw "Sensitive or cache files found in release resources:`n$names"
}
Get-ChildItem -LiteralPath $Resources -Recurse -File |
Select-Object FullName, Length |
Out-File -Encoding UTF8 (Join-Path $Release "manifest.txt")
if (-not $SkipInstaller) {
Push-Location $Electron
& npm.cmd run build
Pop-Location
Copy-Item -Path (Join-Path $Electron "dist\*.exe") -Destination $Release -Force
Copy-Item -Path (Join-Path $Electron "dist\*.blockmap") -Destination $Release -Force -ErrorAction SilentlyContinue
$releaseForbidden = Get-ChildItem -LiteralPath $Release -Recurse -Force |
Where-Object { Test-ForbiddenReleaseFile $_ }
if ($releaseForbidden) {
$names = ($releaseForbidden | Select-Object -ExpandProperty FullName) -join "`n"
throw "Sensitive certificate, private data, or cache files found in release output:`n$names"
}
$installer = Get-ChildItem -LiteralPath $Release -Filter "ChatLab-Setup-*.exe" -File |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if ($installer) {
$signature = Get-AuthenticodeSignature -FilePath $installer.FullName
if ($shouldSign -or $ForceSign) {
if ($signature.Status -ne "Valid") {
throw "Installer signing verification failed: $($signature.Status) $($signature.StatusMessage)"
}
Write-Host "Installer signature verified: $($signature.SignerCertificate.Subject)"
} else {
Write-Host "Installer is unsigned by design for this build."
}
}
}
Write-Host "Desktop build completed. Output: $Release"