Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/script_chainer/config/script_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ class ScriptConfig:
attach_direction: str = AttachDirection.NONE
no_log_timeout_seconds: int = 0
no_log_max_retries: int = 3
block: bool = True

# 不参与序列化的元数据
idx: int = field(default=0, repr=False, compare=False)
Expand Down
11 changes: 11 additions & 0 deletions src/script_chainer/gui/page/script_edit_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,15 @@ def get_content_widget(self) -> QWidget:
)
content_widget.add_widget(self.run_timeout_seconds_opt)

block_switch_widget, self.block_switch = self._create_switch_option('阻塞')
self.block_opt = MultiPushSettingCard(
icon=FluentIcon.PAUSE,
title='阻塞运行',
content='勾选后启动会等待该脚本运行结束(默认开启);不勾选则在后台非阻塞启动',
btn_list=[block_switch_widget],
)
content_widget.add_widget(self.block_opt)

self.check_done_opt = ComboBoxSettingCard(
icon=FluentIcon.COMPLETED,
title='检查完成方式',
Expand Down Expand Up @@ -257,6 +266,7 @@ def init_by_config(self, config: ScriptConfig):
self.kill_script_after_done_switch.setChecked(config.kill_script_after_done)
self.kill_game_after_done_switch.setChecked(config.kill_game_after_done)
self.script_arguments_opt.setValue(config.script_arguments, emit_signal=False)
self.block_switch.setChecked(config.block)
self.notify_start_switch.setChecked(config.notify_start)
self.notify_done_switch.setChecked(config.notify_done)

Expand Down Expand Up @@ -351,6 +361,7 @@ def get_config_value(self) -> ScriptConfig:
config.kill_script_after_done = self.kill_script_after_done_switch.isChecked()
config.kill_game_after_done = self.kill_game_after_done_switch.isChecked()
config.script_arguments = self.script_arguments_opt.getValue()
config.block = self.block_switch.isChecked()
config.notify_start = self.notify_start_switch.isChecked()
config.notify_done = self.notify_done_switch.isChecked()

Expand Down
4 changes: 3 additions & 1 deletion src/script_chainer/gui/page/script_setting_cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,10 @@ def on_debug_clicked(self) -> None:
command=cmd,
cwd=cwd,
title=f'调试 {display}',
block=self.config.block,
)
show_success(self.window(), '调试运行', f'已在终端启动 {display}')
mode = '阻塞' if self.config.block else '后台非阻塞'
show_success(self.window(), '调试运行', f'已以{mode}模式启动 {display}')
except Exception as e:
show_error(self.window(), '启动失败', str(e))

Expand Down
69 changes: 51 additions & 18 deletions src/script_chainer/gui/page/script_setting_interface.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from PySide6.QtCore import QThread, Signal
from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QVBoxLayout, QWidget
from qfluentwidgets import (
Action,
Expand Down Expand Up @@ -36,9 +37,35 @@
show_warning,
)
from script_chainer.utils.process_utils import launch_in_terminal
from script_chainer.utils.runner_utils import (
build_runner_command,
)
from script_chainer.utils.runner_utils import build_runner_command


class ChainRunner(QThread):
"""后台逐条启动脚本链,每条按自身 block 决定阻塞/非阻塞,结束后经信号汇总结果。"""

chain_finished = Signal(int, list)

def __init__(self, chain_name: str, specs: list[tuple[int, bool, str]], parent=None):
super().__init__(parent)
self.chain_name = chain_name
self.specs = specs

def run(self) -> None:
launched = 0
failed: list[str] = []
for script_index, block, display_name in self.specs:
try:
cmd, cwd = build_runner_command(self.chain_name, script_index)
proc = launch_in_terminal(command=cmd, cwd=cwd, block=block)
except Exception:
failed.append(display_name)
continue
# 阻塞脚本非零退出 → 计入失败;非阻塞 rc 为 wt 的 0,按已启动计
if proc.returncode not in (0, None):
failed.append(display_name)
else:
launched += 1
self.chain_finished.emit(launched, failed)


class ScriptSettingRootInterface(VerticalScrollInterface):
Expand Down Expand Up @@ -201,26 +228,32 @@ def on_import_python_script_clicked(self) -> None:
self.update_chain_display()

def on_run_chain_clicked(self) -> None:
"""拉起独立 runner 运行当前脚本链。"""
"""按配置顺序逐条启动当前脚本链,每条按自身 block 决定阻塞/非阻塞。"""
if self.chosen_config is None or self._runner_launch_in_progress:
return

specs = [
(i, cfg.block, cfg.display_name)
for i, cfg in enumerate(self.chosen_config.script_list)
if cfg.enabled
Comment on lines +235 to +238

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

失败提示应使用始终可用的脚本显示名。

display_name 默认为空,默认脚本启动失败时会得到空白条目。请改用 cfg.script_display_name,以便错误提示包含文件名或回退名称。

-            (i, cfg.block, cfg.display_name)
+            (i, cfg.block, cfg.script_display_name)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
specs = [
(i, cfg.block, cfg.display_name)
for i, cfg in enumerate(self.chosen_config.script_list)
if cfg.enabled
specs = [
(i, cfg.block, cfg.script_display_name)
for i, cfg in enumerate(self.chosen_config.script_list)
if cfg.enabled
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/script_chainer/gui/page/script_setting_interface.py` around lines 235 -
238, Update the specs construction in the relevant script settings method to use
cfg.script_display_name instead of cfg.display_name, ensuring failure messages
always include the script filename or fallback display name while preserving the
existing enabled-script filtering.

]
if not specs:
show_warning(self.window(), '无法运行', '没有已启用的脚本')
return

self._runner_launch_in_progress = True
self.run_chain_btn.setEnabled(False)
chain_name = self.chosen_config.module_name
try:
cmd, cwd = build_runner_command(chain_name)
launch_in_terminal(
command=cmd,
cwd=cwd,
title=f'运行脚本链 {chain_name}',
)
show_success(self.window(), '运行全部', f'已在终端启动脚本链 {chain_name}')
except Exception as e:
show_error(self.window(), '启动失败', str(e))
finally:
self._runner_launch_in_progress = False
self.run_chain_btn.setEnabled(True)
self._chain_runner = ChainRunner(self.chosen_config.module_name, specs, parent=self)
self._chain_runner.chain_finished.connect(self._on_chain_run_finished)
self._chain_runner.start()

def _on_chain_run_finished(self, launched: int, failed: list) -> None:
self._runner_launch_in_progress = False
self.run_chain_btn.setEnabled(True)
if failed:
show_error(self.window(), '运行异常', f'{len(failed)} 个脚本启动失败:{", ".join(failed)}')
else:
show_success(self.window(), '运行全部', f'已启动 {launched} 个脚本')

def update_chain_display(self) -> None:
"""更新脚本链的显示"""
Expand Down
17 changes: 16 additions & 1 deletion src/script_chainer/utils/process_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def launch_in_terminal(
command: list[str],
cwd: str | None = None,
title: str | None = None,
block: bool = False,
) -> subprocess.Popen:
"""在终端窗口中启动命令。

Expand All @@ -49,11 +50,25 @@ def launch_in_terminal(
Args:
command: 命令及参数列表。
cwd: 工作目录。
title: 终端窗口标题。
title: 终端窗口标题(仅非阻塞的 wt 分支使用)。
block: 是否阻塞等待命令真正结束。

Returns:
启动的 Popen 对象。

说明:
- ``block=False``(默认):用 wt 开新标签页/窗口后**立即返回**(非阻塞)。
- ``block=True``:Windows Terminal 开完标签页即退出,无法等待真正的子进程,
故绕过 wt、改用新建控制台窗口(``CREATE_NEW_CONSOLE``)并 ``.wait()``——
既有可见窗口,又能正确阻塞到脚本真正结束。
"""
if block:
# 阻塞:不能用 wt(开完标签页即退、等不到真进程),改用新控制台窗口并等待。
flags = subprocess.CREATE_NEW_CONSOLE if sys.platform == 'win32' else 0
proc = subprocess.Popen(command, cwd=cwd, creationflags=flags)
proc.wait()
return proc

wt_path = _find_windows_terminal()
if wt_path is not None:
wt_cmd = [wt_path]
Expand Down