页面标题

This commit is contained in:
tanglong 2026-10-20 17:21:57 +08:00
parent 89b6eb5e8a
commit ed1fef31be
6 changed files with 33 additions and 19 deletions

View File

@ -53,6 +53,14 @@ corepack yarn start
`deploy.sh` 会构建并启动 Docker 容器 `shuziren`,并等待健康检查通过。停止服务使用 `docker compose down`;查看日志使用 `docker compose logs -f`。 `deploy.sh` 会构建并启动 Docker 容器 `shuziren`,并等待健康检查通过。停止服务使用 `docker compose down`;查看日志使用 `docker compose logs -f`。
如果 Nginx 直接从 `/data/nginx/www/shuziren/` 提供静态页面,而不是把整个站点反向代理到 9000 端口,请使用:
```bash
STATIC_DIRECTORY=/data/nginx/www/shuziren sh deploy.sh
```
发布脚本会强制重建容器,并将容器中的最新 `dist` 同步到该 Nginx 静态目录,避免继续显示旧视频。
服务器需要安装 Docker Engine 和 Docker Compose v2,并放行 TCP `9000` 入站端口。服务端必须能够访问 `https://api.deepseek.com`。 服务器需要安装 Docker Engine 和 Docker Compose v2,并放行 TCP `9000` 入站端口。服务端必须能够访问 `https://api.deepseek.com`。
## 安全提示 ## 安全提示

View File

@ -16,12 +16,26 @@ if ! docker compose version >/dev/null 2>&1; then
exit 1 exit 1
fi fi
docker compose up -d --build --remove-orphans # Do not reuse an old frontend layer: media files must always come from the
# currently uploaded source package.
docker compose build --no-cache shuziren
docker compose up -d --force-recreate --remove-orphans shuziren
sync_static_files() {
if [ -z "${STATIC_DIRECTORY:-}" ]; then
return
fi
mkdir -p "$STATIC_DIRECTORY"
docker cp "shuziren:/app/dist/." "$STATIC_DIRECTORY/"
echo "Static frontend synchronized to: $STATIC_DIRECTORY"
}
attempt=0 attempt=0
while [ "$attempt" -lt 20 ]; do while [ "$attempt" -lt 20 ]; do
status=$(docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' shuziren 2>/dev/null || true) status=$(docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' shuziren 2>/dev/null || true)
if [ "$status" = "healthy" ]; then if [ "$status" = "healthy" ]; then
sync_static_files
echo "Published successfully: http://SERVER_IP:9000" echo "Published successfully: http://SERVER_IP:9000"
docker compose ps docker compose ps
exit 0 exit 0

View File

@ -4,7 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#2468f2" /> <meta name="theme-color" content="#2468f2" />
<title>DeepSeek 数字人助手</title> <meta name="description" content="钱学森数字人航天科学语音问答" />
<title>钱学森数字人 · 航天科学问答</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@ -18,6 +18,7 @@ const contentTypes: Record<string, string> = {
'.js': 'text/javascript; charset=utf-8', '.js': 'text/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8', '.json': 'application/json; charset=utf-8',
'.map': 'application/json; charset=utf-8', '.map': 'application/json; charset=utf-8',
'.mp4': 'video/mp4',
'.png': 'image/png', '.png': 'image/png',
'.svg': 'image/svg+xml', '.svg': 'image/svg+xml',
'.webp': 'image/webp', '.webp': 'image/webp',

View File

@ -132,7 +132,7 @@ import {checkPlayUnMute} from './utils/audioUtils';
const ReadyState = {UNINSTANTIATED: -1, CONNECTING: 0, OPEN: 1, CLOSING: 2, CLOSED: 3}; const ReadyState = {UNINSTANTIATED: -1, CONNECTING: 0, OPEN: 1, CLOSING: 2, CLOSED: 3};
const TOOLBAR_HIDE_DELAY_MS = 3_000; const TOOLBAR_HIDE_DELAY_MS = 3_000;
const POST_SPEECH_DISCONNECT_DELAY_MS = 10_000; const POST_SPEECH_DISCONNECT_DELAY_MS = 10_000;
const HUMAN_SCENE_FADE_OUT_MS = 360; const HUMAN_SCENE_FADE_OUT_MS = 920;
const STANDBY_CROSSFADE_MS = 650; const STANDBY_CROSSFADE_MS = 650;
const dhIframe = new DHIframe('digital-human-iframe'); const dhIframe = new DHIframe('digital-human-iframe');
const realTimeVideoReady = ref(false); const realTimeVideoReady = ref(false);
@ -299,7 +299,7 @@ const releaseHuman = (preservePendingAnswer = false, smooth = false) => {
}; };
const disconnectHuman = () => { const disconnectHuman = () => {
soundUnlockTip.value = false; soundUnlockTip.value = false;
releaseHuman(false); releaseHuman(false, true);
}; };
const sendSpeech = (text) => { const sendSpeech = (text) => {
if (postSpeechDisconnectTimer) { if (postSpeechDisconnectTimer) {
@ -350,15 +350,6 @@ const handleAnswerReady = async (text) => {
deepSeekAnswer.value = text; deepSeekAnswer.value = text;
waitingForAnswer.value = false; waitingForAnswer.value = false;
pendingSpeechText.value = text; pendingSpeechText.value = text;
if (soundEnabled.value && !audioUnlocked.value) {
const canPlayWithSound = await checkPlayUnMute();
if (soundEnabled.value && !canPlayWithSound && !audioUnlocked.value) {
soundUnlockTip.value = true;
showToolbar();
return;
}
if (canPlayWithSound) audioUnlocked.value = true;
}
if (!showHuman.value) connectHuman(); if (!showHuman.value) connectHuman();
else { else {
cancelHumanRelease(); cancelHumanRelease();
@ -449,8 +440,8 @@ const onMessage = (event) => {
} }
if (content.action === 'localVideoMuted') { if (content.action === 'localVideoMuted') {
videoIsMuted.value = parseMutedState(content.body); videoIsMuted.value = parseMutedState(content.body);
if (soundEnabled.value && videoIsMuted.value && (pendingSpeechText.value || speechBusy.value)) { if (soundEnabled.value && videoIsMuted.value && !audioUnlocked.value
audioUnlocked.value = false; && (pendingSpeechText.value || speechBusy.value)) {
soundUnlockTip.value = true; soundUnlockTip.value = true;
showToolbar(); showToolbar();
} }
@ -474,8 +465,7 @@ const onMessage = (event) => {
if (replayAfterMutedPlayback) { if (replayAfterMutedPlayback) {
pendingSpeechText.value = replayText; pendingSpeechText.value = replayText;
soundUnlockTip.value = true; soundUnlockTip.value = true;
audioUnlocked.value = false; releaseHuman(true, true);
releaseHuman(true);
return; return;
} }
postSpeechDisconnectTimer = window.setTimeout(() => { postSpeechDisconnectTimer = window.setTimeout(() => {
@ -520,8 +510,8 @@ onUnmounted(() => {
.standby-video.visible { z-index: 1; }.standby-video.incoming { z-index: 2; } .standby-video.visible { z-index: 1; }.standby-video.incoming { z-index: 2; }
.standby-scene.thinking .standby-video { filter: saturate(.92) brightness(.86); } .standby-scene.thinking .standby-video { filter: saturate(.92) brightness(.86); }
.standby-shade { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(18, 8, 4, .03) 42%, rgba(17, 8, 4, .58) 100%); } .standby-shade { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(18, 8, 4, .03) 42%, rgba(17, 8, 4, .58) 100%); }
.human-frame { z-index: 1; opacity: 0; background: #5a2f1b; pointer-events: none; transition: opacity 320ms cubic-bezier(.22, 1, .36, 1); } .human-frame { z-index: 1; opacity: 0; overflow: hidden; background: #5a2f1b; pointer-events: none; transform: translateZ(0) scale(1.004); filter: blur(1.5px); backface-visibility: hidden; will-change: opacity, transform, filter; transition: opacity 900ms cubic-bezier(.4, 0, .2, 1), transform 1100ms cubic-bezier(.22, 1, .36, 1), filter 700ms ease-out; }
.human-frame.ready { opacity: 1; pointer-events: auto; } .human-frame.ready { opacity: 1; pointer-events: auto; transform: translateZ(0) scale(1); filter: blur(0); }
.thinking-panel { position: absolute; z-index: 2; left: 50%; bottom: clamp(120px, 17vh, 190px); display: flex; align-items: center; gap: 13px; min-width: min(360px, calc(100% - 36px)); padding: 13px 17px; transform: translateX(-50%); border: 1px solid rgba(255, 238, 220, .35); border-radius: 18px; color: #fff7ed; background: rgba(37, 18, 10, .7); box-shadow: 0 18px 48px rgba(20, 7, 2, .3); backdrop-filter: blur(13px); } .thinking-panel { position: absolute; z-index: 2; left: 50%; bottom: clamp(120px, 17vh, 190px); display: flex; align-items: center; gap: 13px; min-width: min(360px, calc(100% - 36px)); padding: 13px 17px; transform: translateX(-50%); border: 1px solid rgba(255, 238, 220, .35); border-radius: 18px; color: #fff7ed; background: rgba(37, 18, 10, .7); box-shadow: 0 18px 48px rgba(20, 7, 2, .3); backdrop-filter: blur(13px); }
.thinking-orbit { position: relative; flex: 0 0 36px; width: 36px; height: 36px; } .thinking-orbit { position: relative; flex: 0 0 36px; width: 36px; height: 36px; }
.thinking-orbit span { position: absolute; inset: 0; border: 1px solid rgba(255, 221, 181, .44); border-radius: 50%; } .thinking-orbit span { position: absolute; inset: 0; border: 1px solid rgba(255, 221, 181, .44); border-radius: 50%; }

Binary file not shown.