feat(desktop): ship full and mini clients
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@ coverage/
|
|||||||
*.log
|
*.log
|
||||||
test-results/
|
test-results/
|
||||||
playwright-report/
|
playwright-report/
|
||||||
|
apps/desktop/release/
|
||||||
|
|||||||
20
apps/desktop/README.md
Normal file
20
apps/desktop/README.md
Normal file
@@ -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`。
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "desktop",
|
"name": "desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"dev": "tauri dev",
|
"start": "npm run mini:dev",
|
||||||
"build": "tauri build --no-bundle",
|
"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"
|
"bundle": "tauri build --ci --no-sign"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
44
apps/desktop/scripts/build-all.mjs
Normal file
44
apps/desktop/scripts/build-all.mjs
Normal file
@@ -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}`)
|
||||||
|
}
|
||||||
4
apps/desktop/src-tauri/Cargo.lock
generated
4
apps/desktop/src-tauri/Cargo.lock
generated
@@ -2833,8 +2833,8 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "senlinai_workbench"
|
name = "senlinai-full"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "senlinai_workbench"
|
name = "senlinai-full"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
description = "森林AI 项目工作台桌面端"
|
description = "森林AI 完整版桌面客户端"
|
||||||
authors = ["森林AI"]
|
authors = ["森林AI"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "森林AI",
|
"productName": "森林AI Full",
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"identifier": "ai.senlin.workbench",
|
"identifier": "ai.senlin.workbench",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../../web_v1/dist",
|
"frontendDist": "../../web_v1/dist",
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
{
|
{
|
||||||
"title": "森林AI",
|
"title": "森林AI Full",
|
||||||
"width": 1280,
|
"width": 1280,
|
||||||
"height": 820
|
"height": 820
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ npm run desktop:dev
|
|||||||
```
|
```
|
||||||
|
|
||||||
- Web 开发端口:`5180`
|
- Web 开发端口:`5180`
|
||||||
- 桌面窗口默认尺寸:`420 × 820`
|
- 桌面窗口默认宽度:`420`;启动后高度自动适配当前屏幕全高,并停靠在屏幕右侧
|
||||||
- 左右停靠状态保存在本地;在 Tauri 桌面环境中会移动实际窗口。
|
- 左右停靠状态保存在本地;在 Tauri 桌面环境中会移动实际窗口。
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use tauri::{PhysicalPosition, Window};
|
use tauri::{PhysicalPosition, PhysicalSize, Window};
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn dock_window(window: Window, side: String) -> Result<(), String> {
|
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 window_size = window.outer_size().map_err(|error| error.to_string())?;
|
||||||
let monitor_position = monitor.position();
|
let monitor_position = monitor.position();
|
||||||
let monitor_size = monitor.size();
|
let monitor_size = monitor.size();
|
||||||
|
let dock_size = PhysicalSize::new(window_size.width, monitor_size.height);
|
||||||
let x = if side == "left" {
|
let x = if side == "left" {
|
||||||
monitor_position.x
|
monitor_position.x
|
||||||
} else {
|
} 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
|
window
|
||||||
.set_position(PhysicalPosition::new(x, monitor_position.y))
|
.set_position(PhysicalPosition::new(x, monitor_position.y))
|
||||||
.map_err(|error| error.to_string())?;
|
.map_err(|error| error.to_string())?;
|
||||||
|
|||||||
@@ -31,7 +31,10 @@ export function App() {
|
|||||||
const [experts, setExperts] = useState<Expert[]>([])
|
const [experts, setExperts] = useState<Expert[]>([])
|
||||||
const [aiSessions, setAISessions] = useState<AISession[]>([])
|
const [aiSessions, setAISessions] = useState<AISession[]>([])
|
||||||
const [view, setView] = useState<View>('home')
|
const [view, setView] = useState<View>('home')
|
||||||
const [dockSide, setDockSide] = useState<DockSide>(() => localStorage.getItem('senlin-mini-dock') === 'left' ? 'left' : 'right')
|
const [dockSide, setDockSide] = useState<DockSide>(() => {
|
||||||
|
if ('__TAURI_INTERNALS__' in window) return 'right'
|
||||||
|
return localStorage.getItem('senlin-mini-dock') === 'left' ? 'left' : 'right'
|
||||||
|
})
|
||||||
const [loading, setLoading] = useState(Boolean(session))
|
const [loading, setLoading] = useState(Boolean(session))
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
const [action, setAction] = useState<Action>(null)
|
const [action, setAction] = useState<Action>(null)
|
||||||
|
|||||||
Reference in New Issue
Block a user