页面标题
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`。
|
||||
|
||||
如果 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`。
|
||||
|
||||
## 安全提示
|
||||
|
||||
16
deploy.sh
16
deploy.sh
@ -16,12 +16,26 @@ if ! docker compose version >/dev/null 2>&1; then
|
||||
exit 1
|
||||
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
|
||||
while [ "$attempt" -lt 20 ]; do
|
||||
status=$(docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' shuziren 2>/dev/null || true)
|
||||
if [ "$status" = "healthy" ]; then
|
||||
sync_static_files
|
||||
echo "Published successfully: http://SERVER_IP:9000"
|
||||
docker compose ps
|
||||
exit 0
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#2468f2" />
|
||||
<title>DeepSeek 数字人助手</title>
|
||||
<meta name="description" content="钱学森数字人航天科学语音问答" />
|
||||
<title>钱学森数字人 · 航天科学问答</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@ -18,6 +18,7 @@ const contentTypes: Record<string, string> = {
|
||||
'.js': 'text/javascript; charset=utf-8',
|
||||
'.json': 'application/json; charset=utf-8',
|
||||
'.map': 'application/json; charset=utf-8',
|
||||
'.mp4': 'video/mp4',
|
||||
'.png': 'image/png',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.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 TOOLBAR_HIDE_DELAY_MS = 3_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 dhIframe = new DHIframe('digital-human-iframe');
|
||||
const realTimeVideoReady = ref(false);
|
||||
@ -299,7 +299,7 @@ const releaseHuman = (preservePendingAnswer = false, smooth = false) => {
|
||||
};
|
||||
const disconnectHuman = () => {
|
||||
soundUnlockTip.value = false;
|
||||
releaseHuman(false);
|
||||
releaseHuman(false, true);
|
||||
};
|
||||
const sendSpeech = (text) => {
|
||||
if (postSpeechDisconnectTimer) {
|
||||
@ -350,15 +350,6 @@ const handleAnswerReady = async (text) => {
|
||||
deepSeekAnswer.value = text;
|
||||
waitingForAnswer.value = false;
|
||||
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();
|
||||
else {
|
||||
cancelHumanRelease();
|
||||
@ -449,8 +440,8 @@ const onMessage = (event) => {
|
||||
}
|
||||
if (content.action === 'localVideoMuted') {
|
||||
videoIsMuted.value = parseMutedState(content.body);
|
||||
if (soundEnabled.value && videoIsMuted.value && (pendingSpeechText.value || speechBusy.value)) {
|
||||
audioUnlocked.value = false;
|
||||
if (soundEnabled.value && videoIsMuted.value && !audioUnlocked.value
|
||||
&& (pendingSpeechText.value || speechBusy.value)) {
|
||||
soundUnlockTip.value = true;
|
||||
showToolbar();
|
||||
}
|
||||
@ -474,8 +465,7 @@ const onMessage = (event) => {
|
||||
if (replayAfterMutedPlayback) {
|
||||
pendingSpeechText.value = replayText;
|
||||
soundUnlockTip.value = true;
|
||||
audioUnlocked.value = false;
|
||||
releaseHuman(true);
|
||||
releaseHuman(true, true);
|
||||
return;
|
||||
}
|
||||
postSpeechDisconnectTimer = window.setTimeout(() => {
|
||||
@ -520,8 +510,8 @@ onUnmounted(() => {
|
||||
.standby-video.visible { z-index: 1; }.standby-video.incoming { z-index: 2; }
|
||||
.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%); }
|
||||
.human-frame { z-index: 1; opacity: 0; background: #5a2f1b; pointer-events: none; transition: opacity 320ms cubic-bezier(.22, 1, .36, 1); }
|
||||
.human-frame.ready { opacity: 1; pointer-events: auto; }
|
||||
.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; 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-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%; }
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user