diff --git a/.gitignore b/.gitignore index 11ead7c..945ae0f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ coverage/ *.log test-results/ playwright-report/ +apps/desktop/release/ diff --git a/apps/desktop/README.md b/apps/desktop/README.md new file mode 100644 index 0000000..f4617cd --- /dev/null +++ b/apps/desktop/README.md @@ -0,0 +1,20 @@ +# 森林AI 桌面客户端 + +桌面端提供两个独立可执行文件: + +- `senlinai-mini.exe`:默认启动入口。窄屏、置顶,启动后按当前显示器高度停靠在屏幕右侧,也可以切换到左侧。 +- `senlinai-full.exe`:完整工作台客户端。 + +```bash +# 默认启动 Mini +npm run dev + +# 分别启动 +npm run mini:dev +npm run full:dev + +# 一次构建两个 EXE +npm run build +``` + +两个构建产物会汇总到 `apps/desktop/release`。 diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 99ef71a..cc0c420 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,12 +1,17 @@ { "name": "desktop", "private": true, - "version": "0.1.0", + "version": "1.0.0", "type": "module", "scripts": { "tauri": "tauri", - "dev": "tauri dev", - "build": "tauri build --no-bundle", + "start": "npm run mini:dev", + "dev": "npm run mini:dev", + "mini:dev": "npm --prefix ../mini run desktop:dev", + "full:dev": "tauri dev", + "mini:build": "npm --prefix ../mini run desktop:build", + "full:build": "tauri build --no-bundle", + "build": "node scripts/build-all.mjs", "bundle": "tauri build --ci --no-sign" }, "devDependencies": { diff --git a/apps/desktop/scripts/build-all.mjs b/apps/desktop/scripts/build-all.mjs new file mode 100644 index 0000000..8a0784b --- /dev/null +++ b/apps/desktop/scripts/build-all.mjs @@ -0,0 +1,44 @@ +import { copyFileSync, mkdirSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { spawnSync } from 'node:child_process' + +const desktopDir = join(dirname(fileURLToPath(import.meta.url)), '..') +const repoDir = join(desktopDir, '..', '..') +const isWindows = process.platform === 'win32' +const npmCommand = isWindows ? process.env.ComSpec || 'cmd.exe' : 'npm' + +function run(script) { + const args = isWindows ? ['/d', '/s', '/c', `npm run ${script}`] : ['run', script] + const result = spawnSync(npmCommand, args, { + cwd: desktopDir, + stdio: 'inherit', + }) + + if (result.error) throw result.error + if (result.status !== 0) { + process.exit(result.status ?? 1) + } +} + +run('full:build') +run('mini:build') + +const releaseDir = join(desktopDir, 'release') +mkdirSync(releaseDir, { recursive: true }) + +const artifacts = [ + { + source: join(desktopDir, 'src-tauri', 'target', 'release', 'senlinai-full.exe'), + target: join(releaseDir, 'senlinai-full.exe'), + }, + { + source: join(repoDir, 'apps', 'mini', 'src-tauri', 'target', 'release', 'senlinai-mini.exe'), + target: join(releaseDir, 'senlinai-mini.exe'), + }, +] + +for (const artifact of artifacts) { + copyFileSync(artifact.source, artifact.target) + console.log(`Created ${artifact.target}`) +} diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock index 74b1164..b873547 100644 --- a/apps/desktop/src-tauri/Cargo.lock +++ b/apps/desktop/src-tauri/Cargo.lock @@ -2833,8 +2833,8 @@ dependencies = [ ] [[package]] -name = "senlinai_workbench" -version = "0.1.0" +name = "senlinai-full" +version = "1.0.0" dependencies = [ "serde", "serde_json", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 2b83110..b9072db 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "senlinai_workbench" -version = "0.1.0" -description = "森林AI 项目工作台桌面端" +name = "senlinai-full" +version = "1.0.0" +description = "森林AI 完整版桌面客户端" authors = ["森林AI"] edition = "2021" diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 5c877b5..7ae3bde 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", - "productName": "森林AI", - "version": "0.1.0", + "productName": "森林AI Full", + "version": "1.0.0", "identifier": "ai.senlin.workbench", "build": { "frontendDist": "../../web_v1/dist", @@ -12,7 +12,7 @@ "app": { "windows": [ { - "title": "森林AI", + "title": "森林AI Full", "width": 1280, "height": 820 } diff --git a/apps/mini/README.md b/apps/mini/README.md index 857f583..89af3d9 100644 --- a/apps/mini/README.md +++ b/apps/mini/README.md @@ -9,5 +9,5 @@ npm run desktop:dev ``` - Web 开发端口:`5180` -- 桌面窗口默认尺寸:`420 × 820` +- 桌面窗口默认宽度:`420`;启动后高度自动适配当前屏幕全高,并停靠在屏幕右侧 - 左右停靠状态保存在本地;在 Tauri 桌面环境中会移动实际窗口。 diff --git a/apps/mini/src-tauri/src/lib.rs b/apps/mini/src-tauri/src/lib.rs index 1788b35..26cc11a 100644 --- a/apps/mini/src-tauri/src/lib.rs +++ b/apps/mini/src-tauri/src/lib.rs @@ -1,4 +1,4 @@ -use tauri::{PhysicalPosition, Window}; +use tauri::{PhysicalPosition, PhysicalSize, Window}; #[tauri::command] fn dock_window(window: Window, side: String) -> Result<(), String> { @@ -9,12 +9,16 @@ fn dock_window(window: Window, side: String) -> Result<(), String> { let window_size = window.outer_size().map_err(|error| error.to_string())?; let monitor_position = monitor.position(); let monitor_size = monitor.size(); + let dock_size = PhysicalSize::new(window_size.width, monitor_size.height); let x = if side == "left" { monitor_position.x } else { - monitor_position.x + monitor_size.width as i32 - window_size.width as i32 + monitor_position.x + monitor_size.width as i32 - dock_size.width as i32 }; + window + .set_size(dock_size) + .map_err(|error| error.to_string())?; window .set_position(PhysicalPosition::new(x, monitor_position.y)) .map_err(|error| error.to_string())?; diff --git a/apps/mini/src/App.tsx b/apps/mini/src/App.tsx index 10c7db5..917e284 100644 --- a/apps/mini/src/App.tsx +++ b/apps/mini/src/App.tsx @@ -31,7 +31,10 @@ export function App() { const [experts, setExperts] = useState([]) const [aiSessions, setAISessions] = useState([]) const [view, setView] = useState('home') - const [dockSide, setDockSide] = useState(() => localStorage.getItem('senlin-mini-dock') === 'left' ? 'left' : 'right') + const [dockSide, setDockSide] = useState(() => { + if ('__TAURI_INTERNALS__' in window) return 'right' + return localStorage.getItem('senlin-mini-dock') === 'left' ? 'left' : 'right' + }) const [loading, setLoading] = useState(Boolean(session)) const [error, setError] = useState('') const [action, setAction] = useState(null)