Skip to content
Open
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
45 changes: 23 additions & 22 deletions frontends/desktop_pet_v2.pyw
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
from PIL import Image, ImageDraw, ImageFont, ImageOps

PORT = 41983
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = os.path.dirname(SCRIPT_DIR)
SKINS_DIR = os.path.join(SCRIPT_DIR, 'skins')
Expand Down Expand Up @@ -240,7 +239,7 @@ class PetBase:
self._schedule_main(lambda m=message: self.show_toast(m))

def _start_server(self):
"""Start HTTP control server."""
"""Start HTTP control server with dynamic port allocation."""
pet = self

class Handler(BaseHTTPRequestHandler):
Expand Down Expand Up @@ -280,16 +279,29 @@ class PetBase:
def log_message(self, *a):
pass

try:
HTTPServer.allow_reuse_address = True
srv = HTTPServer(('127.0.0.1', PORT), Handler)
# Find an available port starting from 41983
HTTPServer.allow_reuse_address = True
assigned_port = None
for p in range(41983, 42000):
try:
srv = HTTPServer(('127.0.0.1', p), Handler)
assigned_port = p
break
except OSError as e:
if e.errno == 48:
print(f'⚠ Port {p} already in use, try next port')
continue
else:
raise

if assigned_port:
threading.Thread(target=srv.serve_forever, daemon=True).start()
print(f'✓ Server: http://127.0.0.1:{PORT}/?state=walk')
except OSError as e:
if e.errno == 48:
print(f'⚠ Port {PORT} already in use')
else:
raise
print(f'✓ Server: http://127.0.0.1:{assigned_port}/?state=walk')
print(f'__PET_PORT_READY__:{assigned_port}')
sys.stdout.flush()
else:
print('⚠ Failed to bind any port')
raise


# ============================================================================
Expand Down Expand Up @@ -1068,17 +1080,6 @@ else:
self.app.exec()

if __name__ == '__main__':
# Singleton: if port already in use, another instance is running
import socket
_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
_s.connect(('127.0.0.1', PORT))
_s.close()
print(f'⚠ Pet already running on port {PORT}, exiting.')
sys.exit(0)
except ConnectionRefusedError:
pass

if sys.platform == 'darwin':
pet = MacPet('vita')
pet.run()
Expand Down
63 changes: 43 additions & 20 deletions frontends/stapp.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import os, sys, subprocess
import os, sys, subprocess, atexit
from urllib.request import urlopen
from urllib.parse import quote
if sys.stdout is None: sys.stdout = open(os.devnull, "w")
Expand Down Expand Up @@ -71,25 +71,48 @@ def render_sidebar():
st.toast(f"Tools injected")
except Exception as e: st.toast(f"Injected tools failed: {e}")
if st.button(T('desktop_pet')):
kwargs = {'creationflags': 0x08} if sys.platform == 'win32' else {}
pet_script = os.path.join(script_dir, 'desktop_pet_v2.pyw')
if not os.path.exists(pet_script): pet_script = os.path.join(script_dir, 'desktop_pet.pyw')
subprocess.Popen([sys.executable, pet_script], **kwargs)
def _pet_req(q):
def _do():
try: urlopen(f'http://127.0.0.1:41983/?{q}', timeout=2)
except Exception: pass
threading.Thread(target=_do, daemon=True).start()
agent._pet_req = _pet_req
if not hasattr(agent, '_turn_end_hooks'): agent._turn_end_hooks = {}
def _pet_hook(ctx):
parts = [f"Turn {ctx.get('turn','?')}"]
if ctx.get('summary'): parts.append(ctx['summary'])
if ctx.get('exit_reason'): parts.append('DONE')
_pet_req(f'msg={quote(chr(10).join(parts))}')
if ctx.get('exit_reason'): _pet_req('state=idle')
agent._turn_end_hooks['pet'] = _pet_hook
st.toast("Desktop pet started")
if not getattr(agent, '_pet_running', False):
kwargs = {'creationflags': 0x08} if sys.platform == 'win32' else {}
pet_script = os.path.join(script_dir, 'desktop_pet_v2.pyw')
if not os.path.exists(pet_script): pet_script = os.path.join(script_dir, 'desktop_pet.pyw')
proc = subprocess.Popen([sys.executable, pet_script], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs)
atexit.register(proc.kill)

def _listen_port():
if proc.stdout:
for line in iter(proc.stdout.readline, b''):
try:
line_str = line.decode('utf-8', errors='replace').strip()
if line_str.startswith('__PET_PORT_READY__:'):
agent._pet_port = int(line_str.split(':')[1])
break
if '⚠ Failed to bind' in line_str:
break
except: pass
if getattr(agent, '_pet_port', 0) > 0:
proc.wait() # 等待子进程退出
agent._pet_port = 0
agent._pet_running = False # 重置标志,允许重新开启
threading.Thread(target=_listen_port, daemon=True).start()

def _pet_req(q):
def _do():
p = getattr(agent, '_pet_port', 41983)
try: urlopen(f'http://127.0.0.1:{p}/?{q}', timeout=2)
except Exception: pass
threading.Thread(target=_do, daemon=True).start()
agent._pet_req = _pet_req

if not hasattr(agent, '_turn_end_hooks'): agent._turn_end_hooks = {}
def _pet_hook(ctx):
parts = [f"Turn {ctx.get('turn','?')}"]
if ctx.get('summary'): parts.append(ctx['summary'])
if ctx.get('exit_reason'): parts.append('DONE')
_pet_req(f'msg={quote(chr(10).join(parts))}')
if ctx.get('exit_reason'): _pet_req('state=idle')
agent._turn_end_hooks['pet'] = _pet_hook
agent._pet_running = True
st.toast("Desktop pet started")

if LANG == 'zh':
st.divider()
Expand Down