diff --git a/backend/Dockerfile b/backend/Dockerfile
index 2af3ddf..c4b427c 100644
--- a/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -55,6 +55,6 @@ VOLUME ["/app/backend/media", "/app/backend/knowledge", "/app/backend/data.json"
EXPOSE 10085 8765
HEALTHCHECK --interval=15s --timeout=5s --start-period=30s --retries=5 \
- CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:10085/api/health', timeout=4).status==200 else 1)" || exit 1
+ CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://192.168.1.9:10085/api/health', timeout=4).status==200 else 1)" || exit 1
CMD ["python", "docker_entry.py"]
diff --git a/backend/README.md b/backend/README.md
index ecf13c8..1092b36 100644
--- a/backend/README.md
+++ b/backend/README.md
@@ -102,7 +102,7 @@ export DPM_S2S_ENABLED=1 # 实时语音对话开关
docker compose up -d --build
# 3) 验证
-curl http://127.0.0.1:10085/api/health # {"ok":true,...}
+curl http://192.168.1.9:10085/api/health # {"ok":true,...}
docker compose logs -f backend
```
diff --git a/backend/app/config.py b/backend/app/config.py
index f8c4942..96a51a1 100644
--- a/backend/app/config.py
+++ b/backend/app/config.py
@@ -41,7 +41,7 @@ class Settings:
# ---- MQTT(优先使用 .env 中的局域网 Broker 配置) ----
MQTT_ENABLED = _env("DPM_MQTT_ENABLED", "1") == "1"
- MQTT_HOST = _env("DPM_MQTT_HOST", _env("MQTT_BROKER_HOST", "127.0.0.1"))
+ MQTT_HOST = _env("DPM_MQTT_HOST", _env("MQTT_BROKER_HOST", "192.168.1.9"))
MQTT_PORT = int(_env("DPM_MQTT_PORT", _env("MQTT_BROKER_PORT", "1883")))
MQTT_USERNAME = _env("DPM_MQTT_USERNAME", _env("MQTT_USERNAME", "")) or None
MQTT_PASSWORD = _env("DPM_MQTT_PASSWORD", _env("MQTT_PASSWORD", "")) or None
diff --git a/backend/app/routers.py b/backend/app/routers.py
index fe9f2ba..4bf2c86 100644
--- a/backend/app/routers.py
+++ b/backend/app/routers.py
@@ -117,7 +117,7 @@ async def runtime_config():
"""
ws = settings.MQTT_WS_URL
# 从 ws://host:port/mqtt 中提取 broker 主机
- host = "127.0.0.1"
+ host = "192.168.1.9"
try:
host = ws.split("://", 1)[1].split(":", 1)[0]
except Exception: # noqa: BLE001
diff --git a/backend/vendor/s2s-cloud/README.md b/backend/vendor/s2s-cloud/README.md
index a717760..f46635d 100644
--- a/backend/vendor/s2s-cloud/README.md
+++ b/backend/vendor/s2s-cloud/README.md
@@ -35,7 +35,7 @@ This starts an OpenAI Realtime-compatible server at `ws://localhost:8765/v1/real
From a source checkout, talk to it from a second terminal:
```bash
-python scripts/listen_and_play_realtime.py --host 127.0.0.1 --port 8765
+python scripts/listen_and_play_realtime.py --host 192.168.1.9 --port 8765
```
Prefer to keep the LLM on your own machine? Serve Gemma 4 with llama.cpp:
@@ -49,7 +49,7 @@ Then point the OpenAI-compatible LLM backend at it:
```bash
speech-to-speech \
--model_name "ggml-org/gemma-4-E4B-it-GGUF" \
- --responses_api_base_url "http://127.0.0.1:8080/v1" \
+ --responses_api_base_url "http://192.168.1.9:8080/v1" \
--responses_api_api_key ""
```
@@ -342,7 +342,7 @@ Works with any provider or server that implements the OpenAI Responses API. Poin
| HF Inference Providers | `https://router.huggingface.co/v1` | `$HF_TOKEN` |
| OpenRouter | `https://openrouter.ai/api/v1` | `$OPENROUTER_API_KEY` |
| vLLM | `http://localhost:8000/v1` | omit or any string |
-| llama.cpp | `http://127.0.0.1:8080/v1` | empty string |
+| llama.cpp | `http://192.168.1.9:8080/v1` | empty string |
```bash
# OpenAI
@@ -439,7 +439,7 @@ speech-to-speech \
--llm_backend responses-api \
--tts qwen3 \
--model_name "ggml-org/gemma-4-E4B-it-GGUF" \
- --responses_api_base_url "http://127.0.0.1:8080/v1" \
+ --responses_api_base_url "http://192.168.1.9:8080/v1" \
--responses_api_api_key "" \
--responses_api_stream \
--enable_live_transcription
diff --git a/backend/vendor/s2s-cloud/demo/README.md b/backend/vendor/s2s-cloud/demo/README.md
index 779be5b..6bd767a 100644
--- a/backend/vendor/s2s-cloud/demo/README.md
+++ b/backend/vendor/s2s-cloud/demo/README.md
@@ -57,7 +57,7 @@ Realtime **GA** protocol and plays back the assistant's audio as it arrives.
3. Open , click the orb, allow the mic, talk.
> Browsers require **HTTPS or `localhost`** for `getUserMedia()` (mic + camera).
-> `127.0.0.1` and `localhost` both work; plain `http://192.168.x.y` does NOT.
+> `192.168.1.9` and `localhost` both work; plain `http://192.168.x.y` does NOT.
Smoke-test the backend from the shell:
diff --git a/backend/vendor/s2s-cloud/demo/index.html b/backend/vendor/s2s-cloud/demo/index.html
index 2a4a9c4..3998b16 100644
--- a/backend/vendor/s2s-cloud/demo/index.html
+++ b/backend/vendor/s2s-cloud/demo/index.html
@@ -21,7 +21,7 @@
-
+
diff --git a/backend/vendor/s2s-cloud/demo/textchat.html b/backend/vendor/s2s-cloud/demo/textchat.html
index bdecdab..561f41c 100644
--- a/backend/vendor/s2s-cloud/demo/textchat.html
+++ b/backend/vendor/s2s-cloud/demo/textchat.html
@@ -57,7 +57,7 @@ async function getWsUrl() {
const cfg = await r.json();
if (cfg.s2sUrl) return cfg.s2sUrl;
} catch (e) { /* fall through */ }
- return "ws://127.0.0.1:8765/v1/realtime";
+ return "ws://192.168.1.9:8765/v1/realtime";
}
// Must be called from a user gesture (button click) so the browser allows audio.
diff --git a/backend/vendor/s2s-cloud/scripts/listen_and_play_realtime.py b/backend/vendor/s2s-cloud/scripts/listen_and_play_realtime.py
index 7ee43d1..ac04f44 100644
--- a/backend/vendor/s2s-cloud/scripts/listen_and_play_realtime.py
+++ b/backend/vendor/s2s-cloud/scripts/listen_and_play_realtime.py
@@ -13,8 +13,8 @@ from openai import AsyncOpenAI
@dataclass
class ListenAndPlayRealtimeArguments:
host: str = field(
- default="127.0.0.1",
- metadata={"help": "Realtime server host. Default is 127.0.0.1."},
+ default="192.168.1.9",
+ metadata={"help": "Realtime server host. Default is 192.168.1.9."},
)
port: int = field(
default=8765,
@@ -30,11 +30,11 @@ class ListenAndPlayRealtimeArguments:
)
base_url: Optional[str] = field(
default=None,
- metadata={"help": "Optional HTTP base URL, e.g. http://127.0.0.1:8765/v1"},
+ metadata={"help": "Optional HTTP base URL, e.g. http://192.168.1.9:8765/v1"},
)
websocket_base_url: Optional[str] = field(
default=None,
- metadata={"help": "Optional WS base URL, e.g. ws://127.0.0.1:8765/v1"},
+ metadata={"help": "Optional WS base URL, e.g. ws://192.168.1.9:8765/v1"},
)
send_rate: int = field(
default=16000,
diff --git a/backend/vendor/s2s-cloud/scripts/synthetic_conversation_realtime_client.py b/backend/vendor/s2s-cloud/scripts/synthetic_conversation_realtime_client.py
index 6945064..2e99089 100644
--- a/backend/vendor/s2s-cloud/scripts/synthetic_conversation_realtime_client.py
+++ b/backend/vendor/s2s-cloud/scripts/synthetic_conversation_realtime_client.py
@@ -536,7 +536,7 @@ def main() -> None:
"Overrides --host/--port."
),
)
- parser.add_argument("--host", default="127.0.0.1", help="Used only when --url and --lb-url are unset.")
+ parser.add_argument("--host", default="192.168.1.9", help="Used only when --url and --lb-url are unset.")
parser.add_argument("--port", type=int, default=8765, help="Used only when --url and --lb-url are unset.")
parser.add_argument("--clients", type=int, default=1, help="Number of parallel clients (default 1).")
parser.add_argument("--turns", type=int, default=60, help="Turns per client (default 60).")
diff --git a/backend/vendor/s2s-cloud/src/speech_to_speech/LLM/README.md b/backend/vendor/s2s-cloud/src/speech_to_speech/LLM/README.md
index 2a5a9d6..eeef541 100644
--- a/backend/vendor/s2s-cloud/src/speech_to_speech/LLM/README.md
+++ b/backend/vendor/s2s-cloud/src/speech_to_speech/LLM/README.md
@@ -116,7 +116,7 @@ python s2s_pipeline.py \
--ws_port 8765
# 2. Connect with the realtime client
-python listen_and_play_realtime.py --host 127.0.0.1 --port 8765
+python listen_and_play_realtime.py --host 192.168.1.9 --port 8765
```
Or with `--local_mac_optimal_settings` on Apple Silicon:
diff --git a/backend/vendor/s2s-cloud/tests/openai_realtime/test_openai_client.py b/backend/vendor/s2s-cloud/tests/openai_realtime/test_openai_client.py
index 1568d4a..b85270d 100644
--- a/backend/vendor/s2s-cloud/tests/openai_realtime/test_openai_client.py
+++ b/backend/vendor/s2s-cloud/tests/openai_realtime/test_openai_client.py
@@ -47,7 +47,7 @@ from speech_to_speech.pipeline.messages import AUDIO_RESPONSE_DONE, PIPELINE_END
def _free_port() -> int:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
- s.bind(("127.0.0.1", 0))
+ s.bind(("192.168.1.9", 0))
return s.getsockname()[1]
@@ -91,7 +91,7 @@ class _ServerEnv:
def start(self):
config = uvicorn.Config(
self.app,
- host="127.0.0.1",
+ host="192.168.1.9",
port=self.port,
log_level="warning",
)
@@ -101,7 +101,7 @@ class _ServerEnv:
self._server_thread.start()
for _ in range(50):
try:
- with socket.create_connection(("127.0.0.1", self.port), timeout=0.1):
+ with socket.create_connection(("192.168.1.9", self.port), timeout=0.1):
return
except OSError:
time.sleep(0.1)
@@ -116,8 +116,8 @@ class _ServerEnv:
def make_client(self) -> AsyncOpenAI:
return AsyncOpenAI(
api_key="test-key",
- base_url=f"http://127.0.0.1:{self.port}/v1",
- websocket_base_url=f"ws://127.0.0.1:{self.port}/v1",
+ base_url=f"http://192.168.1.9:{self.port}/v1",
+ websocket_base_url=f"ws://192.168.1.9:{self.port}/v1",
)
diff --git a/docker-compose.yml b/docker-compose.yml
index d09c6d2..d117554 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -71,8 +71,8 @@ services:
MQTT_USERNAME: "${EMQX_USER:-dpmserver}"
MQTT_PASSWORD: "${EMQX_PASS:-123456}"
# 展播端(Tauri 大屏)经后端 /api/config 获取的 WebSocket 地址:
- # 必须填"展播机能访问到的服务器地址",默认 127.0.0.1(同机部署)
- DPM_MQTT_WS: "ws://${MQTT_PUBLIC_HOST:-127.0.0.1}:8083/mqtt"
+ # 必须填"展播机能访问到的服务器地址",默认 192.168.1.9(同机部署)
+ DPM_MQTT_WS: "ws://${MQTT_PUBLIC_HOST:-192.168.1.9}:8083/mqtt"
DPM_S2S_ENABLED: "${DPM_S2S_ENABLED:-1}"
DASHSCOPE_API_KEY: "${DASHSCOPE_API_KEY:-}"
volumes:
@@ -80,7 +80,7 @@ services:
- dpm_knowledge:/app/backend/knowledge # 知识库 park.md(可热更新,删除 kb_index.json 触发重建)
- dpm_data:/app/backend/data.json # 播放列表 / 设置
healthcheck:
- test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:10085/api/health', timeout=4).status==200 else 1)"]
+ test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://192.168.1.9:10085/api/health', timeout=4).status==200 else 1)"]
interval: 15s
timeout: 5s
retries: 5
diff --git a/docs/win-deploy.md b/docs/win-deploy.md
index a78b2ac..41bec43 100644
--- a/docs/win-deploy.md
+++ b/docs/win-deploy.md
@@ -19,7 +19,7 @@
- `mqtt_url`:EMQX Broker 的 **WebSocket** 地址(端口 8083)
- `mqtt_username/password`:Broker 鉴权账号(默认 dpm / 123456,与后端 .env 一致)
-地址解析优先级:**config.json > 后端 /api/config > 构建默认(127.0.0.1)**。
+地址解析优先级:**config.json > 后端 /api/config > 构建默认(192.168.1.9)**。
## 二、后端 /api/config 方式(同机部署自动生效)
@@ -36,7 +36,7 @@ MQTT_PASSWORD=你的密码
```
> 若后端与 Broker 同机:后端默认 `MQTT_WS_URL=ws://localhost:8083/mqtt` 即可,
-> 展播端连 `127.0.0.1` 也通;跨机必须显式配置上面的 IP。
+> 展播端连 `192.168.1.9` 也通;跨机必须显式配置上面的 IP。
## 三、构建期固定地址(不推荐,需重新打包)
diff --git a/src/config.js b/src/config.js
index 33f294d..1c60d1e 100644
--- a/src/config.js
+++ b/src/config.js
@@ -3,7 +3,7 @@
优先级:window.__DPM_*__ 全局变量(运行时可被 /api/config 引导覆盖)
> Vite 环境变量(.env.local,构建时)> 内置默认
内置默认(打包部署):Tauri 内 WebView 的 hostname 为 tauri.localhost,
- 后端与 MQTT Broker 若与展播程序同机部署,回退到 127.0.0.1;
+ 后端与 MQTT Broker 若与展播程序同机部署,回退到 192.168.1.9;
浏览器开发/局域网访问时回退到当前主机 hostname。
========================================================= */
@@ -11,10 +11,10 @@ const env = import.meta.env || {};
/* 运行期解析(惰性读取 window.__DPM_*__,供 main.jsx bootstrap 覆盖后再生效) */
function resolveHost() {
- const h = window.location.hostname || '127.0.0.1';
- // Tauri 打包 WebView(tauri.localhost / localhost)→ 服务同机部署用 127.0.0.1
- if (h === 'tauri.localhost' || h === 'localhost' || h === '127.0.0.1' || h.includes('tauri')) {
- return '127.0.0.1';
+ const h = window.location.hostname || '192.168.1.9';
+ // Tauri 打包 WebView(tauri.localhost / localhost)→ 服务同机部署用 192.168.1.9
+ if (h === 'tauri.localhost' || h === 'localhost' || h === '192.168.1.9' || h.includes('tauri')) {
+ return '192.168.1.9';
}
return h; // 浏览器开发/局域网:当前主机 IP
}
@@ -41,10 +41,21 @@ export function getMqttCredentials() {
};
}
-/** 惰性读取 s2s 实时语音 WebSocket 地址(打包部署:tauri.localhost → 127.0.0.1) */
+/** 惰性读取 s2s 实时语音 WebSocket 地址。
+ * 优先 config.json / VITE_VOICE_WS 显式配置;
+ * 否则默认跟随 `api_base` 的主机名(s2s 由 s2s_bridge 在后端同机 :8765 启动),
+ * 而不是页面 hostname(打包后是 tauri.localhost/192.168.1.9,会导致连本机回环而失败)。 */
export function getVoiceWsUrl() {
- const baked = env.VITE_VOICE_WS || `ws://${resolveHost()}:8765/v1/realtime`;
- return window.__DPM_VOICE_WS__ || baked;
+ if (window.__DPM_VOICE_WS__) return window.__DPM_VOICE_WS__;
+ const baked = env.VITE_VOICE_WS;
+ if (baked) return baked;
+ let host;
+ try {
+ host = new URL(getApiBase()).hostname;
+ } catch {
+ host = resolveHost();
+ }
+ return `ws://${host}:8765/v1/realtime`;
}
export const MQTT_TOPIC_COMMAND = 'opc/display/command';
diff --git a/src/main.jsx b/src/main.jsx
index 5b58b95..00e94d7 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -10,7 +10,7 @@ import "./styles/global.css";
启动引导(打包部署关键)—— 地址解析优先级:
1. exe 旁 config.json(Tauri 命令读取,部署免重打包改 IP)★
2. 后端 GET /api/config(MQTT 地址/账号,后端 .env 为准)
- 3. 内置默认(Tauri 打包回退 127.0.0.1 / 浏览器用当前主机)
+ 3. 内置默认(Tauri 打包回退 192.168.1.9 / 浏览器用当前主机)
结果写入 window.__DPM_*__,api.js / mqtt.js 惰性读取生效。
========================================================= */
async function bootstrapRuntimeConfig() {
diff --git a/src/pages/VoiceAssistant.jsx b/src/pages/VoiceAssistant.jsx
index 4d5145a..683e6cc 100644
--- a/src/pages/VoiceAssistant.jsx
+++ b/src/pages/VoiceAssistant.jsx
@@ -13,7 +13,7 @@ import '../styles/datascreen.css';
import './voice-original.css';
import './voice-overrides.css';
-// 惰性读取 s2s 地址:打包部署 tauri.localhost → 127.0.0.1,且可在 config.json 覆盖
+// 惰性读取 s2s 地址:打包部署 tauri.localhost → 192.168.1.9,且可在 config.json 覆盖
const VOICE_URL = getVoiceWsUrl();
// 基础提示词由后端统一管理(GET /api/s2s/instructions:配置提示词 + 园区知识库),
// 前端不硬编码;此处仅为后端不可用时的兜底