mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
feat: release v0.83.0 with full pending changes
✨ 新功能 - 新增 OpenClaw 服务集成和相关命令支持 - 新增 Windows 启动诊断命令和故障排查文档 - 新增样式库管理面板 (StyleLibraryPanel) - 新增 A2UI 任务卡片组件和完整测试覆盖 - 新增输入栏多个子组件:提示路由弹窗、执行策略选择、模型扩展配置等 - 新增工作台创建入口主页和右侧面板扩展视图 - 新增运行时样式控制栏组件 - 新增样式运行时工具模块和测试 - 新增内容审查面板测试 - 新增多个工作台右侧面板能力:音频任务、视频任务、画外音任务、图片任务等 - 新增 Windows 支持包收集脚本 - 新增开发桥健康检查脚本 🐛 修复 - 修复 TypeScript 编译错误 - 修复崩溃诊断逻辑 - 修复 Tauri mock 核心模块 - 修复开发桥 HTTP 客户端 - 修复 webview API 调用 - 修复 Provider 模型列表组件 - 修复多个 Rust clippy 警告 🔧 优化与重构 - 重构输入栏组件架构,拆分为多个独立 hooks 和子组件 - 重构工作台右侧面板,拆分为多个独立模块和配置文件 - 优化 A2UI 组件渲染器和布局系统 - 优化内容创建器文档画布和工具栏 - 优化记忆层指标计算和测试 - 优化项目提示工具 - 优化主题工作台侧边栏 - 优化样式指南面板 - 优化工作台导航和面板渲染 hooks - 优化确认策略创建工具 - 优化主题模块(novel、video)和共享面板渲染器 - 优化 Agent 事件转换器和会话存储 - 优化数据库 DAO 层 - 优化模型注册服务和技能服务 - 优化日志命令和应用运行器 - 优化开发桥调度器 - 更新 Windows 平台 Tauri 配置 - 更新 CI release workflow 📦 其他 - 新增 Windows 启动问题故障排查文档 - 更新 Playwright E2E 测试文档 - 更新构建和运维文档 - 更新常见问题文档 - 更新 README
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import process from "node:process";
|
||||
|
||||
const DEFAULTS = {
|
||||
url: "http://127.0.0.1:3030/health",
|
||||
timeoutMs: 60000,
|
||||
intervalMs: 1000,
|
||||
};
|
||||
|
||||
function printHelp() {
|
||||
console.log(`
|
||||
ProxyCast DevBridge 健康检查
|
||||
|
||||
用法:
|
||||
node scripts/check-dev-bridge-health.mjs [选项]
|
||||
|
||||
选项:
|
||||
--url <health_url> 健康检查地址,默认 http://127.0.0.1:3030/health
|
||||
--timeout-ms <ms> 超时时间,默认 60000
|
||||
--interval-ms <ms> 轮询间隔,默认 1000
|
||||
-h, --help 显示帮助
|
||||
|
||||
示例:
|
||||
npm run bridge:health
|
||||
npm run bridge:health -- --timeout-ms 120000
|
||||
`);
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const options = { ...DEFAULTS };
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
if ((arg === "--help") || (arg === "-h")) {
|
||||
printHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
if (arg === "--url" && argv[index + 1]) {
|
||||
options.url = argv[index + 1];
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--timeout-ms" && argv[index + 1]) {
|
||||
options.timeoutMs = Number(argv[index + 1]);
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--interval-ms" && argv[index + 1]) {
|
||||
options.intervalMs = Number(argv[index + 1]);
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Number.isFinite(options.timeoutMs) || options.timeoutMs < 1000) {
|
||||
throw new Error("--timeout-ms 必须是 >= 1000 的数字");
|
||||
}
|
||||
if (!Number.isFinite(options.intervalMs) || options.intervalMs < 100) {
|
||||
throw new Error("--interval-ms 必须是 >= 100 的数字");
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function checkOnce(url) {
|
||||
const response = await fetch(url, { method: "GET" });
|
||||
const text = await response.text();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
let payload = null;
|
||||
try {
|
||||
payload = text ? JSON.parse(text) : null;
|
||||
} catch {
|
||||
payload = null;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (typeof fetch !== "function") {
|
||||
throw new Error("当前 Node 运行时不支持 fetch,请使用 Node 18+");
|
||||
}
|
||||
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
const startedAt = Date.now();
|
||||
let lastError = null;
|
||||
|
||||
console.log(`[bridge:health] 开始检查: ${options.url}`);
|
||||
|
||||
while (Date.now() - startedAt < options.timeoutMs) {
|
||||
try {
|
||||
const payload = await checkOnce(options.url);
|
||||
const elapsed = Date.now() - startedAt;
|
||||
const status = payload && typeof payload === "object" ? payload.status : undefined;
|
||||
console.log(
|
||||
`[bridge:health] 就绪: ${options.url} (${elapsed}ms)${status ? ` status=${status}` : ""}`
|
||||
);
|
||||
return;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
await sleep(options.intervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
const detail = lastError instanceof Error ? lastError.message : String(lastError || "unknown error");
|
||||
throw new Error(
|
||||
`[bridge:health] 超时未就绪: ${options.url}。请先启动 npm run tauri:dev:headless,并确认 DevBridge 已监听 3030。最后错误: ${detail}`
|
||||
);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,194 @@
|
||||
param(
|
||||
[string]$OutputRoot = "",
|
||||
[switch]$KeepExpanded
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Write-Step {
|
||||
param([string]$Message)
|
||||
Write-Host "[ProxyCast Support] $Message"
|
||||
}
|
||||
|
||||
function Ensure-Directory {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
New-Item -ItemType Directory -Path $Path -Force | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
function Get-WebView2Version {
|
||||
$keys = @(
|
||||
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}",
|
||||
"HKLM:\SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}",
|
||||
"HKCU:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}",
|
||||
"HKCU:\SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"
|
||||
)
|
||||
|
||||
foreach ($key in $keys) {
|
||||
try {
|
||||
$value = (Get-ItemProperty -LiteralPath $key -Name "pv" -ErrorAction Stop).pv
|
||||
if ($value -and $value -ne "0.0.0.0") {
|
||||
return [string]$value
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-PathMetadata {
|
||||
param([string]$TargetPath)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $TargetPath)) {
|
||||
return [pscustomobject]@{
|
||||
path = $TargetPath
|
||||
exists = $false
|
||||
type = $null
|
||||
last_write_time = $null
|
||||
size_bytes = $null
|
||||
}
|
||||
}
|
||||
|
||||
$item = Get-Item -LiteralPath $TargetPath -Force
|
||||
$size = $null
|
||||
if ($item.PSIsContainer) {
|
||||
try {
|
||||
$size = (Get-ChildItem -LiteralPath $TargetPath -Recurse -Force -File -ErrorAction Stop |
|
||||
Measure-Object -Property Length -Sum).Sum
|
||||
} catch {
|
||||
$size = $null
|
||||
}
|
||||
} else {
|
||||
$size = $item.Length
|
||||
}
|
||||
|
||||
return [pscustomobject]@{
|
||||
path = $TargetPath
|
||||
exists = $true
|
||||
type = if ($item.PSIsContainer) { "directory" } else { "file" }
|
||||
last_write_time = $item.LastWriteTime.ToString("o")
|
||||
size_bytes = $size
|
||||
}
|
||||
}
|
||||
|
||||
function Export-PathListing {
|
||||
param(
|
||||
[string]$SourcePath,
|
||||
[string]$OutputFile
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $SourcePath)) {
|
||||
"路径不存在: $SourcePath" | Set-Content -LiteralPath $OutputFile -Encoding UTF8
|
||||
return
|
||||
}
|
||||
|
||||
Get-ChildItem -LiteralPath $SourcePath -Recurse -Force -ErrorAction SilentlyContinue |
|
||||
Select-Object FullName, PSIsContainer, Length, LastWriteTime |
|
||||
ConvertTo-Json -Depth 4 |
|
||||
Set-Content -LiteralPath $OutputFile -Encoding UTF8
|
||||
}
|
||||
|
||||
function Copy-DirectoryIfExists {
|
||||
param(
|
||||
[string]$SourcePath,
|
||||
[string]$DestinationPath
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $SourcePath)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
Ensure-Directory -Path (Split-Path -Parent $DestinationPath)
|
||||
Copy-Item -LiteralPath $SourcePath -Destination $DestinationPath -Recurse -Force
|
||||
return $true
|
||||
}
|
||||
|
||||
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$desktop = [Environment]::GetFolderPath("Desktop")
|
||||
if ([string]::IsNullOrWhiteSpace($OutputRoot)) {
|
||||
$OutputRoot = if ([string]::IsNullOrWhiteSpace($desktop)) { $env:TEMP } else { $desktop }
|
||||
}
|
||||
|
||||
$bundleName = "ProxyCast-Support-$timestamp"
|
||||
$bundleDir = Join-Path $OutputRoot $bundleName
|
||||
$zipPath = "$bundleDir.zip"
|
||||
|
||||
$appDataDir = Join-Path $env:APPDATA "proxycast"
|
||||
$legacyDir = Join-Path $env:USERPROFILE ".proxycast"
|
||||
$configPath = Join-Path $appDataDir "config.yaml"
|
||||
$dbPath = Join-Path $legacyDir "proxycast.db"
|
||||
$logsDir = Join-Path $legacyDir "logs"
|
||||
$requestLogsDir = Join-Path $legacyDir "request_logs"
|
||||
|
||||
Write-Step "输出目录: $bundleDir"
|
||||
Ensure-Directory -Path $bundleDir
|
||||
Ensure-Directory -Path (Join-Path $bundleDir "logs")
|
||||
Ensure-Directory -Path (Join-Path $bundleDir "meta")
|
||||
|
||||
$systemInfo = [ordered]@{
|
||||
collected_at = (Get-Date).ToString("o")
|
||||
computer_name = $env:COMPUTERNAME
|
||||
username = $env:USERNAME
|
||||
windows_version = [System.Environment]::OSVersion.VersionString
|
||||
powershell_version = $PSVersionTable.PSVersion.ToString()
|
||||
webview2_version = Get-WebView2Version
|
||||
appdata_dir = $appDataDir
|
||||
legacy_proxycast_dir = $legacyDir
|
||||
config_path = $configPath
|
||||
database_path = $dbPath
|
||||
shell_paths = @{
|
||||
powershell = (Get-Command powershell.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -ErrorAction SilentlyContinue)
|
||||
pwsh = (Get-Command pwsh.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -ErrorAction SilentlyContinue)
|
||||
cmd = $env:ComSpec
|
||||
}
|
||||
path_checks = @(
|
||||
Get-PathMetadata -TargetPath $appDataDir
|
||||
Get-PathMetadata -TargetPath $legacyDir
|
||||
Get-PathMetadata -TargetPath $configPath
|
||||
Get-PathMetadata -TargetPath $dbPath
|
||||
Get-PathMetadata -TargetPath $logsDir
|
||||
Get-PathMetadata -TargetPath $requestLogsDir
|
||||
)
|
||||
}
|
||||
|
||||
$systemInfo | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $bundleDir "meta/system-info.json") -Encoding UTF8
|
||||
|
||||
Export-PathListing -SourcePath $appDataDir -OutputFile (Join-Path $bundleDir "meta/appdata-listing.json")
|
||||
Export-PathListing -SourcePath $legacyDir -OutputFile (Join-Path $bundleDir "meta/legacy-listing.json")
|
||||
|
||||
$copiedLogs = Copy-DirectoryIfExists -SourcePath $logsDir -DestinationPath (Join-Path $bundleDir "logs/logs")
|
||||
$copiedRequestLogs = Copy-DirectoryIfExists -SourcePath $requestLogsDir -DestinationPath (Join-Path $bundleDir "logs/request_logs")
|
||||
|
||||
@(
|
||||
"ProxyCast 支持包已生成。",
|
||||
"",
|
||||
"已收集内容:",
|
||||
"- system-info.json(系统与路径元数据)",
|
||||
"- appdata-listing.json / legacy-listing.json(目录结构摘要)",
|
||||
"- logs/(如果存在)",
|
||||
"- request_logs/(如果存在)",
|
||||
"",
|
||||
"默认未收集内容:",
|
||||
"- config.yaml 正文(避免泄露 API Key / 凭证)",
|
||||
"- proxycast.db 正文(避免泄露会话与敏感数据)",
|
||||
"- credentials/ 目录内容",
|
||||
"",
|
||||
"是否复制 logs: $copiedLogs",
|
||||
"是否复制 request_logs: $copiedRequestLogs",
|
||||
"",
|
||||
"请将生成的 zip 文件发给支持人员。"
|
||||
) | Set-Content -LiteralPath (Join-Path $bundleDir "README.txt") -Encoding UTF8
|
||||
|
||||
if (Test-Path -LiteralPath $zipPath) {
|
||||
Remove-Item -LiteralPath $zipPath -Force
|
||||
}
|
||||
|
||||
Compress-Archive -LiteralPath $bundleDir -DestinationPath $zipPath -Force
|
||||
|
||||
if (-not $KeepExpanded) {
|
||||
Remove-Item -LiteralPath $bundleDir -Recurse -Force
|
||||
}
|
||||
|
||||
Write-Step "支持包已生成: $zipPath"
|
||||
Reference in New Issue
Block a user