页面标题
This commit is contained in:
parent
89b6eb5e8a
commit
ed1fef31be
@ -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`。
|
||||||
|
|
||||||
## 安全提示
|
## 安全提示
|
||||||
|
|||||||
16
deploy.sh
16
deploy.sh
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
24
src/App.vue
24
src/App.vue
@ -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.
Loading…
x
Reference in New Issue
Block a user