Logankunfall commited on
Commit
596d793
·
verified ·
1 Parent(s): 01558be

Upload 19 files

Browse files
Files changed (4) hide show
  1. frontend/assets/app.js +35 -0
  2. frontend/index.html +13 -13
  3. package.json +3 -6
  4. server.js +17 -35
frontend/assets/app.js CHANGED
@@ -4,6 +4,21 @@
4
  const $ = (sel, root = document) => root.querySelector(sel);
5
  const $$ = (sel, root = document) => [...root.querySelectorAll(sel)];
6
  const byId = (id) => document.getElementById(id);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  // 提示音状态(运行时),默认关闭,URL 指向 /ring/ring.mp3
8
  let soundEnabled = false;
9
  let soundUrl = "/ring/ring.mp3";
@@ -129,6 +144,15 @@
129
  if (byId("cfg-custom-primary")) byId("cfg-custom-primary").value = cfg.custom_primary ?? "#7ba23f";
130
  applyAccentFromConfig(cfg);
131
 
 
 
 
 
 
 
 
 
 
132
  // 提示音配置
133
  soundEnabled = !!cfg.sound_enabled;
134
  soundUrl = cfg.sound_url || "/ring/ring.mp3";
@@ -511,6 +535,10 @@
511
  const selOutBtn = byId("btn-select-output-dir");
512
  if (selOutBtn) {
513
  selOutBtn.addEventListener("click", async () => {
 
 
 
 
514
  try {
515
  loading.show();
516
  const res = await fetch("/api/select-output-dir");
@@ -542,6 +570,10 @@
542
  const openOutBtn = byId("btn-open-output-dir");
543
  if (openOutBtn) {
544
  openOutBtn.addEventListener("click", async () => {
 
 
 
 
545
  try {
546
  loading.show();
547
  const p = byId("cfg-output-dir")?.value || "";
@@ -578,5 +610,8 @@
578
  byId("btn-inpaint").addEventListener("click", handleInpaint);
579
 
580
  // Init
 
 
 
581
  loadConfig();
582
  })();
 
4
  const $ = (sel, root = document) => root.querySelector(sel);
5
  const $$ = (sel, root = document) => [...root.querySelectorAll(sel)];
6
  const byId = (id) => document.getElementById(id);
7
+ // HF 环境识别(用于禁用不支持的本地能力)
8
+ const onHF = typeof location !== "undefined" && /\bhf\.space$/i.test(location.hostname);
9
+ function hideById(id) {
10
+ const el = byId(id);
11
+ if (el) {
12
+ el.style.display = "none";
13
+ if ("disabled" in el) el.disabled = true;
14
+ }
15
+ }
16
+ function hideLabelForInput(id) {
17
+ const el = byId(id);
18
+ const label = el && el.closest ? el.closest("label") : null;
19
+ if (label) label.style.display = "none";
20
+ }
21
+
22
  // 提示音状态(运行时),默认关闭,URL 指向 /ring/ring.mp3
23
  let soundEnabled = false;
24
  let soundUrl = "/ring/ring.mp3";
 
144
  if (byId("cfg-custom-primary")) byId("cfg-custom-primary").value = cfg.custom_primary ?? "#7ba23f";
145
  applyAccentFromConfig(cfg);
146
 
147
+ // HF 环境不支持选择/打开本地目录,端口也由平台分配,不可编辑
148
+ if (onHF) {
149
+ hideLabelForInput("cfg-port");
150
+ hideById("btn-select-output-dir");
151
+ hideById("btn-open-output-dir");
152
+ const msg = byId("cfg-message");
153
+ if (msg) msg.textContent = "HF 环境:端口由平台分配(已忽略本地端口设置),目录选择/打开功能已禁用。";
154
+ }
155
+
156
  // 提示音配置
157
  soundEnabled = !!cfg.sound_enabled;
158
  soundUrl = cfg.sound_url || "/ring/ring.mp3";
 
535
  const selOutBtn = byId("btn-select-output-dir");
536
  if (selOutBtn) {
537
  selOutBtn.addEventListener("click", async () => {
538
+ if (onHF) {
539
+ toast("HF 环境不支持系统目录选择,请在输入框手填或留空使用默认。", "info");
540
+ return;
541
+ }
542
  try {
543
  loading.show();
544
  const res = await fetch("/api/select-output-dir");
 
570
  const openOutBtn = byId("btn-open-output-dir");
571
  if (openOutBtn) {
572
  openOutBtn.addEventListener("click", async () => {
573
+ if (onHF) {
574
+ toast("HF 环境不支持打开系统目录(不影响生成与下载)。", "info");
575
+ return;
576
+ }
577
  try {
578
  loading.show();
579
  const p = byId("cfg-output-dir")?.value || "";
 
610
  byId("btn-inpaint").addEventListener("click", handleInpaint);
611
 
612
  // Init
613
+ if (onHF) {
614
+ toast("HF 环境:端口由平台分配,目录选择/打开已禁用。", "info");
615
+ }
616
  loadConfig();
617
  })();
frontend/index.html CHANGED
@@ -10,14 +10,14 @@
10
  <header class="app-header">
11
  <div class="brand">New NAI</div>
12
  <nav class="tabs">
13
- <button class="tab-btn active" data-tab="tab-config">配置</button>
14
- <button class="tab-btn" data-tab="tab-t2i">文生图</button>
15
- <button class="tab-btn" data-tab="tab-i2i">图生图</button>
16
- <button class="tab-btn" data-tab="tab-inpaint">局部重绘</button>
17
  </nav>
18
  <div class="header-actions">
19
- <button id="theme-toggle" class="tab-btn">主题:深色</button>
20
- <button id="sound-toggle" class="tab-btn" aria-pressed="false">提示音:关</button>
21
  </div>
22
  </header>
23
 
@@ -114,10 +114,10 @@
114
  </label>
115
  </div>
116
  <div class="actions">
117
- <button id="btn-load-config">读取配置</button>
118
- <button id="btn-select-output-dir">选择保存目录</button>
119
- <button id="btn-open-output-dir">打开保存目录</button>
120
- <button id="btn-save-config" class="primary">保存配置</button>
121
  </div>
122
  <p id="cfg-message" class="message"></p>
123
  </div>
@@ -193,7 +193,7 @@
193
  </label>
194
  </div>
195
  <div class="actions">
196
- <button id="btn-t2i" class="primary">生成</button>
197
  </div>
198
  <div class="result">
199
  <img id="t2i-img" alt="生成结果" />
@@ -287,7 +287,7 @@
287
  </label>
288
  </div>
289
  <div class="actions">
290
- <button id="btn-i2i" class="primary">生成</button>
291
  </div>
292
  <div class="result">
293
  <img id="i2i-img" alt="生成结果" />
@@ -389,7 +389,7 @@
389
  </label>
390
  </div>
391
  <div class="actions">
392
- <button id="btn-inpaint" class="primary">生成</button>
393
  </div>
394
  <div class="result">
395
  <img id="inpaint-img" alt="生成结果" />
 
10
  <header class="app-header">
11
  <div class="brand">New NAI</div>
12
  <nav class="tabs">
13
+ <button type="button" class="tab-btn active" data-tab="tab-config">配置</button>
14
+ <button type="button" class="tab-btn" data-tab="tab-t2i">文生图</button>
15
+ <button type="button" class="tab-btn" data-tab="tab-i2i">图生图</button>
16
+ <button type="button" class="tab-btn" data-tab="tab-inpaint">局部重绘</button>
17
  </nav>
18
  <div class="header-actions">
19
+ <button type="button" id="theme-toggle" class="tab-btn">主题:深色</button>
20
+ <button type="button" id="sound-toggle" class="tab-btn" aria-pressed="false">提示音:关</button>
21
  </div>
22
  </header>
23
 
 
114
  </label>
115
  </div>
116
  <div class="actions">
117
+ <button type="button" id="btn-load-config">读取配置</button>
118
+ <button type="button" id="btn-select-output-dir">选择保存目录</button>
119
+ <button type="button" id="btn-open-output-dir">打开保存目录</button>
120
+ <button type="button" id="btn-save-config" class="primary">保存配置</button>
121
  </div>
122
  <p id="cfg-message" class="message"></p>
123
  </div>
 
193
  </label>
194
  </div>
195
  <div class="actions">
196
+ <button type="button" id="btn-t2i" class="primary">生成</button>
197
  </div>
198
  <div class="result">
199
  <img id="t2i-img" alt="生成结果" />
 
287
  </label>
288
  </div>
289
  <div class="actions">
290
+ <button type="button" id="btn-i2i" class="primary">生成</button>
291
  </div>
292
  <div class="result">
293
  <img id="i2i-img" alt="生成结果" />
 
389
  </label>
390
  </div>
391
  <div class="actions">
392
+ <button type="button" id="btn-inpaint" class="primary">生成</button>
393
  </div>
394
  <div class="result">
395
  <img id="inpaint-img" alt="生成结果" />
package.json CHANGED
@@ -1,11 +1,11 @@
1
  {
2
- "name": "new-nai-node",
3
  "version": "1.0.0",
4
  "private": true,
 
5
  "main": "server.js",
6
  "scripts": {
7
- "start": "node server.js",
8
- "dev": "nodemon --watch ./ --ext js,json --ignore output --ignore \"Semi-Auto-NovelAI-to-Pixiv\" server.js"
9
  },
10
  "engines": {
11
  "node": ">=18.0.0"
@@ -19,8 +19,5 @@
19
  "axios": "^1.7.2",
20
  "adm-zip": "^0.5.10",
21
  "fs-extra": "^11.2.0"
22
- },
23
- "devDependencies": {
24
- "nodemon": "^3.1.0"
25
  }
26
  }
 
1
  {
2
+ "name": "new-nai-node-hf",
3
  "version": "1.0.0",
4
  "private": true,
5
+ "description": "New NAI - Node/Express for Hugging Face Spaces (Docker)",
6
  "main": "server.js",
7
  "scripts": {
8
+ "start": "node server.js"
 
9
  },
10
  "engines": {
11
  "node": ">=18.0.0"
 
19
  "axios": "^1.7.2",
20
  "adm-zip": "^0.5.10",
21
  "fs-extra": "^11.2.0"
 
 
 
22
  }
23
  }
server.js CHANGED
@@ -90,28 +90,30 @@ const DEFAULT_CONFIG = {
90
  sound_enabled: false,
91
  sound_url: "/ring/ring.mp3"
92
  };
 
93
 
94
  function readConfig() {
 
 
95
  try {
96
  if (fs.existsSync(CONFIG_PATH)) {
97
  const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
98
  const fileCfg = JSON.parse(raw || '{}');
99
- // 合并默认值,确保前端读取字段齐全
100
- return { ...DEFAULT_CONFIG, ...fileCfg };
101
  }
102
  } catch (e) {
103
  // ignore and fallback
104
  }
105
- // 若不存在则写入默认文件
106
- writeConfig(DEFAULT_CONFIG);
107
- return { ...DEFAULT_CONFIG };
108
  }
109
 
110
  function writeConfig(cfg) {
 
111
  const merged = { ...DEFAULT_CONFIG, ...(cfg || {}) };
112
- fse.ensureDirSync(path.dirname(CONFIG_PATH));
113
- fs.writeFileSync(CONFIG_PATH, JSON.stringify(merged, null, 2), 'utf-8');
114
- return merged;
115
  }
116
 
117
  // ---------- 工具函数:系统命令 ----------
@@ -247,37 +249,14 @@ app.put('/api/config', (req, res) => {
247
 
248
  // 选择输出目录(桌面环境)
249
  app.get('/api/select-output-dir', async (req, res) => {
250
- try {
251
- const p = await pickDirectory();
252
- if (!p) return res.status(500).json({ detail: '未选择任何目录' });
253
- return res.json({ path: p });
254
- } catch (e) {
255
- // 与前端对齐:返回 { detail }
256
- return res.status(500).json({ detail: String(e && e.message || e) });
257
- }
258
  });
259
 
260
  // 打开目录
261
  app.post('/api/open-dir', async (req, res) => {
262
- try {
263
- let target = (req.body && req.body.path) || '';
264
- if (!target) {
265
- const cfg = readConfig();
266
- target = cfg.output_dir || '';
267
- }
268
- if (!target) {
269
- return res.status(400).json({ detail: '未提供路径且配置中未设置 output_dir' });
270
- }
271
- await fse.ensureDir(target);
272
- try {
273
- await openDirectory(target);
274
- } catch (e) {
275
- // 某些无 GUI 环境会失败,但仍视作已准备好目录
276
- }
277
- res.json({ ok: true, path: target });
278
- } catch (e) {
279
- res.status(500).json({ detail: String(e && e.message || e) });
280
- }
281
  });
282
 
283
  // 生成接口:T2I
@@ -372,6 +351,9 @@ app.post('/api/generate/inpaint', async (req, res) => {
372
  }
373
  });
374
 
 
 
 
375
  // ---------- 静态资源 ----------
376
 
377
  // 铃声目录
 
90
  sound_enabled: false,
91
  sound_url: "/ring/ring.mp3"
92
  };
93
+ let CURRENT_CFG = null; // HF: 内存态配置,避免写盘触发重启
94
 
95
  function readConfig() {
96
+ // HF 环境:优先使用内存态,避免文件写入引发 nodemon/watchdog 重启
97
+ if (CURRENT_CFG) return { ...CURRENT_CFG };
98
  try {
99
  if (fs.existsSync(CONFIG_PATH)) {
100
  const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
101
  const fileCfg = JSON.parse(raw || '{}');
102
+ CURRENT_CFG = { ...DEFAULT_CONFIG, ...fileCfg };
103
+ return { ...CURRENT_CFG };
104
  }
105
  } catch (e) {
106
  // ignore and fallback
107
  }
108
+ CURRENT_CFG = { ...DEFAULT_CONFIG };
109
+ return { ...CURRENT_CFG };
 
110
  }
111
 
112
  function writeConfig(cfg) {
113
+ // HF 环境:仅更新内存态,不写盘,避免引发容器内重启/热更新
114
  const merged = { ...DEFAULT_CONFIG, ...(cfg || {}) };
115
+ CURRENT_CFG = merged;
116
+ return { ...merged };
 
117
  }
118
 
119
  // ---------- 工具函数:系统命令 ----------
 
249
 
250
  // 选择输出目录(桌面环境)
251
  app.get('/api/select-output-dir', async (req, res) => {
252
+ // HF 环境不支持系统目录选择,直接返回 501,前端已做兜底提示
253
+ return res.status(501).json({ detail: 'HF 环境不支持目录选择,请在“保存目录”中手动填写或留空。' });
 
 
 
 
 
 
254
  });
255
 
256
  // 打开目录
257
  app.post('/api/open-dir', async (req, res) => {
258
+ // HF 环境不支持打开系统目录,统一返回 501
259
+ return res.status(501).json({ detail: 'HF 环境不支持打开系统目录(不影响生成与下载)。' });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  });
261
 
262
  // 生成接口:T2I
 
351
  }
352
  });
353
 
354
+ // favicon 占位,避免某些浏览器 404 导致干扰
355
+ app.get('/favicon.ico', (req, res) => res.status(204).end());
356
+
357
  // ---------- 静态资源 ----------
358
 
359
  // 铃声目录