Logankunfall commited on
Commit
6ddb2f2
·
verified ·
1 Parent(s): dd0bb4d

Upload 21 files

Browse files
backend/config.py CHANGED
@@ -23,13 +23,10 @@ class AppConfig(BaseModel):
23
  legacy_uc: bool = False
24
 
25
  # 通用配置
26
- port: int = 9180
27
  save_output: bool = True
28
  # 输出根目录(默认:项目根/output)
29
  output_dir: str = str(Path(__file__).resolve().parent.parent / "output")
30
- # 颜色方案:auto | bamboo | custom(前端即时应用)
31
- color_scheme: str = "auto"
32
- custom_primary: Optional[str] = None
33
  # 提示音配置
34
  sound_enabled: bool = False
35
  sound_url: Optional[str] = "/ring/ring.mp3"
 
23
  legacy_uc: bool = False
24
 
25
  # 通用配置
26
+ port: int = 7860
27
  save_output: bool = True
28
  # 输出根目录(默认:项目根/output)
29
  output_dir: str = str(Path(__file__).resolve().parent.parent / "output")
 
 
 
30
  # 提示音配置
31
  sound_enabled: bool = False
32
  sound_url: Optional[str] = "/ring/ring.mp3"
backend/server.py CHANGED
@@ -24,8 +24,15 @@ def _open_browser_later(url: str, delay: float = 1.5):
24
 
25
  if __name__ == "__main__":
26
  cfg = load_config()
27
- port = int(cfg.port or 11451)
28
- url = f"http://127.0.0.1:{port}"
29
- _open_browser_later(url)
30
- # 直接传 app 实例,避免模块路径问题
31
- uvicorn.run(app, host="127.0.0.1", port=port, reload=False)
 
 
 
 
 
 
 
 
24
 
25
  if __name__ == "__main__":
26
  cfg = load_config()
27
+ # HF 环境使用 0.0.0.0,默认端口 7860
28
+ port = int(cfg.port or 7860)
29
+ host = "0.0.0.0"
30
+
31
+ print(f"[New NAI HF] 服务运行于 http://{host}:{port}")
32
+ print(f"[New NAI HF] HF Space 通过公共 URL 访问")
33
+
34
+ # HF 环境不自动打开浏览器
35
+ # _open_browser_later(url)
36
+
37
+ # 使用 0.0.0.0 监听以便 HF Space 外部访问
38
+ uvicorn.run(app, host=host, port=port, reload=False)
frontend/assets/app.js CHANGED
@@ -18,15 +18,6 @@
18
  const btnMobile = byId('theme-toggle-mobile');
19
  if (btn) btn.textContent = text;
20
  if (btnMobile) btnMobile.textContent = text;
21
-
22
- // 主题切换时重新应用背景方案
23
- const schemeSel = byId('cfg-color-scheme');
24
- const customColor = byId('cfg-custom-primary');
25
- if (schemeSel) {
26
- const scheme = schemeSel.value || 'auto';
27
- const custom = customColor?.value || '#7ba23f';
28
- applyBackgroundScheme(scheme, custom);
29
- }
30
  }
31
  const initialTheme = localStorage.getItem('theme') || 'dark';
32
  applyTheme(initialTheme);
@@ -102,31 +93,6 @@
102
  }
103
  setupSidebar();
104
 
105
- // ===== 背景方案处理(非字体/主按钮):根据配置切换页面背景 =====
106
- function setBackground(color) {
107
- // 统一覆盖背景变量,立即生效(无刷新)
108
- rootEl.style.setProperty("--bg", color);
109
- }
110
- function applyBackgroundScheme(scheme, custom) {
111
- const theme = rootEl.getAttribute("data-theme") || "dark";
112
- if (scheme === "custom" && custom) {
113
- setBackground(custom);
114
- return;
115
- }
116
- if (scheme === "bamboo") {
117
- setBackground("#7ba23f"); // 竹子色
118
- return;
119
- }
120
- // auto:浅=白 深=深灰
121
- if (theme === "light") setBackground("#ffffff");
122
- else setBackground("#121315");
123
- }
124
- function applyAccentFromConfig(cfg) {
125
- const scheme = cfg?.color_scheme || "auto";
126
- const custom = cfg?.custom_primary || "";
127
- applyBackgroundScheme(scheme, custom);
128
- }
129
-
130
  // Tabs(桌面端和侧边栏都要同步)
131
  $$(".tab-btn[data-tab]").forEach((btn) => {
132
  btn.addEventListener("click", () => {
@@ -186,13 +152,9 @@
186
  byId("cfg-uc-preset").value = cfg.uc_preset ?? "";
187
  byId("cfg-quality-toggle").checked = !!cfg.quality_toggle;
188
  byId("cfg-legacy-uc").checked = !!cfg.legacy_uc;
189
- byId("cfg-port").value = cfg.port ?? 9180;
190
  byId("cfg-save-output").checked = !!cfg.save_output;
191
  if (byId("cfg-output-dir")) byId("cfg-output-dir").value = cfg.output_dir ?? "";
192
- // 颜色配置(带预览)
193
- if (byId("cfg-color-scheme")) byId("cfg-color-scheme").value = cfg.color_scheme ?? "auto";
194
- if (byId("cfg-custom-primary")) byId("cfg-custom-primary").value = cfg.custom_primary ?? "#7ba23f";
195
- applyAccentFromConfig(cfg);
196
 
197
  // 提示音配置
198
  soundEnabled = !!cfg.sound_enabled;
@@ -229,8 +191,6 @@
229
  port: byId("cfg-port").value ? Number(byId("cfg-port").value) : null,
230
  save_output: byId("cfg-save-output").checked,
231
  output_dir: nullIfEmpty(byId("cfg-output-dir")?.value),
232
- color_scheme: nullIfEmpty(byId("cfg-color-scheme")?.value),
233
- custom_primary: nullIfEmpty(byId("cfg-custom-primary")?.value),
234
  sound_enabled: !!soundEnabled,
235
  sound_url: nullIfEmpty(soundUrl),
236
  };
@@ -693,19 +653,7 @@
693
  }
694
  });
695
  }
696
- // 颜色方案即时预览(无需保存即可体验)
697
- const schemeSel = byId("cfg-color-scheme");
698
- const customColor = byId("cfg-custom-primary");
699
- if (schemeSel) {
700
- schemeSel.addEventListener("change", () => {
701
- applyBackgroundScheme(schemeSel.value || "auto", customColor?.value || "");
702
- });
703
- }
704
- if (customColor) {
705
- customColor.addEventListener("input", () => {
706
- applyBackgroundScheme("custom", customColor.value || "#7ba23f");
707
- });
708
- }
709
  byId("btn-t2i").addEventListener("click", handleT2I);
710
  byId("btn-i2i").addEventListener("click", handleI2I);
711
  byId("btn-inpaint").addEventListener("click", handleInpaint);
 
18
  const btnMobile = byId('theme-toggle-mobile');
19
  if (btn) btn.textContent = text;
20
  if (btnMobile) btnMobile.textContent = text;
 
 
 
 
 
 
 
 
 
21
  }
22
  const initialTheme = localStorage.getItem('theme') || 'dark';
23
  applyTheme(initialTheme);
 
93
  }
94
  setupSidebar();
95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  // Tabs(桌面端和侧边栏都要同步)
97
  $$(".tab-btn[data-tab]").forEach((btn) => {
98
  btn.addEventListener("click", () => {
 
152
  byId("cfg-uc-preset").value = cfg.uc_preset ?? "";
153
  byId("cfg-quality-toggle").checked = !!cfg.quality_toggle;
154
  byId("cfg-legacy-uc").checked = !!cfg.legacy_uc;
155
+ byId("cfg-port").value = cfg.port ?? 7860;
156
  byId("cfg-save-output").checked = !!cfg.save_output;
157
  if (byId("cfg-output-dir")) byId("cfg-output-dir").value = cfg.output_dir ?? "";
 
 
 
 
158
 
159
  // 提示音配置
160
  soundEnabled = !!cfg.sound_enabled;
 
191
  port: byId("cfg-port").value ? Number(byId("cfg-port").value) : null,
192
  save_output: byId("cfg-save-output").checked,
193
  output_dir: nullIfEmpty(byId("cfg-output-dir")?.value),
 
 
194
  sound_enabled: !!soundEnabled,
195
  sound_url: nullIfEmpty(soundUrl),
196
  };
 
653
  }
654
  });
655
  }
656
+
 
 
 
 
 
 
 
 
 
 
 
 
657
  byId("btn-t2i").addEventListener("click", handleT2I);
658
  byId("btn-i2i").addEventListener("click", handleI2I);
659
  byId("btn-inpaint").addEventListener("click", handleInpaint);
frontend/index.html CHANGED
@@ -120,18 +120,6 @@
120
  <span>保存目录</span>
121
  <input id="cfg-output-dir" type="text" placeholder="例如:D:\Pictures\NAI 输出(留空=默认 output)" />
122
  </label>
123
- <label>
124
- <span>背景方案</span>
125
- <select id="cfg-color-scheme">
126
- <option value="auto">自动(浅=白/深=深灰)</option>
127
- <option value="bamboo">竹子色</option>
128
- <option value="custom">自定义</option>
129
- </select>
130
- </label>
131
- <label>
132
- <span>自定义背景色</span>
133
- <input id="cfg-custom-primary" type="color" value="#7ba23f" />
134
- </label>
135
  </div>
136
  <div class="actions">
137
  <button id="btn-load-config">读取配置</button>
 
120
  <span>保存目录</span>
121
  <input id="cfg-output-dir" type="text" placeholder="例如:D:\Pictures\NAI 输出(留空=默认 output)" />
122
  </label>
 
 
 
 
 
 
 
 
 
 
 
 
123
  </div>
124
  <div class="actions">
125
  <button id="btn-load-config">读取配置</button>
server.js CHANGED
@@ -86,9 +86,7 @@ const DEFAULT_CONFIG = {
86
  port: 7860,
87
  save_output: true,
88
  output_dir: path.join(ROOT, 'output'),
89
- // UI 配色与提示音(与前端一致)
90
- color_scheme: "auto",
91
- custom_primary: null,
92
  sound_enabled: false,
93
  sound_url: "/ring/ring.mp3"
94
  };
@@ -144,35 +142,41 @@ function sleep(ms) {
144
 
145
  // Windows: 使用 COM Shell.Application 弹出目录选择器
146
  async function winBrowseForFolder() {
 
147
  try {
148
  const psCmd = `$f=(New-Object -ComObject Shell.Application).BrowseForFolder(0,"选择保存目录",0); if($f){$f.Self.Path}`;
149
  const { stdout } = await runSpawn('powershell.exe', ['-NoProfile', '-Command', psCmd], { timeout: 60000 });
150
  const out = (stdout || '').trim();
151
- if (out) return out;
152
  } catch (e) {
153
- // ignore, fallback to VBS
154
  }
155
 
156
- // VBScript 兜底:cscript //nologo temp.vbs
157
- const vbs = [
158
- 'Set sh = CreateObject("Shell.Application")',
159
- 'Set f = sh.BrowseForFolder(0, "选择保存目录", 0)',
160
- 'If (Not f Is Nothing) Then',
161
- ' WScript.Echo f.Self.Path',
162
- 'End If'
163
- ].join('\n');
164
-
165
- const osTmp = os.tmpdir();
166
- const vbsPath = path.join(osTmp, `browse_${Date.now()}.vbs`);
167
- fs.writeFileSync(vbsPath, vbs, 'utf-8');
168
  try {
169
- const { stdout } = await runSpawn('cscript.exe', ['//nologo', vbsPath], { timeout: 60000 });
170
- const out = (stdout || '').trim();
171
- if (out) return out;
172
- } finally {
173
- try { fs.unlinkSync(vbsPath); } catch {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  }
175
- throw new Error('Windows 目录选择失败');
 
176
  }
177
 
178
  async function darwinChooseFolder() {
@@ -204,14 +208,24 @@ async function pickDirectory() {
204
 
205
  async function openDirectory(p) {
206
  const sys = os.platform();
 
 
 
 
 
207
  if (sys === 'win32') {
208
- // 使用 explorer 打开目录;或 start 命令
209
- await runSpawn('explorer.exe', [p]);
 
 
 
 
 
210
  } else if (sys === 'darwin') {
211
- await runSpawn('open', [p]);
212
  } else {
213
- // linux
214
- await runSpawn('xdg-open', [p]);
215
  }
216
  }
217
 
@@ -388,32 +402,35 @@ function resolvePort() {
388
  }
389
 
390
  const PORT = resolvePort();
 
391
  const HOST = process.env.HOST || '0.0.0.0';
392
 
393
  const server = http.createServer(app);
394
  server.listen(PORT, HOST, () => {
395
- const url = `http://${HOST}:${PORT}`;
396
- // 模拟 Python 版本的延迟打开浏览器(仅本地)
397
- if (HOST === '127.0.0.1' || HOST === '0.0.0.0') {
398
- const shouldOpen = process.env.AUTO_OPEN_BROWSER !== '0';
399
- if (shouldOpen) {
400
- setTimeout(() => {
401
- try {
402
- const platform = os.platform();
403
- if (platform === 'win32') {
404
- // start "" "url"
405
- spawn('cmd', ['/c', 'start', '', url], { detached: true, stdio: 'ignore' }).unref();
406
- } else if (platform === 'darwin') {
407
- spawn('open', [url], { detached: true, stdio: 'ignore' }).unref();
408
- } else {
409
- spawn('xdg-open', [url], { detached: true, stdio: 'ignore' }).unref();
410
- }
411
- } catch { /* ignore */ }
412
- }, 1500);
413
- }
414
  }
 
415
  // eslint-disable-next-line no-console
416
- console.log(`[New NAI] Express server is running at ${url}`);
 
417
  });
418
 
419
  // 导出 app 以便测试或其他入口使用
 
86
  port: 7860,
87
  save_output: true,
88
  output_dir: path.join(ROOT, 'output'),
89
+ // 提示音配置
 
 
90
  sound_enabled: false,
91
  sound_url: "/ring/ring.mp3"
92
  };
 
142
 
143
  // Windows: 使用 COM Shell.Application 弹出目录选择器
144
  async function winBrowseForFolder() {
145
+ // 方案1:PowerShell COM
146
  try {
147
  const psCmd = `$f=(New-Object -ComObject Shell.Application).BrowseForFolder(0,"选择保存目录",0); if($f){$f.Self.Path}`;
148
  const { stdout } = await runSpawn('powershell.exe', ['-NoProfile', '-Command', psCmd], { timeout: 60000 });
149
  const out = (stdout || '').trim();
150
+ if (out && out.length > 0) return out;
151
  } catch (e) {
152
+ // 继续尝试下一个方案
153
  }
154
 
155
+ // 方案2:VBScript 兜底
 
 
 
 
 
 
 
 
 
 
 
156
  try {
157
+ const vbs = [
158
+ 'Set sh = CreateObject("Shell.Application")',
159
+ 'Set f = sh.BrowseForFolder(0, "选择保存目录", 0)',
160
+ 'If (Not f Is Nothing) Then',
161
+ ' WScript.Echo f.Self.Path',
162
+ 'End If'
163
+ ].join('\n');
164
+
165
+ const osTmp = os.tmpdir();
166
+ const vbsPath = path.join(osTmp, `browse_${Date.now()}.vbs`);
167
+ fs.writeFileSync(vbsPath, vbs, 'utf-8');
168
+ try {
169
+ const { stdout } = await runSpawn('cscript.exe', ['//nologo', vbsPath], { timeout: 60000 });
170
+ const out = (stdout || '').trim();
171
+ if (out && out.length > 0) return out;
172
+ } finally {
173
+ try { fs.unlinkSync(vbsPath); } catch {}
174
+ }
175
+ } catch (e) {
176
+ // 继续尝试下一个方案
177
  }
178
+
179
+ throw new Error('Windows 目录选择失败:所有方案均失败');
180
  }
181
 
182
  async function darwinChooseFolder() {
 
208
 
209
  async function openDirectory(p) {
210
  const sys = os.platform();
211
+ const absPath = path.resolve(p);
212
+
213
+ // 确保目录存在
214
+ await fse.ensureDir(absPath);
215
+
216
  if (sys === 'win32') {
217
+ // Windows: 使用 explorer
218
+ try {
219
+ await runSpawn('explorer.exe', [absPath]);
220
+ } catch (e) {
221
+ // 回退方案:使用 cmd start
222
+ spawn('cmd', ['/c', 'start', '', absPath], { detached: true, stdio: 'ignore' }).unref();
223
+ }
224
  } else if (sys === 'darwin') {
225
+ await runSpawn('open', [absPath]);
226
  } else {
227
+ // Linux
228
+ await runSpawn('xdg-open', [absPath]);
229
  }
230
  }
231
 
 
402
  }
403
 
404
  const PORT = resolvePort();
405
+ // HF 必须使用 0.0.0.0 以便外部访问
406
  const HOST = process.env.HOST || '0.0.0.0';
407
 
408
  const server = http.createServer(app);
409
  server.listen(PORT, HOST, () => {
410
+ // HF 环境显示 0.0.0.0,但实际通过 Space URL 访问
411
+ const displayUrl = HOST === '0.0.0.0' ? `http://0.0.0.0:${PORT}` : `http://${HOST}:${PORT}`;
412
+
413
+ // HF 环境不自动打开浏览器
414
+ const shouldOpen = process.env.AUTO_OPEN_BROWSER !== '0' && HOST === '127.0.0.1';
415
+ if (shouldOpen) {
416
+ setTimeout(() => {
417
+ try {
418
+ const localUrl = `http://127.0.0.1:${PORT}`;
419
+ const platform = os.platform();
420
+ if (platform === 'win32') {
421
+ spawn('cmd', ['/c', 'start', '', localUrl], { detached: true, stdio: 'ignore' }).unref();
422
+ } else if (platform === 'darwin') {
423
+ spawn('open', [localUrl], { detached: true, stdio: 'ignore' }).unref();
424
+ } else {
425
+ spawn('xdg-open', [localUrl], { detached: true, stdio: 'ignore' }).unref();
426
+ }
427
+ } catch { /* ignore */ }
428
+ }, 1500);
429
  }
430
+
431
  // eslint-disable-next-line no-console
432
+ console.log(`[New NAI HF] 服务运行于 ${displayUrl}`);
433
+ console.log(`[New NAI HF] ${HOST === '0.0.0.0' ? 'HF Space 通过公共 URL 访问' : '按 Ctrl+C 停止服务'}`);
434
  });
435
 
436
  // 导出 app 以便测试或其他入口使用