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:配置提示词 + 园区知识库), // 前端不硬编码;此处仅为后端不可用时的兜底