fix release assets and refresh lime branding
@@ -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'
|
||||
|
||||
@@ -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 常见提示,不代表安装包必然损坏
|
||||
|
||||
---
|
||||
|
||||
@@ -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`:体积更小,适合网络稳定且可访问微软下载源的环境
|
||||
- 如果在线安装失败,请改用离线安装包
|
||||
|
||||
---
|
||||
|
||||
@@ -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 到处复用,结果某个场景总显得小或不协调”。
|
||||
|
After Width: | Height: | Size: 881 KiB |
|
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 257 KiB |
@@ -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());
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 191 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 284 KiB After Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 897 KiB After Width: | Height: | Size: 843 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 36 KiB |
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||