win系统适配完成

This commit is contained in:
PineWin
2026-05-30 15:39:42 +08:00
parent b8debfcd46
commit 304f4b54ff
67 changed files with 323 additions and 111 deletions
+7
View File
@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(cargo check *)"
]
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

+77 -19
View File
@@ -27,35 +27,93 @@
} }
@keyframes spin { to { transform: rotate(360deg); } } @keyframes spin { to { transform: rotate(360deg); } }
p { color: #4a5568; font-size: 14px; } p { color: #4a5568; font-size: 14px; }
.status { color: #718096; font-size: 13px; margin-top: 12px; }
.retry-btn {
display: none;
margin-top: 16px;
padding: 8px 24px;
background: #2563eb;
color: #fff;
border: none;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
}
.retry-btn:hover { background: #1d4ed8; }
.retry-btn.show { display: inline-block; }
</style> </style>
<script> <script>
// 等待 streamlit 服务就绪后跳转
(function() { (function() {
var STREAMLIT_URL = 'http://localhost:8501';
var retries = 0; var retries = 0;
var maxRetries = 50; var maxRetries = 120; // 最多等待 120 次(约 60 秒)
var interval = 500; // 每 500ms 检查一次
var statusEl = null;
var retryBtn = null;
function showStatus(msg, isError) {
if (!statusEl) {
statusEl = document.createElement('p');
statusEl.className = 'status';
document.querySelector('.loader').appendChild(statusEl);
}
statusEl.textContent = msg;
if (isError) {
statusEl.style.color = '#dc2626';
} else {
statusEl.style.color = '#718096';
}
if (!retryBtn) {
retryBtn = document.createElement('button');
retryBtn.className = 'retry-btn';
retryBtn.textContent = '手动重试';
retryBtn.onclick = function() {
retries = 0;
retryBtn.classList.remove('show');
showStatus('正在重新连接...', false);
setTimeout(check, 500);
};
document.querySelector('.loader').appendChild(retryBtn);
}
}
function check() { function check() {
var xhr = new XMLHttpRequest(); // 使用 fetch no-cors 模式检测端口是否可达
xhr.open('GET', 'http://127.0.0.1:8501', true); // no-cors: 请求成功则 resolve,连接失败则 reject
xhr.timeout = 2000; var controller = new AbortController();
xhr.onload = function() { var timeoutId = setTimeout(function() { controller.abort(); }, 3000);
window.location.href = 'http://127.0.0.1:8501';
}; fetch(STREAMLIT_URL, { mode: 'no-cors', signal: controller.signal })
xhr.onerror = function() { .then(function() {
clearTimeout(timeoutId);
// 服务可达 → 跳转
showStatus('服务已就绪,正在进入...');
window.location.replace(STREAMLIT_URL);
})
.catch(function(err) {
clearTimeout(timeoutId);
retries++; retries++;
if (retries < maxRetries) { if (retries < maxRetries) {
setTimeout(check, 500); showStatus('正在启动服务... (' + retries + '/' + maxRetries + ')');
setTimeout(check, interval);
} else {
showStatus('服务启动超时。请确认 Streamlit 已在 :8501 端口运行,或点击下方按钮重试。', true);
if (retryBtn) retryBtn.classList.add('show');
} }
}; });
xhr.ontimeout = function() {
retries++;
if (retries < maxRetries) {
setTimeout(check, 500);
} }
};
xhr.send(); // 等待 DOM 就绪后再开始检测
function start() {
showStatus('正在启动服务...');
// 首次延迟 1.5 秒,给服务一点启动时间
setTimeout(check, 1500);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start);
} else {
start();
} }
// 等待 500ms 后开始轮询(给 streamlit 启动时间)
setTimeout(check, 500);
})(); })();
</script> </script>
</head> </head>
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -128,7 +128,6 @@ def save_cfg_to_file(cfg: dict) -> None:
- API Keys + 品牌信息 → .streamlit/secrets.toml - API Keys + 品牌信息 → .streamlit/secrets.toml
""" """
import tomllib import tomllib
import tomli_w # type: ignore
# ── 1. 非敏感配置 → config.json ── # ── 1. 非敏感配置 → config.json ──
config_path = Path(__file__).with_name("config.json") config_path = Path(__file__).with_name("config.json")
@@ -203,6 +202,7 @@ def save_cfg_to_file(cfg: dict) -> None:
new_app_config["temperature"] = cfg["temperature"] new_app_config["temperature"] = cfg["temperature"]
# 写出 TOML # 写出 TOML
import tomli_w
secrets_path.parent.mkdir(parents=True, exist_ok=True) secrets_path.parent.mkdir(parents=True, exist_ok=True)
with secrets_path.open("wb") as f: with secrets_path.open("wb") as f:
content = {"api_keys": new_api_keys, "app_config": new_app_config} content = {"api_keys": new_api_keys, "app_config": new_app_config}
+4 -4
View File
@@ -21,7 +21,6 @@ datas = [
("config.json", "."), ("config.json", "."),
("geo_data.db", "."), ("geo_data.db", "."),
(".streamlit", ".streamlit"), (".streamlit", ".streamlit"),
("knowledge_base", "knowledge_base"),
("modules", "modules"), ("modules", "modules"),
("platform_sync", "platform_sync"), ("platform_sync", "platform_sync"),
] ]
@@ -150,9 +149,10 @@ hiddenimports = [
"watchdog", "watchdog",
"sqlite3", "sqlite3",
"importlib.metadata", "importlib.metadata",
"json", "tomli_w",
"csv", # ---- Streamlit 内部依赖(动态子模块多,显式声明)----
"hashlib", "altair",
"pydeck",
] ]
# ── 排除不必要的包(减小体积) ────────────────────────────────── # ── 排除不必要的包(减小体积) ──────────────────────────────────
+4 -4
View File
@@ -24,7 +24,7 @@ requires = ["setuptools"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
include = ["build_scripts*"] include = ["scripts*"]
[dependency-groups] [dependency-groups]
dev = [ dev = [
@@ -35,6 +35,6 @@ dev = [
package = true package = true
[project.scripts] [project.scripts]
python-build = "build_scripts.build:main" python-build = "scripts.build:main"
tauri-run = "build_scripts.runtauri:main" tauri-run = "scripts.runtauri:main"
tauri-build = "build_scripts.build:build_tauri" tauri-build = "scripts.build:build_tauri"
View File
+19
View File
@@ -0,0 +1,19 @@
{
"permissions": {
"allow": [
"Bash(cargo clean *)",
"Bash(cargo build *)",
"Bash(awk '{print $NF}')",
"Bash(xxd \"D:/MyCode/ChouJuGEO/build_scripts/tauri.py\")",
"Bash(cargo tauri *)",
"Bash(cargo install *)",
"Read(//c/Users/Administrator/AppData/Local/tauri/WixTools314/**)",
"Bash(powershell *)",
"Bash(dism /online /get-featureinfo /featurename:NetFx3)",
"Bash(reg query *)",
"Bash(cmd *)",
"Bash(\"C:/Users/Administrator/AppData/Local/tauri/WixTools314/light.exe\")",
"Bash(C:/Users/Administrator/AppData/Local/tauri/WixTools314/light.exe *)"
]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@color/ic_launcher_background"/>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#fff</color>
</resources>
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 953 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

+182 -67
View File
@@ -2,7 +2,30 @@ use std::process::{Child, Command, Stdio};
use std::sync::Mutex; use std::sync::Mutex;
use tauri::Manager; use tauri::Manager;
struct StreamlitProcess(Mutex<Option<Child>>); /// 包装 Child,在 Drop 时强制终止整个进程树(防止 panic/crash 时泄漏)
struct ProcessGuard {
child: Option<Child>,
}
impl ProcessGuard {
fn new(child: Child) -> Self {
Self { child: Some(child) }
}
fn take(&mut self) -> Option<Child> {
self.child.take()
}
}
impl Drop for ProcessGuard {
fn drop(&mut self) {
if let Some(ref mut child) = self.child.take() {
kill_process_tree(child);
}
}
}
struct StreamlitProcess(Mutex<Option<ProcessGuard>>);
/// 等待指定端口可连接,最多等待 timeout_secs 秒 /// 等待指定端口可连接,最多等待 timeout_secs 秒
fn wait_for_port(host: &str, port: u16, timeout_secs: u64) -> bool { fn wait_for_port(host: &str, port: u16, timeout_secs: u64) -> bool {
@@ -16,31 +39,135 @@ fn wait_for_port(host: &str, port: u16, timeout_secs: u64) -> bool {
false false
} }
/// 停止 Streamlit 子进程 /// 终止整个进程树(跨平台)
/// ///
/// - Unix: 先 SIGTERM(优雅退出),等待 3 秒无响应后 SIGKILL /// - Windows: taskkill /F /T → 终止目标进程及其所有子进程
/// - Windows: 直接 TerminateProcess /// - Unix: SIGTERM → 等待 3s → SIGKILL
fn kill_streamlit(child: &mut Child) { fn kill_process_tree(child: &mut Child) {
#[cfg(windows)]
{
let pid = child.id();
// taskkill /T 递归终止整个进程树,确保 Streamlit worker 等孙进程也被清理
match Command::new("taskkill")
.args(["/F", "/T", "/PID", &pid.to_string()])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
{
Ok(mut tk) => {
let _ = tk.wait();
log::info!("[清理] taskkill /T /PID {} 完成", pid);
}
Err(e) => {
log::warn!("[清理] taskkill 调用失败: {},回退到直接 kill", e);
let _ = child.kill();
}
}
let _ = child.wait();
log::info!("[清理] Streamlit 进程树已终止 (Windows, PID: {})", pid);
}
#[cfg(unix)] #[cfg(unix)]
{ {
// 先发 SIGTERM 请求优雅退出 let pid = child.id();
let _ = Command::new("kill") unsafe {
.arg("-TERM") extern "C" {
.arg(child.id().to_string()) fn kill(pid: i32, sig: i32) -> i32;
.spawn(); }
kill(pid as i32, 15); // SIGTERM
}
std::thread::sleep(std::time::Duration::from_secs(3)); std::thread::sleep(std::time::Duration::from_secs(3));
// 尝试 wait(如果已退出则返回 Ok(Some(status))
if let Ok(Some(_)) = child.try_wait() { if let Ok(Some(_)) = child.try_wait() {
log::info!("Streamlit 进程已优雅退出"); log::info!("[清理] Streamlit 进程已优雅退出 (SIGTERM, PID: {})", pid);
return; return;
} }
log::warn!("Streamlit 进程未响应 SIGTERM,强制终止..."); log::warn!("[清理] Streamlit 进程未响应 SIGTERM,强制终止...");
}
// 强制终止
let _ = child.kill(); let _ = child.kill();
let _ = child.wait(); let _ = child.wait();
log::info!("[清理] Streamlit 进程已强制终止 (Unix, PID: {})", pid);
}
#[cfg(not(any(windows, unix)))]
{
let _ = child.kill();
let _ = child.wait();
}
}
/// 从 StreamlitProcess 状态中取出进程并终止
fn cleanup_streamlit(state: &StreamlitProcess) {
if let Ok(mut guard) = state.0.lock() {
if let Some(ref mut pg) = *guard {
if let Some(ref mut child) = pg.take() {
kill_process_tree(child);
}
}
// 清除空壳,避免 Drop 时二次处理
*guard = None;
}
}
/// 在 debug 模式下,尝试多种方式启动 Streamlit
fn spawn_streamlit_debug(project_root: &std::path::Path) -> Result<Child, String> {
let script_path = project_root.join("geo_tool.py");
let script = script_path.to_str().unwrap_or("geo_tool.py");
let args = [
"run",
script,
"--server.port",
"8501",
"--server.address",
"127.0.0.1",
"--server.headless",
"true",
"--browser.gatherUsageStats",
"false",
"--logger.level",
"error",
];
log::info!("[开发模式] 启动 Streamlit: {:?}", script_path);
let result = Command::new("streamlit")
.args(&args)
.current_dir(project_root)
.stdout(Stdio::null())
.stderr(Stdio::inherit())
.spawn();
if let Ok(child) = result {
log::info!("[开发模式] 通过 'streamlit' 启动成功 (PID: {})", child.id());
return Ok(child);
}
log::warn!("[开发模式] 'streamlit' 命令未找到,尝试 python -m streamlit ...");
Command::new("python")
.arg("-m")
.arg("streamlit")
.args(&args)
.current_dir(project_root)
.stdout(Stdio::null())
.stderr(Stdio::inherit())
.spawn()
.map(|child| {
log::info!("[开发模式] 通过 'python -m streamlit' 启动成功 (PID: {})", child.id());
child
})
.map_err(|e| {
let msg = format!(
"无法启动 Streamlit。已尝试:\n \
1) streamlit run ...\n \
2) python -m streamlit run ...\n \
请确认已安装 streamlit: pip install streamlit\n \
最终错误: {}",
e
);
log::error!("[开发模式] {}", msg);
msg
})
} }
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
@@ -55,39 +182,12 @@ pub fn run() {
)?; )?;
} }
let child = if cfg!(debug_assertions) { let child_result = if cfg!(debug_assertions) {
// ── 开发模式 ── 使用系统安装的 streamlit ──
let manifest_dir = let manifest_dir =
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let project_root = manifest_dir.parent().unwrap_or(&manifest_dir); let project_root = manifest_dir.parent().unwrap_or(&manifest_dir);
let script_path = project_root.join("geo_tool.py"); spawn_streamlit_debug(project_root)
log::info!(
"[开发模式] 启动 Streamlit: {:?}",
script_path
);
Command::new("streamlit")
.args([
"run",
script_path.to_str().unwrap_or("geo_tool.py"),
"--server.port",
"8501",
"--server.address",
"127.0.0.1",
"--server.headless",
"true",
"--browser.gatherUsageStats",
"false",
"--logger.level",
"error",
])
.current_dir(project_root)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
} else { } else {
// ── 生产模式 ── 使用 PyInstaller 打包的独立可执行文件 ──
let resource_dir = app let resource_dir = app
.path() .path()
.resource_dir() .resource_dir()
@@ -98,12 +198,26 @@ pub fn run() {
} else { } else {
"geo_tool_app" "geo_tool_app"
}; };
let exe_path = resource_dir.join("_up_").join("dist_python").join(exe_name);
log::info!( let exe_path_legacy =
"[生产模式] 启动打包应用: {:?}", resource_dir.join("_up_").join("dist_python").join(exe_name);
exe_path let exe_path_direct = resource_dir.join("dist_python").join(exe_name);
let exe_path = if exe_path_legacy.exists() {
exe_path_legacy
} else if exe_path_direct.exists() {
exe_path_direct
} else {
let msg = format!(
"未找到 Streamlit 打包应用:\n 尝试1: {:?}\n 尝试2: {:?}",
exe_path_legacy, exe_path_direct
); );
log::error!("[生产模式] {}", msg);
eprintln!("错误:{}", msg);
return Ok(());
};
log::info!("[生产模式] 启动打包应用: {:?}", exe_path);
let mut cmd = Command::new(&exe_path); let mut cmd = Command::new(&exe_path);
cmd.current_dir(&resource_dir) cmd.current_dir(&resource_dir)
@@ -111,7 +225,6 @@ pub fn run() {
.stdout(Stdio::null()) .stdout(Stdio::null())
.stderr(Stdio::null()); .stderr(Stdio::null());
// Windows: 不显示控制台窗口
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
use std::os::windows::process::CommandExt; use std::os::windows::process::CommandExt;
@@ -119,26 +232,25 @@ pub fn run() {
cmd.creation_flags(CREATE_NO_WINDOW); cmd.creation_flags(CREATE_NO_WINDOW);
} }
cmd.spawn() cmd.spawn().map_err(|e| {
format!("无法启动打包应用 {:?}: {}", exe_path, e)
})
}; };
match child { match child_result {
Ok(child) => { Ok(child) => {
log::info!("Streamlit 进程已启动 (PID: {})", child.id()); log::info!("Streamlit 进程已启动 (PID: {})", child.id());
app.manage(StreamlitProcess(Mutex::new(Some(child)))); let guard = ProcessGuard::new(child);
app.manage(StreamlitProcess(Mutex::new(Some(guard))));
} }
Err(e) => { Err(e) => {
log::error!("启动 Streamlit 失败: {}", e); log::error!("启动 Streamlit 失败: {}", e);
eprintln!( eprintln!("错误:{}", e);
"错误:无法启动 Streamlit 服务。请确保已安装 streamlit。\n {}",
e
);
} }
} }
// 等待 Streamlit 就绪(最多 15 秒)
if !wait_for_port("127.0.0.1", 8501, 15) { if !wait_for_port("127.0.0.1", 8501, 15) {
log::warn!("Streamlit 在 15 秒内未能就绪"); log::warn!("Streamlit 在 15 秒内未能就绪,请检查是否已安装 streamlit");
} else { } else {
log::info!("Streamlit 已就绪 (127.0.0.1:8501)"); log::info!("Streamlit 已就绪 (127.0.0.1:8501)");
} }
@@ -149,19 +261,22 @@ pub fn run() {
.expect("error while building tauri application"); .expect("error while building tauri application");
app.run(|app_handle, event| { app.run(|app_handle, event| {
// 窗口关闭 → 停止子进程 → 退出应用 match event {
if let tauri::RunEvent::ExitRequested { .. } = event { tauri::RunEvent::WindowEvent { event, .. } => {
if let tauri::WindowEvent::CloseRequested { .. } = event {
log::info!("窗口关闭请求,正在清理子进程...");
if let Some(state) = app_handle.try_state::<StreamlitProcess>() { if let Some(state) = app_handle.try_state::<StreamlitProcess>() {
if let Ok(mut guard) = state.0.lock() { cleanup_streamlit(&*state);
if let Some(ref mut child) = *guard {
log::info!("正在停止 Streamlit 进程...");
kill_streamlit(child);
log::info!("Streamlit 进程已停止");
} }
} }
} }
// 确保应用进程完全退出 tauri::RunEvent::Exit => {
std::process::exit(0); log::info!("应用退出,执行最终清理...");
if let Some(state) = app_handle.try_state::<StreamlitProcess>() {
cleanup_streamlit(&*state);
}
}
_ => {}
} }
}); });
} }
+8 -4
View File
@@ -4,8 +4,7 @@
"version": "0.1.0", "version": "0.1.0",
"identifier": "com.pinesound.geo-tool", "identifier": "com.pinesound.geo-tool",
"build": { "build": {
"frontendDist": "../frontend", "frontendDist": "../frontend"
"devUrl": "http://localhost:8501"
}, },
"app": { "app": {
"windows": [ "windows": [
@@ -35,7 +34,12 @@
"icons/icon.ico" "icons/icon.ico"
], ],
"resources": [ "resources": [
"../dist_python/geo_tool_app" "../dist_python/geo_tool_app.exe"
] ],
"windows": {
"wix": {
"language": "zh-CN"
}
}
} }
} }