onw 0.8.5: after an update the onw window reopens on the new version
Browse files- CHANGELOG.md +4 -0
- CHANGELOG_en.md +4 -0
- onw/__init__.py +1 -1
- onw/__main__.py +2 -1
- onw/app.py +19 -4
- onw/relaunch.py +4 -3
- onw/tray.py +7 -4
- pyproject.toml +1 -1
CHANGELOG.md
CHANGED
|
@@ -5,6 +5,10 @@
|
|
| 5 |
日付は日本時間です。各版は Hugging Face の `ryugyosoft/onw` にタグ(`v0.4.2` 以降)付きで残っています。
|
| 6 |
onw ウィンドウは、新しい版があるとこのファイルから変更点を表示します(`## v…` の見出しで区切っています)。
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
## v0.8.4 (2026-09-27)
|
| 9 |
- 画面を英語・中国語(簡体字)にも対応。OS の言語に合わせて自動で選び、「設定」の「言語」で切り替えられます
|
| 10 |
(onw ウィンドウ、トレイのメニューと通知、チャット画面、検証ページ)。
|
|
|
|
| 5 |
日付は日本時間です。各版は Hugging Face の `ryugyosoft/onw` にタグ(`v0.4.2` 以降)付きで残っています。
|
| 6 |
onw ウィンドウは、新しい版があるとこのファイルから変更点を表示します(`## v…` の見出しで区切っています)。
|
| 7 |
|
| 8 |
+
## v0.8.5 (2026-09-27)
|
| 9 |
+
- 更新後、開いていた onw ウィンドウも新しい版で開き直すように(これまではページだけ読み直していたため、タイトルの版表示や
|
| 10 |
+
アイコンが古いままでした)。開いていたタブはそのまま。更新で再起動したトレイも、新しい版のウィンドウを開きます。
|
| 11 |
+
|
| 12 |
## v0.8.4 (2026-09-27)
|
| 13 |
- 画面を英語・中国語(簡体字)にも対応。OS の言語に合わせて自動で選び、「設定」の「言語」で切り替えられます
|
| 14 |
(onw ウィンドウ、トレイのメニューと通知、チャット画面、検証ページ)。
|
CHANGELOG_en.md
CHANGED
|
@@ -4,6 +4,10 @@
|
|
| 4 |
|
| 5 |
Dates are JST. Every version is on Hugging Face `ryugyosoft/onw`, tagged from `v0.4.2` on.
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
## v0.8.4 (2026-09-27)
|
| 8 |
- The UI speaks English and Simplified Chinese too: it follows the OS language, Settings → Language switches it
|
| 9 |
(onw window, tray menu and notifications, chat page, check page).
|
|
|
|
| 4 |
|
| 5 |
Dates are JST. Every version is on Hugging Face `ryugyosoft/onw`, tagged from `v0.4.2` on.
|
| 6 |
|
| 7 |
+
## v0.8.5 (2026-09-27)
|
| 8 |
+
- After an update an open onw window reopens on the new version too (it used to reload only the page, so its title and
|
| 9 |
+
icon stayed old), on the same tab. The tray restarted by an update opens a window on the new version as well.
|
| 10 |
+
|
| 11 |
## v0.8.4 (2026-09-27)
|
| 12 |
- The UI speaks English and Simplified Chinese too: it follows the OS language, Settings → Language switches it
|
| 13 |
(onw window, tray menu and notifications, chat page, check page).
|
onw/__init__.py
CHANGED
|
@@ -2,4 +2,4 @@
|
|
| 2 |
|
| 3 |
A small engine that runs LLMs entirely on the Intel NPU: static segment graphs + host-managed state.
|
| 4 |
"""
|
| 5 |
-
__version__ = "0.8.
|
|
|
|
| 2 |
|
| 3 |
A small engine that runs LLMs entirely on the Intel NPU: static segment graphs + host-managed state.
|
| 4 |
"""
|
| 5 |
+
__version__ = "0.8.5"
|
onw/__main__.py
CHANGED
|
@@ -63,7 +63,8 @@ def main():
|
|
| 63 |
convert(argv)
|
| 64 |
elif cmd == "window":
|
| 65 |
from onw.app import window
|
| 66 |
-
window(argv[argv.index("--tab") + 1] if "--tab" in argv else "server"
|
|
|
|
| 67 |
elif cmd == "tray":
|
| 68 |
from onw.tray import main as tray
|
| 69 |
tray(argv)
|
|
|
|
| 63 |
convert(argv)
|
| 64 |
elif cmd == "window":
|
| 65 |
from onw.app import window
|
| 66 |
+
window(argv[argv.index("--tab") + 1] if "--tab" in argv else "server",
|
| 67 |
+
argv[argv.index("--updated") + 1] if "--updated" in argv else None)
|
| 68 |
elif cmd == "tray":
|
| 69 |
from onw.tray import main as tray
|
| 70 |
tray(argv)
|
onw/app.py
CHANGED
|
@@ -365,9 +365,10 @@ def open_path(p):
|
|
| 365 |
|
| 366 |
# ------------------------------------------------------------------ window
|
| 367 |
|
| 368 |
-
def window(tab="server"):
|
| 369 |
"""The onw window: onw/web/manage.html in a small native web view (pywebview), talking to the manager (the tray
|
| 370 |
-
process; started here when no tray is running). Without pywebview the same page opens in the browser.
|
|
|
|
| 371 |
if not single_instance("window"):
|
| 372 |
raise_window()
|
| 373 |
return
|
|
@@ -377,6 +378,8 @@ def window(tab="server"):
|
|
| 377 |
if own:
|
| 378 |
info = mgr.serve()
|
| 379 |
url = mgr.page_url(info, tab)
|
|
|
|
|
|
|
| 380 |
try:
|
| 381 |
import webview
|
| 382 |
except ImportError:
|
|
@@ -398,7 +401,8 @@ def window(tab="server"):
|
|
| 398 |
win = webview.create_window(TITLE, url, js_api=Api(), width=780, height=460, min_size=(420, 320))
|
| 399 |
|
| 400 |
def follow():
|
| 401 |
-
"""The tray
|
|
|
|
| 402 |
cur = info
|
| 403 |
while True:
|
| 404 |
time.sleep(2)
|
|
@@ -410,7 +414,18 @@ def window(tab="server"):
|
|
| 410 |
timeout=5).read())["version"]
|
| 411 |
except Exception:
|
| 412 |
v = ""
|
| 413 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
if not own:
|
| 415 |
threading.Thread(target=follow, daemon=True).start()
|
| 416 |
webview.start(icon=icon_path())
|
|
|
|
| 365 |
|
| 366 |
# ------------------------------------------------------------------ window
|
| 367 |
|
| 368 |
+
def window(tab="server", updated=None):
|
| 369 |
"""The onw window: onw/web/manage.html in a small native web view (pywebview), talking to the manager (the tray
|
| 370 |
+
process; started here when no tray is running). Without pywebview the same page opens in the browser.
|
| 371 |
+
`updated`: the version just installed (the page says so once)."""
|
| 372 |
if not single_instance("window"):
|
| 373 |
raise_window()
|
| 374 |
return
|
|
|
|
| 378 |
if own:
|
| 379 |
info = mgr.serve()
|
| 380 |
url = mgr.page_url(info, tab)
|
| 381 |
+
if updated:
|
| 382 |
+
url = url.replace("?token=", f"?updated={updated}&token=")
|
| 383 |
try:
|
| 384 |
import webview
|
| 385 |
except ImportError:
|
|
|
|
| 401 |
win = webview.create_window(TITLE, url, js_api=Api(), width=780, height=460, min_size=(420, 320))
|
| 402 |
|
| 403 |
def follow():
|
| 404 |
+
"""The tray restarted (new manager: new port and token). After an update this window's own code is old too:
|
| 405 |
+
close it and open a fresh window on the new code; otherwise just load the page from the new manager."""
|
| 406 |
cur = info
|
| 407 |
while True:
|
| 408 |
time.sleep(2)
|
|
|
|
| 414 |
timeout=5).read())["version"]
|
| 415 |
except Exception:
|
| 416 |
v = ""
|
| 417 |
+
if v and v != __version__:
|
| 418 |
+
try:
|
| 419 |
+
page = win.evaluate_js("location.hash.slice(1)") or "server"
|
| 420 |
+
except Exception:
|
| 421 |
+
page = "server"
|
| 422 |
+
subprocess.Popen([sys.executable, "-m", "onw.relaunch", str(os.getpid()), "window", "--tab", page,
|
| 423 |
+
"--updated", v], cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
| 424 |
+
**({"creationflags": subprocess.CREATE_NO_WINDOW | subprocess.DETACHED_PROCESS}
|
| 425 |
+
if os.name == "nt" else {"start_new_session": True}))
|
| 426 |
+
win.destroy() # the new window takes the single-instance lock once we exit
|
| 427 |
+
return
|
| 428 |
+
win.load_url(mgr.page_url(m, tab))
|
| 429 |
if not own:
|
| 430 |
threading.Thread(target=follow, daemon=True).start()
|
| 431 |
webview.start(icon=icon_path())
|
onw/relaunch.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
| 1 |
-
"""Start
|
| 2 |
-
usage (internal): python -m onw.relaunch OLD_PID"""
|
| 3 |
import os, subprocess, sys, time
|
| 4 |
|
| 5 |
|
| 6 |
def main():
|
| 7 |
pid = int(sys.argv[1])
|
|
|
|
| 8 |
try:
|
| 9 |
import psutil
|
| 10 |
deadline = time.time() + 60
|
|
@@ -17,7 +18,7 @@ def main():
|
|
| 17 |
exe = exe[:-10] + "pythonw.exe"
|
| 18 |
kw = ({"creationflags": subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP} if os.name == "nt"
|
| 19 |
else {"start_new_session": True})
|
| 20 |
-
subprocess.Popen([exe, "-m", "onw",
|
| 21 |
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, **kw)
|
| 22 |
|
| 23 |
|
|
|
|
| 1 |
+
"""Start onw again once an old process has exited (after an update: the tray, or an open onw window).
|
| 2 |
+
usage (internal): python -m onw.relaunch OLD_PID [onw arguments, default: tray]"""
|
| 3 |
import os, subprocess, sys, time
|
| 4 |
|
| 5 |
|
| 6 |
def main():
|
| 7 |
pid = int(sys.argv[1])
|
| 8 |
+
args = sys.argv[2:] or ["tray"]
|
| 9 |
try:
|
| 10 |
import psutil
|
| 11 |
deadline = time.time() + 60
|
|
|
|
| 18 |
exe = exe[:-10] + "pythonw.exe"
|
| 19 |
kw = ({"creationflags": subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP} if os.name == "nt"
|
| 20 |
else {"start_new_session": True})
|
| 21 |
+
subprocess.Popen([exe, "-m", "onw", *args], cwd=os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
| 22 |
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, **kw)
|
| 23 |
|
| 24 |
|
onw/tray.py
CHANGED
|
@@ -9,14 +9,14 @@ going while the window is closed.
|
|
| 9 |
"""
|
| 10 |
import json, os, subprocess, sys, threading, time, webbrowser
|
| 11 |
|
| 12 |
-
from . import app
|
| 13 |
|
| 14 |
|
| 15 |
-
def open_window(tab="server"):
|
| 16 |
exe = sys.executable
|
| 17 |
if os.name == "nt" and exe.lower().endswith("python.exe"):
|
| 18 |
exe = exe[:-10] + "pythonw.exe"
|
| 19 |
-
subprocess.Popen([exe, "-m", "onw", "window", "--tab", tab])
|
| 20 |
|
| 21 |
|
| 22 |
def icon_image(state):
|
|
@@ -133,7 +133,8 @@ def main(argv=None):
|
|
| 133 |
c = cfg()
|
| 134 |
resume = os.path.join(app.config_dir(), "resume.json")
|
| 135 |
resumed = None
|
| 136 |
-
|
|
|
|
| 137 |
try:
|
| 138 |
resumed = json.load(open(resume)).get("model")
|
| 139 |
except (OSError, ValueError):
|
|
@@ -147,6 +148,8 @@ def main(argv=None):
|
|
| 147 |
app.start_server(c)
|
| 148 |
elif c["autostart_server"] and state["s"] == "stopped":
|
| 149 |
start()
|
|
|
|
|
|
|
| 150 |
threading.Thread(target=refresh, daemon=True).start()
|
| 151 |
threading.Thread(target=app.refresh_shortcut, daemon=True).start()
|
| 152 |
icon.run()
|
|
|
|
| 9 |
"""
|
| 10 |
import json, os, subprocess, sys, threading, time, webbrowser
|
| 11 |
|
| 12 |
+
from . import app, __version__
|
| 13 |
|
| 14 |
|
| 15 |
+
def open_window(tab="server", updated=None):
|
| 16 |
exe = sys.executable
|
| 17 |
if os.name == "nt" and exe.lower().endswith("python.exe"):
|
| 18 |
exe = exe[:-10] + "pythonw.exe"
|
| 19 |
+
subprocess.Popen([exe, "-m", "onw", "window", "--tab", tab] + (["--updated", updated] if updated else []))
|
| 20 |
|
| 21 |
|
| 22 |
def icon_image(state):
|
|
|
|
| 133 |
c = cfg()
|
| 134 |
resume = os.path.join(app.config_dir(), "resume.json")
|
| 135 |
resumed = None
|
| 136 |
+
after_update = os.path.exists(resume)
|
| 137 |
+
if after_update: # just restarted by an update: reload that model
|
| 138 |
try:
|
| 139 |
resumed = json.load(open(resume)).get("model")
|
| 140 |
except (OSError, ValueError):
|
|
|
|
| 148 |
app.start_server(c)
|
| 149 |
elif c["autostart_server"] and state["s"] == "stopped":
|
| 150 |
start()
|
| 151 |
+
if after_update and app.installed(c): # show the new version (an open window reopens itself
|
| 152 |
+
open_window("server", updated=__version__) # too; the single-instance lock keeps just one)
|
| 153 |
threading.Thread(target=refresh, daemon=True).start()
|
| 154 |
threading.Thread(target=app.refresh_shortcut, daemon=True).start()
|
| 155 |
icon.run()
|
pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "onw"
|
| 7 |
-
version = "0.8.
|
| 8 |
description = "Ore no NPU ga konna ni ugoku wake nai - LLMs entirely on the Intel NPU"
|
| 9 |
readme = "README.md"
|
| 10 |
license = {text = "Apache-2.0"}
|
|
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "onw"
|
| 7 |
+
version = "0.8.5"
|
| 8 |
description = "Ore no NPU ga konna ni ugoku wake nai - LLMs entirely on the Intel NPU"
|
| 9 |
readme = "README.md"
|
| 10 |
license = {text = "Apache-2.0"}
|