按钮位置调整+商标挂断
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 1m53s

This commit is contained in:
libingxiang 2026-07-06 15:15:43 +08:00
parent 4bf7369f92
commit 3b280eb579
2 changed files with 371 additions and 296 deletions

View File

@ -35,10 +35,10 @@
stroke="currentColor" stroke="currentColor"
stroke-width="2" stroke-width="2"
> >
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" /> <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
<path d="M19 10v2a7 7 0 0 1-14 0v-2" /> <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
<line x1="12" y1="19" x2="12" y2="23" /> <line x1="12" y1="19" x2="12" y2="23"/>
<circle cx="18" cy="5" r="2" fill="currentColor" stroke="none" /> <circle cx="18" cy="5" r="2" fill="currentColor" stroke="none"/>
</svg> </svg>
开始对话 开始对话
</button> </button>
@ -69,16 +69,14 @@
<!-- 字幕区域底部不遮挡数字人主体 --> <!-- 字幕区域底部不遮挡数字人主体 -->
<div class="subtitle-overlay"> <div class="subtitle-overlay">
<div class="subtitle-container"> <div class="subtitle-container">
<SubtitlePanel :ai-subtitle="currentAISubtitle" /> <SubtitlePanel :ai-subtitle="currentAISubtitle"/>
</div> </div>
</div> </div>
<!-- 右侧垂直控制按钮组 --> <!-- 拾音按钮 - 左下角 -->
<div class="side-controls">
<!-- 拾音按钮 -->
<button <button
v-if="audioMode === 'btn'" v-if="audioMode === 'btn'"
class="mic-btn" class="mic-btn mic-bottom-left"
:class="{ :class="{
recording: pickPhase === 'recording', recording: pickPhase === 'recording',
'mic-processing': pickPhase === 'processing', 'mic-processing': pickPhase === 'processing',
@ -110,10 +108,10 @@
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
> >
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" /> <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
<path d="M19 10v2a7 7 0 0 1-14 0v-2" /> <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
<line x1="12" y1="19" x2="12" y2="23" /> <line x1="12" y1="19" x2="12" y2="23"/>
<line x1="8" y1="23" x2="16" y2="23" /> <line x1="8" y1="23" x2="16" y2="23"/>
</svg> </svg>
<!-- 识别中文字气泡+三点跳动 --> <!-- 识别中文字气泡+三点跳动 -->
<svg <svg
@ -212,8 +210,8 @@
fill="currentColor" fill="currentColor"
stroke="none" stroke="none"
/> />
<path class="wave-1" d="M15.54 8.46a5 5 0 0 1 0 7.07" /> <path class="wave-1" d="M15.54 8.46a5 5 0 0 1 0 7.07"/>
<path class="wave-2" d="M19.07 4.93a10 10 0 0 1 0 14.14" /> <path class="wave-2" d="M19.07 4.93a10 10 0 0 1 0 14.14"/>
</svg> </svg>
<!-- 空闲麦克风 --> <!-- 空闲麦克风 -->
<svg <svg
@ -227,10 +225,10 @@
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
> >
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" /> <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
<path d="M19 10v2a7 7 0 0 1-14 0v-2" /> <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
<line x1="12" y1="19" x2="12" y2="23" /> <line x1="12" y1="19" x2="12" y2="23"/>
<line x1="8" y1="23" x2="16" y2="23" /> <line x1="8" y1="23" x2="16" y2="23"/>
</svg> </svg>
<span class="mic-label">{{ <span class="mic-label">{{
pickPhase === "recording" pickPhase === "recording"
@ -245,10 +243,10 @@
}}</span> }}</span>
</button> </button>
<!-- RTC模式拾音按钮 --> <!-- RTC模式拾音按钮 - 左下角 -->
<button <button
v-if="audioMode === 'rtc'" v-if="audioMode === 'rtc'"
class="mic-btn" class="mic-btn mic-bottom-left"
:class="{ :class="{
'mic-on': pickPhase === 'idle' && !isMuted, 'mic-on': pickPhase === 'idle' && !isMuted,
'mic-off': pickPhase === 'idle' && isMuted, 'mic-off': pickPhase === 'idle' && isMuted,
@ -278,10 +276,10 @@
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
> >
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" /> <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
<path d="M19 10v2a7 7 0 0 1-14 0v-2" /> <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
<line x1="12" y1="19" x2="12" y2="23" /> <line x1="12" y1="19" x2="12" y2="23"/>
<line x1="8" y1="23" x2="16" y2="23" /> <line x1="8" y1="23" x2="16" y2="23"/>
</svg> </svg>
<!-- 识别中文字气泡+三点跳动 --> <!-- 识别中文字气泡+三点跳动 -->
<svg <svg
@ -380,8 +378,8 @@
fill="currentColor" fill="currentColor"
stroke="none" stroke="none"
/> />
<path class="wave-1" d="M15.54 8.46a5 5 0 0 1 0 7.07" /> <path class="wave-1" d="M15.54 8.46a5 5 0 0 1 0 7.07"/>
<path class="wave-2" d="M19.07 4.93a10 10 0 0 1 0 14.14" /> <path class="wave-2" d="M19.07 4.93a10 10 0 0 1 0 14.14"/>
</svg> </svg>
<!-- 麦克风已关闭 --> <!-- 麦克风已关闭 -->
<svg <svg
@ -395,9 +393,9 @@
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
> >
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" /> <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
<path d="M19 10v2a7 7 0 0 1-14 0v-2" /> <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
<line x1="1" y1="1" x2="23" y2="23" /> <line x1="1" y1="1" x2="23" y2="23"/>
</svg> </svg>
<span class="mic-label">{{ <span class="mic-label">{{
pickPhase === "processing" pickPhase === "processing"
@ -412,8 +410,8 @@
}}</span> }}</span>
</button> </button>
<!-- 打断按钮 --> <!-- 打断按钮 - 右下角 -->
<button class="interrupt-btn" @click="dhInterrupt"> <button class="interrupt-btn interrupt-bottom-right" @click="dhInterrupt">
<svg <svg
viewBox="0 0 24 24" viewBox="0 0 24 24"
width="24" width="24"
@ -423,44 +421,28 @@
stroke-width="2.5" stroke-width="2.5"
stroke-linecap="round" stroke-linecap="round"
> >
<path d="M18.36 6.64a9 9 0 1 1-12.73 0" /> <path d="M18.36 6.64a9 9 0 1 1-12.73 0"/>
<line x1="12" y1="2" x2="12" y2="12" /> <line x1="12" y1="2" x2="12" y2="12"/>
</svg> </svg>
<span class="interrupt-label">打断</span> <span class="interrupt-label">打断</span>
</button> </button>
<!-- 挂断按钮 --> <!-- 挂断按钮 - 右上角 -->
<button class="hangup-btn" @click="handleDisconnect" title="挂断"> <button class="hangup-btn hangup-top-left" @click="handleDisconnect" title="挂断">
<svg <img src="./GP.webp" alt="挂断" class="hangup-img"/>
viewBox="0 0 24 24"
width="24"
height="24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91"
/>
<line x1="1" y1="1" x2="23" y2="23" />
</svg>
<span class="hangup-label">挂断</span>
</button> </button>
</div>
</template> </template>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from "vue"; import {ref, watch} from "vue";
import { ElMessage } from "element-plus"; import {ElMessage} from "element-plus";
import SubtitlePanel from "@/components/SubtitlePanel.vue"; import SubtitlePanel from "@/components/SubtitlePanel.vue";
import { useDigitalHuman } from "@/hooks/useDigitalHuman"; import {useDigitalHuman} from "@/hooks/useDigitalHuman";
import { useLLM } from "@/hooks/useLLM"; import {useLLM} from "@/hooks/useLLM";
const { sendToChatBase } = useLLM(); const {sendToChatBase} = useLLM();
const { const {
isConnected, isConnected,
@ -621,6 +603,7 @@ async function handleVoiceQueryComplete(text: string) {
background: linear-gradient(135deg, #4f6ef7, #6c8cff); background: linear-gradient(135deg, #4f6ef7, #6c8cff);
color: #fff; color: #fff;
box-shadow: 0 8px 32px rgba(79, 110, 247, 0.35); box-shadow: 0 8px 32px rgba(79, 110, 247, 0.35);
&:hover { &:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 12px 40px rgba(79, 110, 247, 0.5); box-shadow: 0 12px 40px rgba(79, 110, 247, 0.5);
@ -631,6 +614,7 @@ async function handleVoiceQueryComplete(text: string) {
background: rgba(255, 255, 255, 0.06); background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.12); border: 1px solid rgba(255, 255, 255, 0.12);
color: rgba(255, 255, 255, 0.8); color: rgba(255, 255, 255, 0.8);
&:hover { &:hover {
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.2);
@ -670,6 +654,7 @@ async function handleVoiceQueryComplete(text: string) {
&::placeholder { &::placeholder {
color: rgba(255, 255, 255, 0.25); color: rgba(255, 255, 255, 0.25);
} }
&:focus { &:focus {
border-color: rgba(108, 140, 255, 0.5); border-color: rgba(108, 140, 255, 0.5);
} }
@ -720,17 +705,48 @@ async function handleVoiceQueryComplete(text: string) {
font-weight: 500; font-weight: 500;
} }
/* ===== 右侧垂直控制按钮组 ===== */ /* ===== 麦克风按钮 - 字幕下方左侧 ===== */
.side-controls { .mic-bottom-left {
position: absolute; position: absolute;
right: 24px; left: 50%;
top: 50%; transform: translateX(-120px);
transform: translateY(-50%); bottom: 10%;
z-index: 25;
transition: transform 0.2s ease;
&:hover {
transform: translateX(-120px) scale(1.06);
}
&:active {
transform: translateX(-120px) scale(0.95);
}
}
/* ===== 打断按钮 - 字幕下方右侧 ===== */
.interrupt-bottom-right {
position: absolute;
right: 50%;
transform: translateX(120px);
bottom: 10%;
z-index: 25;
transition: transform 0.2s ease;
&:hover {
transform: translateX(120px) scale(1.06);
}
&:active {
transform: translateX(120px) scale(0.95);
}
}
/* ===== 挂断按钮 - 右上角 ===== */
.hangup-top-left {
position: absolute;
right: 32px;
top: 24px;
z-index: 25; z-index: 25;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
} }
/* ===== 字幕区域(屏幕中央居中) ===== */ /* ===== 字幕区域(屏幕中央居中) ===== */
@ -756,11 +772,10 @@ async function handleVoiceQueryComplete(text: string) {
/* ===== 拾音按钮 ===== */ /* ===== 拾音按钮 ===== */
.mic-btn { .mic-btn {
position: relative;
flex-shrink: 0; flex-shrink: 0;
width: 72px; width: 110px;
height: 72px; height: 110px;
border-radius: 20px; border-radius: 30px;
border: 2px solid rgba(255, 255, 255, 0.15); border: 2px solid rgba(255, 255, 255, 0.15);
background: rgba(255, 255, 255, 0.08); background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(12px); backdrop-filter: blur(12px);
@ -785,11 +800,9 @@ async function handleVoiceQueryComplete(text: string) {
&:hover { &:hover {
background: rgba(255, 255, 255, 0.14); background: rgba(255, 255, 255, 0.14);
border-color: rgba(255, 255, 255, 0.3); border-color: rgba(255, 255, 255, 0.3);
transform: scale(1.06);
} }
&:active { &:active {
transform: scale(0.95);
} }
&.recording { &.recording {
@ -869,12 +882,15 @@ async function handleVoiceQueryComplete(text: string) {
.dot { .dot {
transform-origin: center; transform-origin: center;
} }
.dot-1 { .dot-1 {
animation: dotBounce 1.4s ease-in-out infinite; animation: dotBounce 1.4s ease-in-out infinite;
} }
.dot-2 { .dot-2 {
animation: dotBounce 1.4s ease-in-out 0.2s infinite; animation: dotBounce 1.4s ease-in-out 0.2s infinite;
} }
.dot-3 { .dot-3 {
animation: dotBounce 1.4s ease-in-out 0.4s infinite; animation: dotBounce 1.4s ease-in-out 0.4s infinite;
} }
@ -898,12 +914,15 @@ async function handleVoiceQueryComplete(text: string) {
.ellipsis-dot { .ellipsis-dot {
transform-origin: center; transform-origin: center;
} }
.ed-1 { .ed-1 {
animation: ellipsisPulse 1.8s ease-in-out infinite; animation: ellipsisPulse 1.8s ease-in-out infinite;
} }
.ed-2 { .ed-2 {
animation: ellipsisPulse 1.8s ease-in-out 0.3s infinite; animation: ellipsisPulse 1.8s ease-in-out 0.3s infinite;
} }
.ed-3 { .ed-3 {
animation: ellipsisPulse 1.8s ease-in-out 0.6s infinite; animation: ellipsisPulse 1.8s ease-in-out 0.6s infinite;
} }
@ -928,6 +947,7 @@ async function handleVoiceQueryComplete(text: string) {
animation: waveExpand 2s ease-out infinite; animation: waveExpand 2s ease-out infinite;
transform-origin: 12px 12px; transform-origin: 12px 12px;
} }
.wave-2 { .wave-2 {
animation: waveExpand 2s ease-out 0.5s infinite; animation: waveExpand 2s ease-out 0.5s infinite;
transform-origin: 12px 12px; transform-origin: 12px 12px;
@ -997,9 +1017,9 @@ async function handleVoiceQueryComplete(text: string) {
/* ===== 打断按钮 ===== */ /* ===== 打断按钮 ===== */
.interrupt-btn { .interrupt-btn {
flex-shrink: 0; flex-shrink: 0;
width: 72px; width: 110px;
height: 72px; height: 110px;
border-radius: 20px; border-radius: 30px;
border: 2px solid rgba(251, 146, 60, 0.35); border: 2px solid rgba(251, 146, 60, 0.35);
background: rgba(251, 146, 60, 0.12); background: rgba(251, 146, 60, 0.12);
backdrop-filter: blur(12px); backdrop-filter: blur(12px);
@ -1025,55 +1045,34 @@ async function handleVoiceQueryComplete(text: string) {
background: rgba(251, 146, 60, 0.22); background: rgba(251, 146, 60, 0.22);
border-color: rgba(251, 146, 60, 0.6); border-color: rgba(251, 146, 60, 0.6);
box-shadow: 0 0 32px rgba(251, 146, 60, 0.25); box-shadow: 0 0 32px rgba(251, 146, 60, 0.25);
transform: scale(1.06);
} }
&:active { &:active {
background: rgba(251, 146, 60, 0.3); background: rgba(251, 146, 60, 0.3);
border-color: rgba(251, 146, 60, 0.8); border-color: rgba(251, 146, 60, 0.8);
box-shadow: 0 0 48px rgba(251, 146, 60, 0.4); box-shadow: 0 0 48px rgba(251, 146, 60, 0.4);
transform: scale(0.95);
} }
} }
/* ===== 挂断按钮 ===== */ /* ===== 挂断按钮 ===== */
.hangup-btn { .hangup-btn {
flex-shrink: 0; padding: 0;
width: 72px; border: none;
height: 72px; background: none;
border-radius: 20px;
border: 2px solid rgba(239, 68, 68, 0.35);
background: rgba(239, 68, 68, 0.12);
backdrop-filter: blur(12px);
color: #f87171;
cursor: pointer; cursor: pointer;
display: flex; transition: transform 0.2s ease;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
transition: all 0.25s ease;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
.hangup-label { .hangup-img {
font-size: 10px; width: 80px;
font-weight: 600; height: 80px;
opacity: 0.85; object-fit: contain;
} }
&:hover { &:hover {
background: rgba(239, 68, 68, 0.22); transform: scale(1.1);
border-color: rgba(239, 68, 68, 0.6);
box-shadow: 0 0 32px rgba(239, 68, 68, 0.25);
transform: scale(1.06);
} }
&:active { &:active {
background: rgba(239, 68, 68, 0.3);
border-color: rgba(239, 68, 68, 0.8);
box-shadow: 0 0 48px rgba(239, 68, 68, 0.4);
transform: scale(0.95); transform: scale(0.95);
} }
} }
@ -1124,15 +1123,24 @@ async function handleVoiceQueryComplete(text: string) {
width: 65%; width: 65%;
} }
.side-controls { .mic-bottom-left {
right: 12px; left: 50%;
gap: 8px; transform: translateX(-100px);
bottom: 10%;
}
.mic-bottom-left:hover {
transform: translateX(-100px) scale(1.06);
}
.mic-bottom-left:active {
transform: translateX(-100px) scale(0.95);
} }
.mic-btn { .mic-btn {
width: 52px; width: 70px;
height: 52px; height: 70px;
border-radius: 16px; border-radius: 20px;
.mic-label { .mic-label {
font-size: 8px; font-size: 8px;
@ -1143,24 +1151,39 @@ async function handleVoiceQueryComplete(text: string) {
border-radius: 20px; border-radius: 20px;
} }
.interrupt-bottom-right {
right: 50%;
transform: translateX(100px);
bottom: 10%;
}
.interrupt-bottom-right:hover {
transform: translateX(100px) scale(1.06);
}
.interrupt-bottom-right:active {
transform: translateX(100px) scale(0.95);
}
.interrupt-btn { .interrupt-btn {
width: 52px; width: 70px;
height: 52px; height: 70px;
border-radius: 16px; border-radius: 20px;
.interrupt-label { .interrupt-label {
font-size: 8px; font-size: 8px;
} }
} }
.hangup-btn {
width: 52px;
height: 52px;
border-radius: 16px;
.hangup-label { .hangup-top-left {
font-size: 8px; right: 16px;
top: 16px;
} }
.hangup-img {
width: 56px;
height: 56px;
} }
.config-row { .config-row {
@ -1171,6 +1194,7 @@ async function handleVoiceQueryComplete(text: string) {
text-align: left; text-align: left;
min-width: auto; min-width: auto;
} }
input { input {
padding: 10px 14px; padding: 10px 14px;
font-size: 13px; font-size: 13px;
@ -1193,30 +1217,58 @@ async function handleVoiceQueryComplete(text: string) {
padding: 20px 28px; padding: 20px 28px;
} }
.side-controls { .mic-bottom-left {
right: 16px; left: 50%;
transform: translateX(-110px);
bottom: 10%;
}
.mic-bottom-left:hover {
transform: translateX(-110px) scale(1.06);
}
.mic-bottom-left:active {
transform: translateX(-110px) scale(0.95);
} }
.mic-btn { .mic-btn {
width: 60px; width: 80px;
height: 60px; height: 80px;
border-radius: 18px; border-radius: 22px;
} }
.mic-btn-ring { .mic-btn-ring {
border-radius: 22px; border-radius: 22px;
} }
.interrupt-btn { .interrupt-bottom-right {
width: 60px; right: 50%;
height: 60px; transform: translateX(110px);
border-radius: 18px; bottom: 10%;
} }
.hangup-btn { .interrupt-bottom-right:hover {
width: 60px; transform: translateX(110px) scale(1.06);
height: 60px; }
border-radius: 18px;
.interrupt-bottom-right:active {
transform: translateX(110px) scale(0.95);
}
.interrupt-btn {
width: 80px;
height: 80px;
border-radius: 22px;
}
.hangup-top-left {
right: 20px;
top: 20px;
}
.hangup-img {
width: 64px;
height: 64px;
} }
} }
@ -1253,15 +1305,24 @@ async function handleVoiceQueryComplete(text: string) {
width: 70%; width: 70%;
} }
.side-controls { .mic-bottom-left {
right: 10px; left: 50%;
gap: 6px; transform: translateX(-90px);
bottom: 8%;
}
.mic-bottom-left:hover {
transform: translateX(-90px) scale(1.06);
}
.mic-bottom-left:active {
transform: translateX(-90px) scale(0.95);
} }
.mic-btn { .mic-btn {
width: 48px; width: 64px;
height: 48px; height: 64px;
border-radius: 14px; border-radius: 18px;
.mic-label { .mic-label {
font-size: 8px; font-size: 8px;
@ -1272,24 +1333,38 @@ async function handleVoiceQueryComplete(text: string) {
border-radius: 18px; border-radius: 18px;
} }
.interrupt-bottom-right {
right: 50%;
transform: translateX(90px);
bottom: 8%;
}
.interrupt-bottom-right:hover {
transform: translateX(90px) scale(1.06);
}
.interrupt-bottom-right:active {
transform: translateX(90px) scale(0.95);
}
.interrupt-btn { .interrupt-btn {
width: 48px; width: 64px;
height: 48px; height: 64px;
border-radius: 14px; border-radius: 18px;
.interrupt-label { .interrupt-label {
font-size: 8px; font-size: 8px;
} }
} }
.hangup-btn { .hangup-top-left {
width: 48px; right: 14px;
height: 48px; top: 14px;
border-radius: 14px;
.hangup-label {
font-size: 8px;
} }
.hangup-img {
width: 52px;
height: 52px;
} }
} }
</style> </style>

BIN
src/GP.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB