fix release assets and refresh lime branding

This commit is contained in:
coso
2026-03-17 09:20:08 +08:00
parent d15d9aa65d
commit 998fe555e5
62 changed files with 371 additions and 301 deletions
+27 -27
View File
@@ -184,31 +184,6 @@ jobs:
# 默认不启用 voice feature(包含 whisper-rs,编译很慢)
args: --target ${{ matrix.target }}
- name: Build Tauri app (Windows online)
if: matrix.platform == 'windows-2022'
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 禁用 LTO 加速编译(正式发布可改为 thin)
CARGO_PROFILE_RELEASE_LTO: "off"
# 增加并行编译单元
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 32
CARGO_INCREMENTAL: 0
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: sccache
with:
tauriScript: npx tauri
projectPath: src-tauri
tagName: ${{ github.event.inputs.version || github.ref_name }}
releaseName: "Lime ${{ github.event.inputs.version || github.ref_name }}"
releaseBody: ${{ env.RELEASE_BODY }}
releaseDraft: false
prerelease: false
# 默认推荐在线小包,安装时按需拉取 WebView2
releaseAssetNamePattern: "[name]_[version]_[arch]-online[setup][ext]"
# 默认不启用 voice feature(包含 whisper-rs,编译很慢)
args: --target ${{ matrix.target }} --config tauri.windows.online.conf.json
- name: Build Tauri app (Windows offline)
if: matrix.platform == 'windows-2022'
uses: tauri-apps/tauri-action@v0
@@ -229,11 +204,36 @@ jobs:
releaseBody: ${{ env.RELEASE_BODY }}
releaseDraft: false
prerelease: false
# 离线大包内置 WebView2 离线安装器,适用于无网环境
releaseAssetNamePattern: "[name]_[version]_[arch]-offline[setup][ext]"
# tauri-action@v0 使用 assetNamePattern,离线包优先上传并保留独立名称
assetNamePattern: "[name]_[version]_[arch]-offline[setup][ext]"
# 默认不启用 voice feature(包含 whisper-rs,编译很慢)
args: --target ${{ matrix.target }} --config tauri.windows.conf.json
- name: Build Tauri app (Windows online)
if: matrix.platform == 'windows-2022'
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 禁用 LTO 加速编译(正式发布可改为 thin)
CARGO_PROFILE_RELEASE_LTO: "off"
# 增加并行编译单元
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: 32
CARGO_INCREMENTAL: 0
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: sccache
with:
tauriScript: npx tauri
projectPath: src-tauri
tagName: ${{ github.event.inputs.version || github.ref_name }}
releaseName: "Lime ${{ github.event.inputs.version || github.ref_name }}"
releaseBody: ${{ env.RELEASE_BODY }}
releaseDraft: false
prerelease: false
# tauri-action@v0 使用 assetNamePattern,在线包作为体积更小的备选
assetNamePattern: "[name]_[version]_[arch]-online[setup][ext]"
# 默认不启用 voice feature(包含 whisper-rs,编译很慢)
args: --target ${{ matrix.target }} --config tauri.windows.online.conf.json
- name: Build Tauri app (macOS with notarization, attempt 1)
id: build_macos_primary
if: matrix.platform == 'macos-latest'
+2 -2
View File
@@ -126,8 +126,8 @@ brew install --cask lime
从 [Releases](https://github.com/aiclientproxy/lime/releases) 下载对应平台安装包。
- Windows 用户默认优先下载 `Lime_*_x64-online-setup.exe`(NSIS 在线安装器,体积更小)
- 如果处于离线、内网或受限网络环境,请改用 `Lime_*_x64-offline-setup.exe`
- Windows 用户优先下载 `Lime_*_x64-offline-setup.exe`(NSIS 离线安装器,内置 WebView2,安装更完整)
- 如果只想下载更小的安装器,且当前网络可稳定访问微软下载源,再选择 `Lime_*_x64-online-setup.exe`
- 如被 SmartScreen 拦截,属于未签名或签名信誉不足的 Windows 常见提示,不代表安装包必然损坏
---
+2 -2
View File
@@ -22,8 +22,8 @@
### 📦 Windows 下载说明
- `Lime_*_x64-online-setup.exe`:默认推荐,体积更小,安装时按需下载 WebView2
- `Lime_*_x64-offline-setup.exe`:适用于离线、内网或受限网络环境
- `Lime_*_x64-offline-setup.exe`:推荐优先使用,内置 WebView2 离线安装器,安装更完整
- `Lime_*_x64-online-setup.exe`:体积更小,适合网络稳定且可访问微软下载源的环境
- 如果在线安装失败,请改用离线安装包
---
+204
View File
@@ -0,0 +1,204 @@
# OEM Logo 快速替换指南
本文用于未来 OEM / 代理商定制时,快速把 Lime 的品牌图替换为代理商品牌图,并尽量让 AI 一次完成主要资产更新。
目标不是讲设计理论,而是提供一条可执行、可复用、低沟通成本的流程。
## 适用范围
适用于以下两类需求:
1. 只替换图形 Logo,不改产品名、包名、文案。
2. 同时替换 Logo 与对外品牌名,例如启动页 slogan、README 下载文案、发布说明。
## 当前仓库里的关键 Logo 落点
执行 OEM 替换时,优先关注这些位置:
- 通用前端 Logo:`public/logo.png`
- 启动页专用 Logo:`public/logo-splash.png`
- 桌面端打包图标:`src-tauri/icons/`
- 浏览器页签图标:`index.html` 中引用的 `/logo.png`
- 直接引用前端 Logo 的页面:
- `src/components/AppSidebar.tsx`
- `src/components/SplashScreen.tsx`
- `src/components/onboarding/steps/WelcomeStep.tsx`
- `src/components/settings-v2/system/about/index.tsx`
- `src/components/agent/chat/components/MessageList.tsx`
注意:
- `src-tauri/icons/tray/*` 是托盘状态图,不一定适合直接换成 OEM Logo。
- 如果只是换品牌图,通常不要动 tray 状态图。
## 推荐输入物
给 AI 或执行者提供以下输入,成功率最高:
1. 一张高分辨率透明底主 Logo,建议 `2048x2048` 或更高。
2. 一个简短的品牌定位描述。
例如:`偏企业、稳重、科技蓝绿` 或 `轻快、创意、果汁感`
3. 是否需要同步替换 slogan。
4. 是否需要同步替换产品名。
推荐把 OEM 原始图先放到仓库根目录,命名为:
```text
oem-logo.png
```
如果还有启动页专用版本,也可以额外提供:
```text
oem-logo-splash.png
```
## 标准替换流程
### 方案 A:只替换 Logo
1. 把 OEM Logo 放到仓库根目录,例如 `oem-logo.png`
2. 让 AI 先扫描引用点和打包图标目录
3. 更新 `public/logo.png`
4. 单独生成 `public/logo-splash.png`
5. 同步生成 `src-tauri/icons/` 下的桌面端图标资产
6. 保持 `src-tauri/icons/tray/*` 不变
7. 跑 `npm run lint`
### 方案 B:Logo + 品牌名一起替换
在方案 A 基础上,再额外处理:
1. 启动页 slogan
2. README 下载说明
3. `RELEASE_NOTES.md`
4. 如果是完整品牌切换,再继续检查:
- `package.json`
- `src-tauri/tauri.conf.json`
- 深链 scheme
- 安装包命名
- GitHub Release 文案
## 推荐给 AI 的单次提示词
下面这段可以直接复制给 AI,通常一轮就能完成大部分替换:
```text
请帮我把当前仓库的品牌图替换为 OEM Logo。
输入图片路径:
- 主 Logo:./oem-logo.png
要求:
1. 先扫描仓库里当前 logo 的引用点和图标资产,不要盲改。
2. 替换通用前端 logo:public/logo.png
3. 为启动页单独生成一张更适合大尺寸展示的专用图:public/logo-splash.png
4. 同步更新 src-tauri/icons/ 下的桌面端打包图标,包括 png / ico / icns / ios / android 图标集
5. 不要替换 src-tauri/icons/tray/*,这些是状态图标
6. 如果启动页当前复用通用 logo,请改成单独引用 logo-splash.png
7. 如果启动页视觉太小,请顺手调整 SplashScreen 的布局和尺寸,让 logo 更显眼
8. 除非我明确要求,不要改产品名、包名、identifier
9. 修改完成后执行 npm run lint
10. 最后告诉我改了哪些文件、哪些图标已同步、哪些没有动
实现约束:
- 修改前先读文件
- 不要改无关业务逻辑
- 保持 macOS / Windows / Android / iOS 图标资产一致
```
## 如果还要一起改 slogan
把上面的提示词补充为:
```text
另外把启动页改成只保留 logo、slogan、进度动画。
新的 slogan 是:
“青柠一下,灵感即来。”
```
## 如果还要一起改品牌名
把上面的提示词再补充为:
```text
这是一次 OEM 品牌替换,不只是换图。
请额外扫描并更新对外展示名称、README、RELEASE_NOTES、GitHub Release 文案。
但先不要擅自修改历史 tag。
```
## AI 执行后的核对清单
完成后至少检查以下内容:
### 视觉核对
- 浏览器页签是否已经换成 OEM Logo
- 侧边栏 / About / 欢迎页是否已经换图
- 启动页是否明显使用了单独的大图,而不是缩小后的通用 logo
- 启动页 Logo 是否居中,是否存在透明留白过大问题
### 打包核对
- `src-tauri/icons/icon.png`
- `src-tauri/icons/icon.ico`
- `src-tauri/icons/icon.icns`
- `src-tauri/icons/ios/*`
- `src-tauri/icons/android/*`
### 命令核对
```bash
npm run lint
```
如果这次同时改了 Tauri 或 Rust 相关资源生成流程,补跑:
```bash
cargo check --manifest-path "src-tauri/Cargo.toml" --offline
```
## 常见问题
### 1. 启动页 logo 看起来还是小
通常不是图片太小,而是:
- 启动页复用了 `public/logo.png`
- Logo 外面套了过厚的卡片、边框或内边距
- 透明边界太大
解决方式:
1. 单独生成 `public/logo-splash.png`
2. 把启动页改成专用引用
3. 去掉厚重容器,只保留大 logo + slogan + 进度动画
### 2. Windows Release 里只看到一个 setup.exe
这通常不是图标问题,而是 CI 的 Windows 在线包 / 离线包命名或上传逻辑有冲突。
重点检查:
- `.github/workflows/release.yml`
- `assetNamePattern`
- Windows offline / online 构建顺序
### 3. tray 图标看起来不适合直接 OEM
这是正常的。
tray 图标承担的是状态表达,不是品牌海报位。除非 OEM 要求非常明确,否则建议保持原状态图逻辑不变。
## 建议的资产策略
未来长期维护时,建议默认拆成两类资源:
1. 通用品牌图
- `public/logo.png`
- `src-tauri/icons/*`
2. 场景专用品牌图
- `public/logo-splash.png`
这样后续即使继续 OEM,也能避免“一个 logo 到处复用,结果某个场景总显得小或不协调”。
Binary file not shown.

After

Width:  |  Height:  |  Size: 881 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

After

Width:  |  Height:  |  Size: 257 KiB

+6 -6
View File
@@ -227,9 +227,9 @@ mod tests {
}
fn clear_tool_calling_envs() {
std::env::remove_var(ENV_TOOLCALL_V2_ENABLED);
std::env::remove_var(ENV_TOOLCALL_V2_DYNAMIC_FILTERING);
std::env::remove_var(ENV_TOOLCALL_V2_NATIVE_INPUT_EXAMPLES);
std::env::remove_var(ENV_TOOLCALL_V2_ENABLED[0]);
std::env::remove_var(ENV_TOOLCALL_V2_DYNAMIC_FILTERING[0]);
std::env::remove_var(ENV_TOOLCALL_V2_NATIVE_INPUT_EXAMPLES[0]);
}
#[test]
@@ -258,9 +258,9 @@ mod tests {
native_input_examples: false,
});
std::env::set_var(ENV_TOOLCALL_V2_ENABLED, "true");
std::env::set_var(ENV_TOOLCALL_V2_DYNAMIC_FILTERING, "1");
std::env::set_var(ENV_TOOLCALL_V2_NATIVE_INPUT_EXAMPLES, "on");
std::env::set_var(ENV_TOOLCALL_V2_ENABLED[0], "true");
std::env::set_var(ENV_TOOLCALL_V2_DYNAMIC_FILTERING[0], "1");
std::env::set_var(ENV_TOOLCALL_V2_NATIVE_INPUT_EXAMPLES[0], "on");
assert!(tool_calling_v2_enabled());
assert!(tool_calling_dynamic_filtering_enabled());
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 KiB

After

Width:  |  Height:  |  Size: 843 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 36 KiB

+3 -2
View File
@@ -2188,11 +2188,12 @@ impl WorkspaceSandboxedBashTool {
config
}
#[cfg(not(target_os = "windows"))]
fn quote_shell(value: &str) -> String {
format!("'{}'", value.replace('\'', "'\"'\"'"))
}
fn build_shell_command(&self, command: &str, context: &ToolContext) -> (String, Vec<String>) {
fn build_shell_command(&self, command: &str, _context: &ToolContext) -> (String, Vec<String>) {
#[cfg(target_os = "windows")]
{
return (
@@ -2208,7 +2209,7 @@ impl WorkspaceSandboxedBashTool {
#[cfg(not(target_os = "windows"))]
{
let working_dir = context.working_directory.to_string_lossy().to_string();
let working_dir = _context.working_directory.to_string_lossy().to_string();
let wrapped_command = format!("cd {} && {}", Self::quote_shell(&working_dir), command);
("sh".to_string(), vec!["-lc".to_string(), wrapped_command])
}
+1
View File
@@ -494,6 +494,7 @@ fn get_skill_key(app_type: &AppType, directory: &str) -> String {
}
/// 解析指定应用的技能列表(供 dispatcher 等非 Tauri command 场景调用)
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn resolve_skills_for_app(
db: &DbConnection,
skill_service: &Arc<SkillService>,
+21
View File
@@ -936,6 +936,7 @@ pub async fn open_chrome_profile_window(
.await
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn open_chrome_profile_window_global(
app: AppHandle,
app_state: AppState,
@@ -1049,6 +1050,7 @@ async fn ensure_managed_chrome_profile_with_manager(
Ok(session)
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn ensure_managed_chrome_profile_global(
profile_key: String,
url: Option<String>,
@@ -1064,6 +1066,7 @@ pub async fn get_chrome_profile_sessions(
Ok(list_alive_profile_sessions(state.0.clone()).await)
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn get_chrome_profile_sessions_global() -> Result<Vec<ChromeProfileSessionInfo>, String> {
Ok(list_alive_profile_sessions(shared_chrome_profile_manager()).await)
}
@@ -1076,6 +1079,7 @@ pub async fn close_chrome_profile_session(
close_chrome_profile_session_with_manager(state.0.clone(), profile_key).await
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn close_chrome_profile_session_global(profile_key: String) -> Result<bool, String> {
close_chrome_profile_session_with_manager(shared_chrome_profile_manager(), profile_key).await
}
@@ -1128,6 +1132,7 @@ pub async fn get_chrome_bridge_endpoint_info(
get_chrome_bridge_endpoint_info_with_state(app_state.inner().clone()).await
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn get_chrome_bridge_endpoint_info_global(
app_state: AppState,
) -> Result<ChromeBridgeEndpointInfo, String> {
@@ -1161,6 +1166,7 @@ pub async fn get_chrome_bridge_status() -> Result<ChromeBridgeStatusSnapshot, St
.await)
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn get_chrome_bridge_status_global() -> Result<ChromeBridgeStatusSnapshot, String> {
get_chrome_bridge_status().await
}
@@ -1181,6 +1187,7 @@ pub async fn get_browser_backend_policy() -> Result<BrowserBackendPolicy, String
Ok(BROWSER_BACKEND_POLICY.read().await.clone())
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn get_browser_backend_policy_global() -> Result<BrowserBackendPolicy, String> {
get_browser_backend_policy().await
}
@@ -1198,6 +1205,7 @@ pub async fn set_browser_backend_policy(
Ok(normalized)
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn set_browser_backend_policy_global(
policy: BrowserBackendPolicy,
) -> Result<BrowserBackendPolicy, String> {
@@ -1274,6 +1282,7 @@ pub async fn get_browser_backends_status(
})
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn get_browser_backends_status_global() -> Result<BrowserBackendsStatusSnapshot, String> {
let policy = BROWSER_BACKEND_POLICY.read().await.clone();
let bridge_status = chrome_bridge::chrome_bridge_hub()
@@ -1351,6 +1360,7 @@ pub async fn list_cdp_targets(
.await
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn list_cdp_targets_global(
request: ListCdpTargetsRequest,
) -> Result<Vec<CdpTargetInfo>, String> {
@@ -1379,6 +1389,7 @@ pub async fn open_cdp_session(
.await
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn open_cdp_session_global(
request: OpenCdpSessionRequest,
) -> Result<CdpSessionState, String> {
@@ -1407,6 +1418,7 @@ pub async fn close_cdp_session(request: BrowserSessionStateRequest) -> Result<bo
Ok(true)
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn close_cdp_session_global(request: BrowserSessionStateRequest) -> Result<bool, String> {
close_cdp_session(request).await
}
@@ -1425,6 +1437,7 @@ pub async fn start_browser_stream(
Ok(state)
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn start_browser_stream_global(
app: AppHandle,
request: StartBrowserStreamRequest,
@@ -1443,6 +1456,7 @@ pub async fn stop_browser_stream(
Ok(state)
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn stop_browser_stream_global(
request: StopBrowserStreamRequest,
) -> Result<CdpSessionState, String> {
@@ -1461,6 +1475,7 @@ pub async fn get_browser_session_state(
Ok(state)
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn get_browser_session_state_global(
db: DbConnection,
request: BrowserSessionStateRequest,
@@ -1484,6 +1499,7 @@ pub async fn take_over_browser_session(
Ok(state)
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn take_over_browser_session_global(
db: DbConnection,
request: UpdateBrowserSessionControlRequest,
@@ -1507,6 +1523,7 @@ pub async fn release_browser_session(
Ok(state)
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn release_browser_session_global(
db: DbConnection,
request: UpdateBrowserSessionControlRequest,
@@ -1530,6 +1547,7 @@ pub async fn resume_browser_session(
Ok(state)
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn resume_browser_session_global(
db: DbConnection,
request: UpdateBrowserSessionControlRequest,
@@ -1551,6 +1569,7 @@ pub async fn get_browser_event_buffer(
Ok(EventBufferSnapshotResponse::from(snapshot))
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn get_browser_event_buffer_global(
request: BrowserEventBufferRequest,
) -> Result<EventBufferSnapshotResponse, String> {
@@ -1597,6 +1616,7 @@ pub async fn get_browser_action_audit_logs(
Ok(result)
}
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn get_browser_action_audit_logs_global(
limit: Option<usize>,
) -> Result<Vec<BrowserActionAuditRecord>, String> {
@@ -1726,6 +1746,7 @@ pub async fn browser_execute_action_with_manager(
}
/// 使用全局 profile manager 执行动作(供 Agent 工具复用)
#[cfg_attr(any(test, not(debug_assertions)), allow(dead_code))]
pub async fn browser_execute_action_global(
request: BrowserActionRequest,
) -> Result<BrowserActionResult, String> {
+10 -7
View File
@@ -22,8 +22,6 @@ use serde_json::{json, Map, Value};
use std::cmp::Ordering;
use std::collections::{HashSet, VecDeque};
use std::ffi::OsString;
#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;
use std::path::{Path, PathBuf};
use std::process::Stdio;
use std::sync::{Arc, Mutex as StdMutex, OnceLock};
@@ -55,6 +53,7 @@ const NPM_MIRROR_CN: &str = "https://registry.npmmirror.com";
const NODE_MIN_VERSION: (u64, u64, u64) = (22, 12, 0);
const OPENCLAW_PROGRESS_LOG_LIMIT: usize = 400;
const OPENCLAW_INSTALLER_USER_AGENT: &str = "Lime-OpenClaw";
#[cfg(not(target_os = "windows"))]
const OPENCLAW_TEMP_CARGO_CHECK_DIR: &str = "/tmp/lime-cargo-check";
#[cfg(target_os = "windows")]
const CREATE_NO_WINDOW: u32 = 0x08000000;
@@ -546,7 +545,10 @@ impl OpenClawService {
return Ok(result);
}
self.ensure_dependency_ready(app, dependency).await
#[cfg(not(target_os = "windows"))]
{
self.ensure_dependency_ready(app, dependency).await
}
}
pub async fn cleanup_temp_artifacts(
@@ -2772,6 +2774,7 @@ async fn trigger_macos_command_line_tools_install() -> Result<String, String> {
}
#[cfg(not(target_os = "macos"))]
#[allow(dead_code)]
async fn trigger_macos_command_line_tools_install() -> Result<String, String> {
Err("当前平台不支持拉起 macOS 开发者工具安装器。".to_string())
}
@@ -3066,6 +3069,7 @@ async fn terminate_sysinfo_processes(system: &mut System, target_pids: &[Pid]) {
}
}
#[cfg_attr(target_os = "windows", allow(dead_code))]
fn parse_lsof_listener_pids(output: &str) -> Vec<u32> {
let mut pids = output
.lines()
@@ -3858,6 +3862,7 @@ fn shell_command_escape_for(platform: ShellPlatform, value: &str) -> String {
core_shell_command_escape_for(platform, value)
}
#[cfg_attr(target_os = "windows", allow(dead_code))]
fn shell_command_escape(value: &str) -> String {
shell_command_escape_for(current_shell_platform(), value)
}
@@ -3871,6 +3876,7 @@ fn shell_path_assignment_for(platform: ShellPlatform, binary_path: &str) -> Stri
core_shell_path_assignment_for(platform, binary_path)
}
#[cfg_attr(target_os = "windows", allow(dead_code))]
fn shell_path_assignment(binary_path: &str) -> String {
shell_path_assignment_for(current_shell_platform(), binary_path)
}
@@ -4828,6 +4834,7 @@ async fn read_binary_semver(path: &Path) -> Option<(u64, u64, u64)> {
parse_semver(stdout.trim()).or_else(|| parse_semver(stderr.trim()))
}
#[cfg_attr(not(test), allow(dead_code))]
fn select_best_semver_candidate(
candidates: Vec<(PathBuf, Option<(u64, u64, u64)>)>,
) -> Option<PathBuf> {
@@ -5912,10 +5919,6 @@ mod tests {
/// 从 Windows 注册表读取最新的 PATH 环境变量并刷新当前进程
#[cfg(target_os = "windows")]
fn refresh_windows_path_from_registry() -> Result<(), String> {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::ptr;
unsafe {
let mut combined_path = String::new();
+95 -255
View File
@@ -1,7 +1,7 @@
/**
* 启动画面组件
*
* 应用启动时显示 Logo 动画,然后淡出进入主界面
* 应用启动时显示专用 Logo、Slogan 与进度动画,然后淡出进入主界面。
*/
import { useEffect, useState } from "react";
@@ -14,28 +14,28 @@ const sceneEnter = keyframes`
const sceneExit = keyframes`
from { opacity: 1; transform: scale(1); }
to { opacity: 0; transform: scale(1.015); }
to { opacity: 0; transform: scale(1.02); }
`;
const panelFloat = keyframes`
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
`;
const pulse = keyframes`
0%, 100% { opacity: 0.56; }
50% { opacity: 1; }
`;
const progress = keyframes`
0% { transform: translateX(-42%) scaleX(0.72); opacity: 0.55; }
50% { transform: translateX(12%) scaleX(1); opacity: 1; }
100% { transform: translateX(78%) scaleX(0.82); opacity: 0.55; }
`;
const orbShift = keyframes`
const orbFloat = keyframes`
0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
50% { transform: translate3d(10px, -12px, 0) scale(1.06); }
50% { transform: translate3d(0, -16px, 0) scale(1.06); }
`;
const logoFloat = keyframes`
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
`;
const progressShift = keyframes`
0% { transform: translateX(-38%) scaleX(0.78); opacity: 0.56; }
50% { transform: translateX(10%) scaleX(1); opacity: 1; }
100% { transform: translateX(76%) scaleX(0.82); opacity: 0.56; }
`;
const glowPulse = keyframes`
0%, 100% { opacity: 0.58; transform: scale(0.96); }
50% { opacity: 1; transform: scale(1.04); }
`;
const Container = styled.div<{ $isExiting: boolean }>`
@@ -46,13 +46,13 @@ const Container = styled.div<{ $isExiting: boolean }>`
justify-content: center;
overflow: hidden;
background:
radial-gradient(circle at 16% 18%, rgba(56, 189, 248, 0.12), transparent 32%),
radial-gradient(circle at 84% 12%, rgba(16, 185, 129, 0.1), transparent 30%),
radial-gradient(circle at 72% 82%, rgba(245, 158, 11, 0.08), transparent 26%),
radial-gradient(circle at 20% 18%, rgba(132, 204, 22, 0.18), transparent 30%),
radial-gradient(circle at 78% 12%, rgba(250, 204, 21, 0.14), transparent 28%),
radial-gradient(circle at 50% 84%, rgba(34, 197, 94, 0.1), transparent 28%),
linear-gradient(
135deg,
180deg,
hsl(var(--background)) 0%,
hsl(var(--muted) / 0.84) 48%,
hsl(var(--muted) / 0.72) 48%,
hsl(var(--background)) 100%
);
z-index: 9999;
@@ -78,9 +78,8 @@ const AmbientOrb = styled.div<{
left: ${({ $left }) => $left ?? "auto"};
border-radius: 999px;
background: ${({ $color }) => $color};
filter: blur(26px);
opacity: 0.8;
animation: ${orbShift} 11s ease-in-out infinite;
filter: blur(40px);
animation: ${orbFloat} 10s ease-in-out infinite;
animation-delay: ${({ $delay }) => $delay ?? "0s"};
pointer-events: none;
`;
@@ -88,214 +87,90 @@ const AmbientOrb = styled.div<{
const Stage = styled.div`
position: relative;
z-index: 1;
width: min(560px, calc(100vw - 32px));
`;
const Panel = styled.div`
position: relative;
overflow: hidden;
border: 1px solid hsl(var(--border) / 0.78);
border-radius: 32px;
padding: 32px;
background:
linear-gradient(
180deg,
hsl(var(--card) / 0.94) 0%,
hsl(var(--card) / 0.84) 100%
);
backdrop-filter: blur(18px);
box-shadow:
0 26px 80px rgba(15, 23, 42, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.36);
animation: ${panelFloat} 5.5s ease-in-out infinite;
@media (max-width: 640px) {
padding: 24px 20px;
border-radius: 28px;
}
`;
const PanelGlow = styled.div`
position: absolute;
inset: auto -120px -120px auto;
width: 240px;
height: 240px;
border-radius: 999px;
background: radial-gradient(circle, rgba(56, 189, 248, 0.12), transparent 66%);
pointer-events: none;
`;
const HeaderPill = styled.div`
display: inline-flex;
align-items: center;
border-radius: 999px;
border: 1px solid hsl(var(--border) / 0.72);
background: hsl(var(--card) / 0.92);
padding: 6px 12px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.18em;
color: hsl(var(--muted-foreground));
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.04);
`;
const Hero = styled.div`
position: relative;
width: min(720px, calc(100vw - 40px));
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
margin-top: 18px;
@media (max-width: 640px) {
flex-direction: column;
align-items: flex-start;
}
justify-content: center;
text-align: center;
`;
const LogoWrap = styled.div`
const LogoStack = styled.div`
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 108px;
height: 108px;
border-radius: 28px;
border: 1px solid hsl(var(--border) / 0.75);
background:
linear-gradient(
180deg,
hsl(var(--card)) 0%,
hsl(var(--muted) / 0.74) 100%
);
box-shadow:
0 18px 34px rgba(15, 23, 42, 0.08),
inset 0 1px 0 rgba(255, 255, 255, 0.42);
width: min(360px, 78vw);
height: min(360px, 78vw);
`;
@media (max-width: 640px) {
width: 96px;
height: 96px;
border-radius: 24px;
}
const LogoGlow = styled.div`
position: absolute;
inset: 12% 12% 16%;
border-radius: 999px;
background:
radial-gradient(circle, rgba(163, 230, 53, 0.34) 0%, rgba(163, 230, 53, 0.12) 44%, transparent 72%);
filter: blur(24px);
animation: ${glowPulse} 2.8s ease-in-out infinite;
`;
const Logo = styled.img`
width: 72px;
height: 72px;
position: relative;
width: clamp(240px, 34vw, 320px);
height: clamp(240px, 34vw, 320px);
object-fit: contain;
filter: drop-shadow(0 16px 28px rgba(15, 23, 42, 0.16));
animation: ${logoFloat} 4.2s ease-in-out infinite;
filter: drop-shadow(0 28px 44px rgba(15, 23, 42, 0.16));
@media (max-width: 640px) {
width: 64px;
height: 64px;
width: min(260px, 72vw);
height: min(260px, 72vw);
}
`;
const CopyBlock = styled.div`
flex: 1;
min-width: 0;
`;
const AppName = styled.h1`
margin: 0;
font-size: 38px;
const Slogan = styled.p`
margin: 22px 0 0;
max-width: 18em;
font-size: clamp(28px, 4vw, 38px);
line-height: 1.16;
font-weight: 700;
letter-spacing: -0.03em;
letter-spacing: -0.04em;
color: hsl(var(--foreground));
@media (max-width: 640px) {
font-size: 32px;
}
`;
const Subtitle = styled.p`
margin: 10px 0 0;
font-size: 14px;
line-height: 1.7;
color: hsl(var(--muted-foreground));
`;
const MetaRow = styled.div`
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 24px;
`;
const MetaPill = styled.div`
display: inline-flex;
align-items: center;
gap: 8px;
border-radius: 999px;
border: 1px solid hsl(var(--border) / 0.72);
background: hsl(var(--card) / 0.88);
padding: 8px 12px;
font-size: 12px;
font-weight: 500;
color: hsl(var(--muted-foreground));
`;
const Dot = styled.span`
width: 6px;
height: 6px;
border-radius: 999px;
background: rgba(14, 165, 233, 0.85);
animation: ${pulse} 1.5s ease-in-out infinite;
`;
const LoadingArea = styled.div`
margin-top: 26px;
padding-top: 22px;
border-top: 1px solid hsl(var(--border) / 0.7);
text-wrap: balance;
text-shadow: 0 10px 30px rgba(15, 23, 42, 0.08);
`;
const ProgressTrack = styled.div`
position: relative;
overflow: hidden;
width: 100%;
height: 6px;
margin-top: 28px;
width: min(320px, 72vw);
height: 8px;
border-radius: 999px;
background: hsl(var(--muted) / 0.92);
background:
linear-gradient(
90deg,
hsl(var(--muted) / 0.82) 0%,
hsl(var(--muted) / 0.96) 100%
);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.32),
0 12px 28px rgba(15, 23, 42, 0.08);
`;
const ProgressBar = styled.div`
position: absolute;
inset: 0 auto 0 0;
width: 46%;
width: 44%;
border-radius: inherit;
background:
linear-gradient(
90deg,
rgba(56, 189, 248, 0.82) 0%,
rgba(16, 185, 129, 0.72) 100%
rgba(132, 204, 22, 0.96) 0%,
rgba(250, 204, 21, 0.9) 100%
);
animation: ${progress} 1.8s ease-in-out infinite;
`;
const LoadingRow = styled.div`
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
margin-top: 14px;
@media (max-width: 640px) {
flex-direction: column;
align-items: flex-start;
}
`;
const LoadingText = styled.p`
margin: 0;
font-size: 14px;
font-weight: 600;
color: hsl(var(--foreground));
`;
const LoadingHint = styled.p`
margin: 0;
font-size: 12px;
line-height: 1.6;
color: hsl(var(--muted-foreground));
animation: ${pulse} 1.8s ease-in-out infinite;
box-shadow: 0 0 24px rgba(163, 230, 53, 0.35);
animation: ${progressShift} 1.6s ease-in-out infinite;
`;
interface SplashScreenProps {
@@ -327,70 +202,35 @@ export function SplashScreen({
return (
<Container $isExiting={isExiting}>
<AmbientOrb
$size={220}
$top="-40px"
$left="-28px"
$color="rgba(56, 189, 248, 0.22)"
$size={280}
$top="-72px"
$left="-56px"
$color="rgba(132, 204, 22, 0.22)"
/>
<AmbientOrb
$size={260}
$size={340}
$top="8%"
$right="-60px"
$color="rgba(16, 185, 129, 0.18)"
$right="-96px"
$color="rgba(250, 204, 21, 0.14)"
$delay="-2.2s"
/>
<AmbientOrb
$size={220}
$bottom="-54px"
$size={260}
$bottom="-84px"
$left="18%"
$color="rgba(245, 158, 11, 0.14)"
$delay="-4.1s"
$color="rgba(34, 197, 94, 0.14)"
$delay="-4s"
/>
<Stage>
<Panel>
<PanelGlow />
<HeaderPill>CREATIVE WORKBENCH</HeaderPill>
<Hero>
<LogoWrap>
<Logo src="/logo.png" alt="Lime" />
</LogoWrap>
<CopyBlock>
<AppName>Lime</AppName>
<Subtitle>
正在准备创作工作台与本地运行状态,保持当前节奏,不打断你的上下文。
</Subtitle>
</CopyBlock>
</Hero>
<MetaRow>
<MetaPill>
<Dot />
本地优先
</MetaPill>
<MetaPill>
<Dot />
创作工作台
</MetaPill>
<MetaPill>
<Dot />
启动中
</MetaPill>
</MetaRow>
<LoadingArea>
<ProgressTrack>
<ProgressBar />
</ProgressTrack>
<LoadingRow>
<LoadingText>正在加载...</LoadingText>
<LoadingHint>首次启动或更新后进入时间可能稍长</LoadingHint>
</LoadingRow>
</LoadingArea>
</Panel>
<LogoStack>
<LogoGlow />
<Logo src="/logo-splash.png" alt="Lime" />
</LogoStack>
<Slogan>青柠一下,灵感即来。</Slogan>
<ProgressTrack aria-hidden>
<ProgressBar />
</ProgressTrack>
</Stage>
</Container>
);