mioku-service-webui 4.0.5 → 4.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.ts +2 -1
  2. package/package.json +3 -1
  3. package/system.ts +56 -12
package/index.ts CHANGED
@@ -8,7 +8,7 @@ import { createNodeWebSocket } from "@hono/node-ws";
8
8
  import type { MiokuService } from "mioku";
9
9
  import type { WebUISettings } from "./types";
10
10
  import { ensureAuthConfig, loginWithToken, requireAuth } from "./auth";
11
- import { getWebUISettings, getSystemOverview, getSaying } from "./system";
11
+ import { ensureWebUIDist, getWebUISettings, getSystemOverview, getSaying } from "./system";
12
12
  import { CHAT_CONFIG_DIR, CHAT_DATA_DIR, LOGS_DIR, WEBUI_DIST } from "./utils";
13
13
  import { initBuiltinDatasources } from "./datasources";
14
14
  import {
@@ -220,6 +220,7 @@ const webUIService: MiokuService = {
220
220
  async init() {
221
221
  initBuiltinDatasources();
222
222
  ensureAuthConfig();
223
+ await ensureWebUIDist();
223
224
  runtime.initRoutes();
224
225
  },
225
226
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-service-webui",
3
- "version": "4.0.5",
3
+ "version": "4.0.7",
4
4
  "description": "Mioku WebUI 服务",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -18,6 +18,8 @@
18
18
  "dependencies": {
19
19
  "@hono/node-server": "1.19.13",
20
20
  "@hono/node-ws": "^1.2.0",
21
+ "@types/adm-zip": "^0.5.8",
22
+ "adm-zip": "^0.5.16",
21
23
  "fflate": "^0.8.2",
22
24
  "gray-matter": "^4.0.3",
23
25
  "hono": "4.12.14"
package/system.ts CHANGED
@@ -2,7 +2,8 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import process from "node:process";
5
- import { connectedBots, systemInfo } from "mioki";
5
+ import AdmZip from "adm-zip";
6
+ import { connectedBots, logger, systemInfo } from "mioki";
6
7
  import type {
7
8
  InstallRequest,
8
9
  ManagedTarget,
@@ -701,6 +702,7 @@ interface WebUIUpdateCheckResult {
701
702
  assetUrl: string;
702
703
  checkedAt: number;
703
704
  error?: string;
705
+ proxiedAssetUrl?: string;
704
706
  }
705
707
 
706
708
  interface MiokuUpdateCheckResult {
@@ -1568,6 +1570,9 @@ async function fetchLatestWebUIUpdate(
1568
1570
  hasUpdates && !distAsset
1569
1571
  ? "已检测到新版本,但 Release 没有可用 dist 压缩包"
1570
1572
  : "",
1573
+ proxiedAssetUrl: distAsset
1574
+ ? `https://gh-proxy.org/${encodeURIComponent(distAsset.browser_download_url)}`
1575
+ : "",
1571
1576
  };
1572
1577
  })();
1573
1578
 
@@ -1793,6 +1798,9 @@ export async function updateWebUIDistFromRelease(): Promise<
1793
1798
  );
1794
1799
  }
1795
1800
 
1801
+ let lastLogTime = 0;
1802
+ const LOG_INTERVAL_MS = 2000;
1803
+ const downloadUrl = new URL(check.assetUrl);
1796
1804
  const tempDir = path.join(
1797
1805
  os.tmpdir(),
1798
1806
  `mioku-webui-update-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -1805,7 +1813,7 @@ export async function updateWebUIDistFromRelease(): Promise<
1805
1813
 
1806
1814
  let backupPath = "";
1807
1815
  try {
1808
- const downloadRes = await fetch(check.assetUrl, {
1816
+ const downloadRes = await fetch(downloadUrl, {
1809
1817
  headers: {
1810
1818
  Accept: "application/octet-stream",
1811
1819
  "User-Agent": "mioku-webui-updater",
@@ -1816,20 +1824,46 @@ export async function updateWebUIDistFromRelease(): Promise<
1816
1824
  throw new Error(`下载 dist 压缩包失败: HTTP_${downloadRes.status}`);
1817
1825
  }
1818
1826
 
1819
- const buffer = Buffer.from(await downloadRes.arrayBuffer());
1820
- fs.writeFileSync(zipPath, buffer);
1827
+ const contentLength = Number(downloadRes.headers.get("content-length") || 0);
1828
+ const reader = downloadRes.body?.getReader();
1829
+ if (!reader) {
1830
+ throw new Error("无法读取下载流");
1831
+ }
1821
1832
 
1822
- await ensureUnzipCommand();
1833
+ const chunks: Buffer[] = [];
1834
+ let receivedBytes = 0;
1823
1835
 
1824
- const unzip = await runCommand(
1825
- "unzip",
1826
- ["-oq", zipPath, "-d", unpackDir],
1827
- process.cwd(),
1828
- );
1829
- if (unzip.code !== 0) {
1830
- throw new Error(`解压失败: ${unzip.stderr || unzip.stdout}`);
1836
+ while (true) {
1837
+ const { done, value } = await reader.read();
1838
+ if (done) break;
1839
+
1840
+ if (value) {
1841
+ chunks.push(Buffer.from(value));
1842
+ receivedBytes += value.length;
1843
+
1844
+ if (contentLength > 0) {
1845
+ const now = Date.now();
1846
+ if (now - lastLogTime >= LOG_INTERVAL_MS) {
1847
+ const progress = Math.round((receivedBytes / contentLength) * 100);
1848
+ logger.info(`[webui] 正在下载 WebUI dist... ${receivedBytes}/${contentLength} bytes (${progress}%)`);
1849
+ lastLogTime = now;
1850
+ }
1851
+ }
1852
+ }
1853
+ }
1854
+
1855
+ if (contentLength > 0 && receivedBytes !== contentLength) {
1856
+ logger.warn(`[webui] 下载字节数不匹配: 预期 ${contentLength}, 实际 ${receivedBytes}`);
1831
1857
  }
1832
1858
 
1859
+ logger.info(`[webui] 下载完成,正在解压...`);
1860
+
1861
+ const buffer = Buffer.concat(chunks);
1862
+ fs.writeFileSync(zipPath, buffer);
1863
+
1864
+ const zip = new AdmZip(zipPath);
1865
+ zip.extractAllTo(unpackDir, true);
1866
+
1833
1867
  const sourceDir = resolveDistSourceDir(unpackDir);
1834
1868
  if (!sourceDir) {
1835
1869
  throw new Error("压缩包内未找到可用的 WebUI dist 文件");
@@ -1893,6 +1927,16 @@ export async function updateWebUIDistFromRelease(): Promise<
1893
1927
  return task;
1894
1928
  }
1895
1929
 
1930
+ export async function ensureWebUIDist(): Promise<void> {
1931
+ // Skip if dist already exists and has usable files
1932
+ if (hasUsableDistFiles(WEBUI_DIST)) {
1933
+ return;
1934
+ }
1935
+ // Dist missing — trigger download
1936
+ logger.info("[webui] WebUI dist 未找到,正在下载...");
1937
+ await updateWebUIDistFromRelease();
1938
+ }
1939
+
1896
1940
  function readPackageVersion(filePath: string): string {
1897
1941
  try {
1898
1942
  if (!fs.existsSync(filePath)) return "unknown";