后台运行和交互优化
This commit is contained in:
Binary file not shown.
@@ -1,9 +1,12 @@
|
|||||||
package sys
|
package sys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -12,6 +15,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -550,20 +555,67 @@ func GenerateAndWriteConfig(opts ConfigOptions) error {
|
|||||||
return os.WriteFile(configFile, data, 0644)
|
return os.WriteFile(configFile, data, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartGateway 在新窗口启动网关
|
// StartGateway 在后台启动网关 (隐藏窗口)
|
||||||
func StartGateway() error {
|
func StartGateway() error {
|
||||||
cmdName, err := GetMoltbotPath()
|
cmdName, err := GetMoltbotPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Fallback if not found (though unlikely if installed)
|
|
||||||
cmdName = "moltbot"
|
cmdName = "moltbot"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 start 命令在新窗口运行
|
// 使用 SysProcAttr 隐藏窗口
|
||||||
// Windows start command: start "Title" "Executable" args...
|
cmd := exec.Command(cmdName, "gateway", "--verbose")
|
||||||
cmd := exec.Command("cmd", "/c", "start", "Moltbot Gateway", cmdName, "gateway", "--verbose")
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
HideWindow: true,
|
||||||
|
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不等待,让其在后台运行
|
||||||
return cmd.Start()
|
return cmd.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsGatewayRunning 检查端口 18789 是否被占用
|
||||||
|
func IsGatewayRunning() bool {
|
||||||
|
conn, err := net.DialTimeout("tcp", "127.0.0.1:18789", 500*time.Millisecond)
|
||||||
|
if err == nil {
|
||||||
|
conn.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// KillGateway 查找占用 18789 端口的进程并强制结束
|
||||||
|
func KillGateway() error {
|
||||||
|
// 1. netstat -ano 查找 PID
|
||||||
|
cmd := exec.Command("netstat", "-ano")
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(bytes.NewReader(out))
|
||||||
|
var pid string
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.Contains(line, ":18789") && strings.Contains(line, "LISTENING") {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) > 0 {
|
||||||
|
pid = fields[len(fields)-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pid == "" {
|
||||||
|
return nil // 未找到,可能已停止
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. taskkill /F /PID <pid>
|
||||||
|
killCmd := exec.Command("taskkill", "/F", "/PID", pid)
|
||||||
|
killCmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||||
|
return killCmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
// UninstallMoltbot 卸载 Moltbot/Clawdbot 并清理配置
|
// UninstallMoltbot 卸载 Moltbot/Clawdbot 并清理配置
|
||||||
func UninstallMoltbot() error {
|
func UninstallMoltbot() error {
|
||||||
npmPath, err := getNpmPath()
|
npmPath, err := getNpmPath()
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const (
|
|||||||
StateMenu
|
StateMenu
|
||||||
StateConfigApiSelect
|
StateConfigApiSelect
|
||||||
StateConfigInput
|
StateConfigInput
|
||||||
|
StateGatewayRunning
|
||||||
StateUninstallConfirm
|
StateUninstallConfirm
|
||||||
StateUninstalling
|
StateUninstalling
|
||||||
StateError
|
StateError
|
||||||
@@ -49,6 +50,9 @@ type Model struct {
|
|||||||
configStep int
|
configStep int
|
||||||
menuIndex int
|
menuIndex int
|
||||||
|
|
||||||
|
nextState AppState
|
||||||
|
nextCmd tea.Cmd
|
||||||
|
|
||||||
DidStartGateway bool
|
DidStartGateway bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +62,7 @@ type checkMsg struct {
|
|||||||
needsNode bool
|
needsNode bool
|
||||||
moltbotVer string
|
moltbotVer string
|
||||||
moltbotInstalled bool
|
moltbotInstalled bool
|
||||||
|
gatewayRunning bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type installNodeMsg struct{ err error }
|
type installNodeMsg struct{ err error }
|
||||||
@@ -124,9 +129,13 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
case "enter":
|
case "enter":
|
||||||
switch m.menuIndex {
|
switch m.menuIndex {
|
||||||
case 0: // Start
|
case 0: // Start
|
||||||
|
if sys.IsGatewayRunning() {
|
||||||
|
m.state = StateGatewayRunning
|
||||||
|
} else {
|
||||||
sys.StartGateway()
|
sys.StartGateway()
|
||||||
m.DidStartGateway = true
|
m.DidStartGateway = true
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
|
}
|
||||||
case 1: // Configure
|
case 1: // Configure
|
||||||
m.state = StateConfigApiSelect
|
m.state = StateConfigApiSelect
|
||||||
m.configOpts = sys.ConfigOptions{}
|
m.configOpts = sys.ConfigOptions{}
|
||||||
@@ -238,6 +247,33 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, cmd
|
return m, cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gateway Running Conflict State
|
||||||
|
if m.state == StateGatewayRunning {
|
||||||
|
proceed := false
|
||||||
|
switch msg.String() {
|
||||||
|
case "y", "Y":
|
||||||
|
sys.KillGateway()
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
sys.StartGateway()
|
||||||
|
proceed = true
|
||||||
|
case "n", "N", "esc", "enter":
|
||||||
|
proceed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if proceed {
|
||||||
|
m.state = m.nextState
|
||||||
|
if m.nextState == StateInstallingNode {
|
||||||
|
m.logs = append(m.logs, style.RenderStep("➜", "正在安装 Node.js (可能需要管理员权限)...", "running"))
|
||||||
|
return m, m.nextCmd
|
||||||
|
} else if m.nextState == StateConfiguringNpm {
|
||||||
|
m.logs = append(m.logs, style.RenderStep("➜", "正在配置 npm 淘宝镜像...", "running"))
|
||||||
|
return m, m.nextCmd
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Confirm Install State
|
// Confirm Install State
|
||||||
if m.state == StateConfirmInstall {
|
if m.state == StateConfirmInstall {
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
@@ -288,21 +324,41 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine Next Step
|
||||||
|
var nextState AppState
|
||||||
|
var nextCmd tea.Cmd
|
||||||
|
|
||||||
if msg.moltbotInstalled {
|
if msg.moltbotInstalled {
|
||||||
m.logs = append(m.logs, style.RenderStep("!", fmt.Sprintf("检测到 Moltbot 已安装 (%s)", msg.moltbotVer), "warning"))
|
m.logs = append(m.logs, style.RenderStep("!", fmt.Sprintf("检测到 Moltbot 已安装 (%s)", msg.moltbotVer), "warning"))
|
||||||
m.state = StateConfirmInstall
|
nextState = StateConfirmInstall
|
||||||
|
nextCmd = nil
|
||||||
|
} else if !msg.nodeOk {
|
||||||
|
nextState = StateInstallingNode
|
||||||
|
nextCmd = installNodeCmd
|
||||||
|
} else {
|
||||||
|
nextState = StateConfiguringNpm
|
||||||
|
nextCmd = configNpmCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
m.nextState = nextState
|
||||||
|
m.nextCmd = nextCmd
|
||||||
|
|
||||||
|
// Check Gateway Conflict
|
||||||
|
if msg.gatewayRunning {
|
||||||
|
m.state = StateGatewayRunning
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !msg.nodeOk {
|
// Proceed immediately if no conflict
|
||||||
m.state = StateInstallingNode
|
m.state = nextState
|
||||||
|
if nextState == StateInstallingNode {
|
||||||
m.logs = append(m.logs, style.RenderStep("➜", "正在安装 Node.js (可能需要管理员权限)...", "running"))
|
m.logs = append(m.logs, style.RenderStep("➜", "正在安装 Node.js (可能需要管理员权限)...", "running"))
|
||||||
return m, installNodeCmd
|
return m, nextCmd
|
||||||
}
|
} else if nextState == StateConfiguringNpm {
|
||||||
|
|
||||||
m.state = StateConfiguringNpm
|
|
||||||
m.logs = append(m.logs, style.RenderStep("➜", "正在配置 npm 淘宝镜像...", "running"))
|
m.logs = append(m.logs, style.RenderStep("➜", "正在配置 npm 淘宝镜像...", "running"))
|
||||||
return m, configNpmCmd
|
return m, nextCmd
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
|
||||||
case installNodeMsg:
|
case installNodeMsg:
|
||||||
if msg.err != nil {
|
if msg.err != nil {
|
||||||
@@ -407,6 +463,10 @@ func (m Model) View() string {
|
|||||||
case StateConfirmInstall:
|
case StateConfirmInstall:
|
||||||
s += fmt.Sprintf("\n%s\n", style.SubtleStyle.Render("是否强制重新安装/更新?[y/N]"))
|
s += fmt.Sprintf("\n%s\n", style.SubtleStyle.Render("是否强制重新安装/更新?[y/N]"))
|
||||||
|
|
||||||
|
case StateGatewayRunning:
|
||||||
|
s += fmt.Sprintf("\n%s\n", style.SubtleStyle.Render("检测到 Moltbot 网关已在运行 (端口 18789 被占用)"))
|
||||||
|
s += fmt.Sprintf("\n%s\n", style.SubtleStyle.Render("是否停止旧进程并重新启动?[y/N]"))
|
||||||
|
|
||||||
case StateUninstallConfirm:
|
case StateUninstallConfirm:
|
||||||
s += fmt.Sprintf("\n%s\n", style.SubtleStyle.Render("确定要卸载 Moltbot 吗?(这将删除配置文件) [y/N]"))
|
s += fmt.Sprintf("\n%s\n", style.SubtleStyle.Render("确定要卸载 Moltbot 吗?(这将删除配置文件) [y/N]"))
|
||||||
|
|
||||||
@@ -492,10 +552,11 @@ func checkEnvCmd() tea.Msg {
|
|||||||
nodeOk bool
|
nodeOk bool
|
||||||
moltbotVer string
|
moltbotVer string
|
||||||
moltbotInstalled bool
|
moltbotInstalled bool
|
||||||
|
gatewayRunning bool
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
)
|
)
|
||||||
|
|
||||||
wg.Add(2)
|
wg.Add(3)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
@@ -507,6 +568,11 @@ func checkEnvCmd() tea.Msg {
|
|||||||
moltbotVer, moltbotInstalled = sys.CheckMoltbot()
|
moltbotVer, moltbotInstalled = sys.CheckMoltbot()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
gatewayRunning = sys.IsGatewayRunning()
|
||||||
|
}()
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
return checkMsg{
|
return checkMsg{
|
||||||
@@ -515,6 +581,7 @@ func checkEnvCmd() tea.Msg {
|
|||||||
needsNode: !nodeOk,
|
needsNode: !nodeOk,
|
||||||
moltbotVer: moltbotVer,
|
moltbotVer: moltbotVer,
|
||||||
moltbotInstalled: moltbotInstalled,
|
moltbotInstalled: moltbotInstalled,
|
||||||
|
gatewayRunning: gatewayRunning,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,4 +22,7 @@ func main() {
|
|||||||
fmt.Println("Web Console: http://127.0.0.1:18789/")
|
fmt.Println("Web Console: http://127.0.0.1:18789/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("\n按 Enter 键退出...")
|
||||||
|
fmt.Scanln()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user