From ad02c2a8a66a1a39749df0b40588afc6a535210e Mon Sep 17 00:00:00 2001
From: lzt404 <2596933790@qq.com>
Date: Sun, 19 Jul 2026 02:47:57 +0800
Subject: [PATCH] refactor(app): rename the desktop app and refine the
interface
---
.github/workflows/release.yml | 28 +++++++++----
build.ps1 | 13 +++++-
build.sh | 2 +-
index.html | 2 +-
run.ps1 | 2 +-
run.sh | 2 +-
scripts/prepare-portable.mjs | 48 +++++++---------------
src-tauri/capabilities/default.json | 2 +-
src-tauri/src/main.rs | 4 +-
src-tauri/tauri.conf.json | 4 +-
src/App.tsx | 43 ++++++++++++++++++--
src/AppErrorBoundary.tsx | 2 +-
src/pages/AgentsPage.tsx | 61 +++++++++++++++++++++++++++-
src/pages/Kernel.tsx | 18 ---------
src/styles.css | 62 +++++++++++++++++++++++------
15 files changed, 206 insertions(+), 87 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8d78e26..d61eecc 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -112,23 +112,37 @@ jobs:
run: >-
node scripts/prepare-portable.mjs
--binary "src-tauri/target/release/cpa-gui${{ matrix.os == 'windows' && '.exe' || '' }}"
- --output "release/cpa-gui-${{ matrix.os }}-${{ matrix.arch }}"
+ --output "release/Easy_CLIProxyAPI-${{ matrix.os }}-${{ matrix.arch }}"
--os "${{ matrix.os }}"
--arch "${{ matrix.arch }}"
--download true
- --clean-core true
shell: bash
+ - name: Verify archive-only core payload
+ shell: bash
+ env:
+ CORE_DIR: release/Easy_CLIProxyAPI-${{ matrix.os }}-${{ matrix.arch }}/cpa-core
+ run: |
+ node - <<'NODE'
+ const { readdirSync } = require('node:fs');
+ const entries = readdirSync(process.env.CORE_DIR, { withFileTypes: true });
+ const valid = entries.length === 1
+ && entries[0].isFile()
+ && /\.(zip|tar\.gz)$/.test(entries[0].name);
+ if (!valid) {
+ throw new Error(`cpa-core must contain exactly one archive; found: ${entries.map((entry) => entry.name).join(', ')}`);
+ }
+ NODE
- name: Archive Unix release
if: matrix.os != 'windows'
- run: tar -czf "cpa-gui-${{ matrix.os }}-${{ matrix.arch }}.tar.gz" -C release "cpa-gui-${{ matrix.os }}-${{ matrix.arch }}"
+ run: tar -czf "Easy_CLIProxyAPI-${{ matrix.os }}-${{ matrix.arch }}.tar.gz" -C release "Easy_CLIProxyAPI-${{ matrix.os }}-${{ matrix.arch }}"
- name: Archive Windows release
if: matrix.os == 'windows'
shell: pwsh
- run: Compress-Archive -Path "release/cpa-gui-${{ matrix.os }}-${{ matrix.arch }}" -DestinationPath "cpa-gui-${{ matrix.os }}-${{ matrix.arch }}.zip"
+ run: Compress-Archive -Path "release/Easy_CLIProxyAPI-${{ matrix.os }}-${{ matrix.arch }}" -DestinationPath "Easy_CLIProxyAPI-${{ matrix.os }}-${{ matrix.arch }}.zip"
- uses: actions/upload-artifact@v7
with:
- name: cpa-gui-${{ matrix.os }}-${{ matrix.arch }}
- path: cpa-gui-${{ matrix.os }}-${{ matrix.arch }}.${{ matrix.suffix }}
+ name: Easy_CLIProxyAPI-${{ matrix.os }}-${{ matrix.arch }}
+ path: Easy_CLIProxyAPI-${{ matrix.os }}-${{ matrix.arch }}.${{ matrix.suffix }}
if-no-files-found: error
retention-days: 14
@@ -159,7 +173,7 @@ jobs:
fi
prerelease="$MANUAL_PRERELEASE"
- release_args=(--title "CPA GUI $RELEASE_TAG" --generate-notes)
+ release_args=(--title "Easy_CLIProxyAPI $RELEASE_TAG" --generate-notes)
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
release_args+=(--verify-tag)
if [[ "$RELEASE_TAG" == *-* ]]; then
diff --git a/build.ps1 b/build.ps1
index a8c777c..7f7226d 100644
--- a/build.ps1
+++ b/build.ps1
@@ -9,7 +9,7 @@ $ErrorActionPreference = 'Stop'
$RootDir = $PSScriptRoot
$AppBin = Join-Path $RootDir 'src-tauri\target\release\cpa-gui.exe'
$BinDir = Join-Path $RootDir 'bin-work'
-$BinOut = Join-Path $BinDir 'cpa-gui.exe'
+$BinOut = Join-Path $BinDir 'Easy_CLIProxyAPI.exe'
$PreparePortable = Join-Path $RootDir 'scripts\prepare-portable.mjs'
Set-Location -LiteralPath $RootDir
@@ -67,5 +67,16 @@ if (-not (Test-Path -LiteralPath $BinOut -PathType Leaf)) {
throw "Portable preparation finished, but executable not found: $BinOut"
}
+$CoreOut = Join-Path $BinDir 'cpa-core'
+$CoreEntries = @(Get-ChildItem -LiteralPath $CoreOut -Force)
+if (
+ $CoreEntries.Count -ne 1 -or
+ $CoreEntries[0].PSIsContainer -or
+ $CoreEntries[0].Name -notmatch '^CLIProxyAPI_.+_windows_.+\.zip$'
+) {
+ throw "Portable core output must contain exactly one Windows core archive: $CoreOut"
+}
+
Write-Host "Built: $AppBin"
Write-Host "Copied: $BinOut"
+Write-Host "Bundled core archive: $($CoreEntries[0].FullName)"
diff --git a/build.sh b/build.sh
index 5031145..3d8b668 100755
--- a/build.sh
+++ b/build.sh
@@ -7,7 +7,7 @@ cd "$ROOT_DIR"
BUILD_JOBS="${CARGO_BUILD_JOBS:-20}"
APP_BIN="$ROOT_DIR/src-tauri/target/release/cpa-gui"
BIN_DIR="$ROOT_DIR/bin-work"
-BIN_OUT="$BIN_DIR/cpa-gui"
+BIN_OUT="$BIN_DIR/Easy_CLIProxyAPI"
if ! command -v bun >/dev/null 2>&1; then
echo "bun is not installed or not in PATH."
diff --git a/index.html b/index.html
index 38b3986..de6f754 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
- CPA GUI
+ Easy_CLIProxyAPI
diff --git a/run.ps1 b/run.ps1
index 95e60e4..0f2a362 100644
--- a/run.ps1
+++ b/run.ps1
@@ -6,7 +6,7 @@ $ErrorActionPreference = 'Stop'
$RootDir = $PSScriptRoot
$BinDir = Join-Path $RootDir 'bin-work'
-$AppBin = Join-Path $BinDir 'cpa-gui.exe'
+$AppBin = Join-Path $BinDir 'Easy_CLIProxyAPI.exe'
if (-not (Test-Path -LiteralPath $AppBin -PathType Leaf)) {
Write-Host "Executable not found: $AppBin"
diff --git a/run.sh b/run.sh
index f59bc66..02fe33e 100755
--- a/run.sh
+++ b/run.sh
@@ -2,7 +2,7 @@
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-APP_BIN="$ROOT_DIR/bin-work/cpa-gui"
+APP_BIN="$ROOT_DIR/bin-work/Easy_CLIProxyAPI"
if [ ! -x "$APP_BIN" ]; then
echo "Executable not found: $APP_BIN"
diff --git a/scripts/prepare-portable.mjs b/scripts/prepare-portable.mjs
index 661b66f..1fcc2d5 100644
--- a/scripts/prepare-portable.mjs
+++ b/scripts/prepare-portable.mjs
@@ -1,8 +1,7 @@
import { createHash } from 'node:crypto';
import { existsSync } from 'node:fs';
-import { chmod, copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
+import { chmod, copyFile, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
import { basename, join, resolve } from 'node:path';
-import { spawnSync } from 'node:child_process';
const args = new Map();
for (let index = 2; index < process.argv.length; index += 2) {
@@ -15,7 +14,6 @@ const binary = resolve(args.get('--binary') ?? join(root, 'src-tauri', 'target',
const targetOS = args.get('--os') ?? ({ linux: 'linux', darwin: 'darwin', win32: 'windows' })[process.platform];
const targetArch = args.get('--arch') ?? ({ x64: 'amd64', arm64: 'aarch64' })[process.arch];
const shouldDownload = args.get('--download') === 'true';
-const cleanCore = args.get('--clean-core') === 'true';
if (!targetOS || !targetArch) throw new Error(`Unsupported target: ${process.platform}/${process.arch}`);
if (!existsSync(binary)) throw new Error(`GUI binary not found: ${binary}`);
@@ -32,13 +30,7 @@ const checksumsPath = join(sourceDir, 'checksums.txt');
await mkdir(sourceDir, { recursive: true });
if (!existsSync(sourceArchive)) {
if (!shouldDownload) {
- console.warn(`Built-in core archive not found; portable output keeps the existing core: ${sourceArchive}`);
- await mkdir(output, { recursive: true });
- const outputBinary = join(output, targetOS === 'windows' ? 'cpa-gui.exe' : 'cpa-gui');
- await copyFile(binary, outputBinary);
- if (targetOS !== 'windows') await chmod(outputBinary, 0o755);
- await copyFile(join(root, 'core-version.txt'), join(output, 'core-version.txt'));
- process.exit(0);
+ throw new Error(`Built-in core archive not found: ${sourceArchive}`);
}
const tag = `v${version}`;
const releaseBase = `https://github.com/router-for-me/CLIProxyAPI/releases/download/${tag}`;
@@ -61,35 +53,25 @@ if (existsSync(checksumsPath)) {
}
await mkdir(output, { recursive: true });
-const outputBinary = join(output, targetOS === 'windows' ? 'cpa-gui.exe' : 'cpa-gui');
+const outputBinary = join(output, targetOS === 'windows' ? 'Easy_CLIProxyAPI.exe' : 'Easy_CLIProxyAPI');
+const legacyOutputBinary = join(output, targetOS === 'windows' ? 'cpa-gui.exe' : 'cpa-gui');
+await rm(legacyOutputBinary, { force: true });
await copyFile(binary, outputBinary);
if (targetOS !== 'windows') await chmod(outputBinary, 0o755);
await copyFile(join(root, 'core-version.txt'), join(output, 'core-version.txt'));
const coreOutput = join(output, 'cpa-core');
-if (cleanCore) await rm(coreOutput, { recursive: true, force: true });
+await rm(coreOutput, { recursive: true, force: true });
await mkdir(coreOutput, { recursive: true });
-let extraction;
-if (extension === 'zip') {
- if (process.platform === 'win32') {
- const quotePowerShell = (value) => value.replaceAll("'", "''");
- const command = `Expand-Archive -LiteralPath '${quotePowerShell(sourceArchive)}' -DestinationPath '${quotePowerShell(coreOutput)}' -Force`;
- extraction = spawnSync('powershell.exe', ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', command], { stdio: 'inherit' });
- } else {
- extraction = spawnSync('unzip', ['-q', sourceArchive, '-d', coreOutput], { stdio: 'inherit' });
- }
-} else {
- extraction = spawnSync('tar', ['-xf', sourceArchive, '-C', coreOutput], { stdio: 'inherit' });
-}
-if (extraction.error) throw new Error(`Failed to start archive extractor for ${assetName}: ${extraction.error.message}`);
-if (extraction.status !== 0) throw new Error(`Failed to extract ${assetName} (exit code ${extraction.status})`);
await copyFile(sourceArchive, join(coreOutput, assetName));
-if (existsSync(checksumsPath)) await copyFile(checksumsPath, join(coreOutput, 'checksums.txt'));
-await writeFile(join(coreOutput, 'cpa-gui-meta.json'), `${JSON.stringify({
- version: `v${version}`,
- assetName,
- installedAtUnix: Math.floor(Date.now() / 1000),
-}, null, 2)}\n`);
+const coreEntries = await readdir(coreOutput, { withFileTypes: true });
+if (
+ coreEntries.length !== 1
+ || !coreEntries[0].isFile()
+ || coreEntries[0].name !== assetName
+) {
+ throw new Error(`Core output must contain only ${assetName}`);
+}
console.log(`Prepared portable directory: ${output}`);
-console.log(`Bundled core: ${basename(sourceArchive)}`);
+console.log(`Bundled core: ${basename(sourceArchive)} (archive only)`);
diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json
index 467363e..92f9b26 100644
--- a/src-tauri/capabilities/default.json
+++ b/src-tauri/capabilities/default.json
@@ -1,7 +1,7 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
- "description": "Default permissions for the main CPA GUI window.",
+ "description": "Default permissions for the main Easy_CLIProxyAPI window.",
"windows": ["main"],
"permissions": ["core:event:allow-listen", "core:event:allow-unlisten"]
}
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 30b85b8..c2c6db0 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -808,7 +808,7 @@ struct GithubAsset {
#[tauri::command]
fn health_check() -> &'static str {
- "CPA GUI Rust backend is ready"
+ "Easy_CLIProxyAPI Rust backend is ready"
}
#[tauri::command]
@@ -5382,7 +5382,7 @@ fn configure_child_lifetime(command: &mut Command) {
if libc::getppid() != parent_process_id {
return Err(io::Error::other(
- "CPA GUI exited before the core process started",
+ "Easy_CLIProxyAPI exited before the core process started",
));
}
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 059bcb3..962f401 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,6 +1,6 @@
{
"$schema": "https://schema.tauri.app/config/2",
- "productName": "CPA GUI",
+ "productName": "Easy_CLIProxyAPI",
"version": "0.1.0-pre.4",
"identifier": "com.cpa.gui",
"build": {
@@ -13,7 +13,7 @@
"windows": [
{
"label": "main",
- "title": "CPA GUI",
+ "title": "Easy_CLIProxyAPI",
"width": 1200,
"height": 800,
"minWidth": 900,
diff --git a/src/App.tsx b/src/App.tsx
index 5e61a52..4b7fcb2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,5 +1,18 @@
import { useEffect, useState } from 'react';
-import { Bot, FileKey, Gauge, GitFork, History, LogIn, Network, ServerCog, Settings } from 'lucide-react';
+import { invoke } from '@tauri-apps/api/core';
+import {
+ Bot,
+ ExternalLink,
+ FileKey,
+ Gauge,
+ GitFork,
+ History,
+ LogIn,
+ MessageCircle,
+ Network,
+ ServerCog,
+ Settings,
+} from 'lucide-react';
import appLogo from './assets/logo.jpg';
import { CoreRuntimeProvider, useCoreRuntime } from './coreRuntime';
import { ConfigPanelPage } from './pages/ConfigPanel';
@@ -12,6 +25,8 @@ import { AgentsPage } from './pages/AgentsPage';
import { ThinkingAliasesPage } from './pages/ThinkingAliasesPage';
import { UsageRecordsPage } from './pages/UsageRecordsPage';
+const CONTACT_URL = 'https://qm.qq.com/q/3queDaIG';
+
const pages = [
{
id: 'kernel',
@@ -99,6 +114,14 @@ function AppContent() {
setActive(pageId);
};
+ const openContact = async () => {
+ try {
+ await invoke('open_external_url', { url: CONTACT_URL });
+ } catch (error) {
+ console.error('打开联系我们链接失败', error);
+ }
+ };
+
return (