仅看ai
This commit is contained in:
parent
006aa04a7f
commit
bb7fea71f1
@ -69,12 +69,7 @@
|
||||
<!-- 字幕区域(底部,不遮挡数字人主体) -->
|
||||
<div class="subtitle-overlay">
|
||||
<div class="subtitle-container">
|
||||
<SubtitlePanel
|
||||
v-model:mode="subtitleMode"
|
||||
:ai-subtitle="currentAISubtitle"
|
||||
:user-subtitle="currentUserSubtitle"
|
||||
:messages="chatMessages"
|
||||
/>
|
||||
<SubtitlePanel :ai-subtitle="currentAISubtitle" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -1,57 +1,6 @@
|
||||
<template>
|
||||
<div class="subtitle-panel">
|
||||
<!-- 字幕模式下拉框 -->
|
||||
<div class="subtitle-header">
|
||||
<div class="dropdown-wrapper" :class="{ open: dropdownOpen }">
|
||||
<button class="dropdown-trigger" @click="dropdownOpen = !dropdownOpen">
|
||||
<span class="dropdown-label">{{ modeLabel }}</span>
|
||||
<svg
|
||||
class="dropdown-arrow"
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
height="14"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9" />
|
||||
</svg>
|
||||
</button>
|
||||
<Transition name="dropdown">
|
||||
<div v-if="dropdownOpen" class="dropdown-menu">
|
||||
<button
|
||||
v-for="opt in modeOptions"
|
||||
:key="opt.value"
|
||||
class="dropdown-item"
|
||||
:class="{ active: currentMode === opt.value }"
|
||||
@click="selectMode(opt.value)"
|
||||
>
|
||||
<span class="item-icon">{{ opt.icon }}</span>
|
||||
<span class="item-label">{{ opt.label }}</span>
|
||||
<svg
|
||||
v-if="currentMode === opt.value"
|
||||
viewBox="0 0 24 24"
|
||||
width="14"
|
||||
height="14"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="3"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模式1:仅AI字幕 -->
|
||||
<div
|
||||
v-if="currentMode === 'ai-only'"
|
||||
class="subtitle-content ai-only-mode"
|
||||
ref="scrollContainerRef"
|
||||
>
|
||||
@ -99,224 +48,23 @@
|
||||
等待AI回复...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模式2:对话气泡 -->
|
||||
<div
|
||||
v-if="currentMode === 'bubble'"
|
||||
class="subtitle-content bubble-mode"
|
||||
ref="scrollContainerRef"
|
||||
>
|
||||
<div v-if="messages.length === 0" class="subtitle-placeholder">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
>
|
||||
<path
|
||||
d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
|
||||
/>
|
||||
</svg>
|
||||
等待对话开始...
|
||||
</div>
|
||||
<div class="chat-list" ref="chatListRef">
|
||||
<div
|
||||
v-for="msg in messages"
|
||||
:key="msg.id"
|
||||
:class="[
|
||||
'chat-bubble',
|
||||
msg.role === 'user' ? 'bubble-user' : 'bubble-ai',
|
||||
]"
|
||||
>
|
||||
<div class="bubble-body">
|
||||
<div
|
||||
class="bubble-text"
|
||||
:class="{
|
||||
collapsed: isCollapsed(msg.id) && isLongText(msg.content),
|
||||
'text-user': msg.role === 'user',
|
||||
'text-ai': msg.role === 'ai',
|
||||
}"
|
||||
:ref="(el) => setMsgRef(msg.id, el)"
|
||||
>
|
||||
{{ msg.content }}
|
||||
</div>
|
||||
<button
|
||||
v-if="isLongText(msg.content)"
|
||||
class="expand-btn"
|
||||
@click="toggleCollapse(msg.id)"
|
||||
>
|
||||
{{ isCollapsed(msg.id) ? "展开全部" : "收起" }}
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="12"
|
||||
height="12"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
:style="{
|
||||
transform: isCollapsed(msg.id)
|
||||
? 'rotate(0deg)'
|
||||
: 'rotate(180deg)',
|
||||
transition: 'transform 0.2s',
|
||||
}"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模式3:合并气泡 -->
|
||||
<div
|
||||
v-if="currentMode === 'merged'"
|
||||
class="subtitle-content merged-mode"
|
||||
ref="scrollContainerRef"
|
||||
>
|
||||
<div v-if="messages.length === 0" class="subtitle-placeholder">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="20"
|
||||
height="20"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
>
|
||||
<path
|
||||
d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
|
||||
/>
|
||||
</svg>
|
||||
等待对话开始...
|
||||
</div>
|
||||
<div class="merged-list" ref="mergedListRef">
|
||||
<div
|
||||
v-for="(pair, idx) in pairedMessages"
|
||||
:key="idx"
|
||||
class="merged-bubble"
|
||||
>
|
||||
<div v-if="pair.user" class="merged-user-section">
|
||||
<div
|
||||
class="merged-text text-user"
|
||||
:class="{
|
||||
collapsed:
|
||||
isCollapsed(pair.user.id) && isLongText(pair.user.content),
|
||||
}"
|
||||
>
|
||||
{{ pair.user.content }}
|
||||
</div>
|
||||
<button
|
||||
v-if="isLongText(pair.user.content)"
|
||||
class="expand-btn"
|
||||
@click="toggleCollapse(pair.user.id)"
|
||||
>
|
||||
{{ isCollapsed(pair.user.id) ? "展开全部" : "收起" }}
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="12"
|
||||
height="12"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
:style="{
|
||||
transform: isCollapsed(pair.user.id)
|
||||
? 'rotate(0deg)'
|
||||
: 'rotate(180deg)',
|
||||
transition: 'transform 0.2s',
|
||||
}"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="pair.ai" class="merged-ai-section">
|
||||
<div
|
||||
class="merged-text text-ai"
|
||||
:class="{
|
||||
collapsed:
|
||||
isCollapsed(pair.ai.id) && isLongText(pair.ai.content),
|
||||
}"
|
||||
>
|
||||
{{ pair.ai.content }}
|
||||
</div>
|
||||
<button
|
||||
v-if="isLongText(pair.ai.content)"
|
||||
class="expand-btn"
|
||||
@click="toggleCollapse(pair.ai.id)"
|
||||
>
|
||||
{{ isCollapsed(pair.ai.id) ? "展开全部" : "收起" }}
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
width="12"
|
||||
height="12"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
:style="{
|
||||
transform: isCollapsed(pair.ai.id)
|
||||
? 'rotate(0deg)'
|
||||
: 'rotate(180deg)',
|
||||
transition: 'transform 0.2s',
|
||||
}"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
watch,
|
||||
nextTick,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
} from "vue";
|
||||
import type { ChatMessage, SubtitleMode } from "@/types";
|
||||
import { ref, watch, nextTick } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
mode: SubtitleMode;
|
||||
aiSubtitle: string;
|
||||
userSubtitle: string;
|
||||
messages: ChatMessage[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:mode": [value: SubtitleMode];
|
||||
}>();
|
||||
|
||||
const currentMode = computed({
|
||||
get: () => props.mode,
|
||||
set: (val: SubtitleMode) => emit("update:mode", val),
|
||||
});
|
||||
|
||||
const dropdownOpen = ref(false);
|
||||
// 滚动容器ref:指向 .subtitle-content(实际可滚动容器)
|
||||
const scrollContainerRef = ref<HTMLElement | null>(null);
|
||||
const aiSubtitleBarRef = ref<HTMLElement | null>(null);
|
||||
const aiSubtitleTextRef = ref<HTMLElement | null>(null);
|
||||
|
||||
// ===== AI字幕自适应字号 =====
|
||||
const aiFontSize = ref(18);
|
||||
const aiTextOverflow = ref(false);
|
||||
|
||||
// 计算AI字幕字号:根据文本长度动态调整
|
||||
const computeAIFontSize = () => {
|
||||
const text = props.aiSubtitle;
|
||||
if (!text) {
|
||||
@ -332,7 +80,6 @@ const computeAIFontSize = () => {
|
||||
else aiFontSize.value = 12;
|
||||
};
|
||||
|
||||
// 检测AI字幕是否溢出
|
||||
const checkAIOverflow = () => {
|
||||
nextTick(() => {
|
||||
const bar = aiSubtitleBarRef.value;
|
||||
@ -352,118 +99,20 @@ const scrollAIToBottom = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// ===== 长文本折叠/展开 =====
|
||||
const COLLAPSE_THRESHOLD = 120; // 超过120字符视为长文本
|
||||
const collapsedIds = ref<Set<string>>(new Set());
|
||||
const msgRefs = new Map<string, HTMLElement>();
|
||||
|
||||
const isLongText = (text: string): boolean => text.length > COLLAPSE_THRESHOLD;
|
||||
|
||||
const isCollapsed = (id: string): boolean => collapsedIds.value.has(id);
|
||||
|
||||
const toggleCollapse = (id: string) => {
|
||||
if (collapsedIds.value.has(id)) {
|
||||
collapsedIds.value.delete(id);
|
||||
} else {
|
||||
collapsedIds.value.add(id);
|
||||
}
|
||||
// 触发响应式更新
|
||||
collapsedIds.value = new Set(collapsedIds.value);
|
||||
const scrollToBottom = () => {
|
||||
const el = scrollContainerRef.value;
|
||||
if (!el) return;
|
||||
el.scrollTo({
|
||||
top: el.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
};
|
||||
|
||||
const setMsgRef = (id: string, el: any) => {
|
||||
if (el) msgRefs.set(id, el as HTMLElement);
|
||||
};
|
||||
|
||||
// ===== 下拉框 =====
|
||||
const modeOptions = [
|
||||
{ value: "ai-only" as SubtitleMode, label: "AI字幕", icon: "💬" },
|
||||
{ value: "bubble" as SubtitleMode, label: "对话气泡", icon: "💭" },
|
||||
{ value: "merged" as SubtitleMode, label: "合并气泡", icon: "📋" },
|
||||
];
|
||||
|
||||
const modeLabel = computed(() => {
|
||||
return (
|
||||
modeOptions.find((o) => o.value === currentMode.value)?.label || "AI字幕"
|
||||
);
|
||||
});
|
||||
|
||||
const selectMode = (val: SubtitleMode) => {
|
||||
currentMode.value = val;
|
||||
dropdownOpen.value = false;
|
||||
};
|
||||
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
if (!target.closest(".dropdown-wrapper")) {
|
||||
dropdownOpen.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
});
|
||||
|
||||
// ===== 消息配对 =====
|
||||
const pairedMessages = computed(() => {
|
||||
const pairs: { user: ChatMessage | null; ai: ChatMessage | null }[] = [];
|
||||
let currentPair: { user: ChatMessage | null; ai: ChatMessage | null } = {
|
||||
user: null,
|
||||
ai: null,
|
||||
};
|
||||
|
||||
for (const msg of props.messages) {
|
||||
if (msg.role === "user") {
|
||||
if (currentPair.user) {
|
||||
pairs.push(currentPair);
|
||||
currentPair = { user: msg, ai: null };
|
||||
} else {
|
||||
currentPair.user = msg;
|
||||
}
|
||||
} else {
|
||||
currentPair.ai = msg;
|
||||
pairs.push(currentPair);
|
||||
currentPair = { user: null, ai: null };
|
||||
}
|
||||
}
|
||||
if (currentPair.user || currentPair.ai) {
|
||||
pairs.push(currentPair);
|
||||
}
|
||||
return pairs;
|
||||
});
|
||||
|
||||
// ===== 监听变化 =====
|
||||
|
||||
// 模式切换时:立即刷新内容并滚动到底部
|
||||
watch(
|
||||
() => props.mode,
|
||||
() => {
|
||||
computeAIFontSize();
|
||||
nextTick(() => {
|
||||
checkAIOverflow();
|
||||
scrollToBottom();
|
||||
// AI模式切换后确保字幕滚动到底部
|
||||
if (props.mode === "ai-only" && aiSubtitleBarRef.value) {
|
||||
aiSubtitleBarRef.value.scrollTo({
|
||||
top: aiSubtitleBarRef.value.scrollHeight,
|
||||
behavior: "instant" as ScrollBehavior,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// AI字幕变化时:自适应字号 + 检测溢出 + 自动滚动
|
||||
watch(
|
||||
() => props.aiSubtitle,
|
||||
() => {
|
||||
computeAIFontSize();
|
||||
checkAIOverflow();
|
||||
// 自动滚动AI字幕到底部
|
||||
nextTick(() => {
|
||||
if (aiSubtitleBarRef.value) {
|
||||
aiSubtitleBarRef.value.scrollTo({
|
||||
@ -474,55 +123,6 @@ watch(
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// 消息变化时:自动滚动到底部 + 新消息默认折叠
|
||||
watch(
|
||||
() => props.messages.length,
|
||||
(newLen, oldLen) => {
|
||||
// 新增的消息默认折叠
|
||||
if (newLen > (oldLen ?? 0)) {
|
||||
for (let i = oldLen ?? 0; i < newLen; i++) {
|
||||
const msg = props.messages[i];
|
||||
if (msg && isLongText(msg.content)) {
|
||||
collapsedIds.value.add(msg.id);
|
||||
}
|
||||
}
|
||||
collapsedIds.value = new Set(collapsedIds.value);
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
scrollToBottom();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// 流式内容更新时:自动滚动到底部(消息数量不变但内容变化)
|
||||
watch(
|
||||
() => {
|
||||
const msgs = props.messages;
|
||||
if (msgs.length === 0) return "";
|
||||
// 监听最后一条消息的内容变化,用于流式更新时自动滚动
|
||||
return msgs[msgs.length - 1].content;
|
||||
},
|
||||
() => {
|
||||
nextTick(() => {
|
||||
scrollToBottom();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 将对话列表滚动到底部
|
||||
* 兼容不同内容量:内容少时无需滚动,内容超出可见区域时平滑滚动到底部
|
||||
*/
|
||||
const scrollToBottom = () => {
|
||||
const el = scrollContainerRef.value;
|
||||
if (!el) return;
|
||||
el.scrollTo({
|
||||
top: el.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@ -532,109 +132,6 @@ const scrollToBottom = () => {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* ===== 下拉框 ===== */
|
||||
.subtitle-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
margin-bottom: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dropdown-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 14px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
.dropdown-wrapper.open & {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: rgba(108, 140, 255, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
transition: transform 0.2s ease;
|
||||
.dropdown-wrapper.open & {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
bottom: calc(100% + 8px);
|
||||
left: 0;
|
||||
min-width: 180px;
|
||||
background: rgba(20, 25, 50, 0.97);
|
||||
backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 12px;
|
||||
padding: 6px;
|
||||
z-index: 100;
|
||||
box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: #fff;
|
||||
}
|
||||
&.active {
|
||||
background: rgba(108, 140, 255, 0.15);
|
||||
color: #6c8cff;
|
||||
}
|
||||
.item-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
.item-label {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-enter-active,
|
||||
.dropdown-leave-active {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.dropdown-enter-from,
|
||||
.dropdown-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(6px) scale(0.96);
|
||||
}
|
||||
|
||||
/* ===== 字幕内容 ===== */
|
||||
.subtitle-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@ -672,7 +169,6 @@ const scrollToBottom = () => {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ===== 模式1:AI字幕 ===== */
|
||||
.ai-only-mode {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
@ -737,171 +233,4 @@ const scrollToBottom = () => {
|
||||
transform: translateX(-50%) scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 模式2:对话气泡 ===== */
|
||||
.bubble-mode {
|
||||
.chat-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-bubble {
|
||||
animation: bubbleSlide 0.35s ease;
|
||||
|
||||
&.bubble-user {
|
||||
.bubble-body {
|
||||
background: rgba(240, 242, 255, 0.92);
|
||||
border-color: rgba(79, 110, 247, 0.12);
|
||||
border-radius: 14px 14px 4px 14px;
|
||||
margin-left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
&.bubble-ai {
|
||||
.bubble-body {
|
||||
background: rgba(20, 30, 50, 0.85);
|
||||
border-color: rgba(52, 211, 153, 0.12);
|
||||
border-radius: 14px 14px 14px 4px;
|
||||
margin-right: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bubbleSlide {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px) scale(0.96);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.bubble-body {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bubble-text {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.35s ease;
|
||||
|
||||
&.text-user {
|
||||
color: #1a1a1a;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
&.text-ai {
|
||||
color: #ffffff;
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
max-height: 4.8em;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2em;
|
||||
background: linear-gradient(transparent, rgba(10, 14, 30, 0.5));
|
||||
pointer-events: none;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 展开/收起按钮 ===== */
|
||||
.expand-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-top: 6px;
|
||||
padding: 3px 10px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
color: rgba(255, 255, 255, 0.55);
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 模式3:合并气泡 ===== */
|
||||
.merged-mode {
|
||||
.merged-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.merged-bubble {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border: 1px solid rgba(255, 255, 255, 0.04);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
animation: bubbleSlide 0.35s ease;
|
||||
}
|
||||
|
||||
.merged-user-section {
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid rgba(79, 110, 247, 0.08);
|
||||
}
|
||||
|
||||
.merged-ai-section {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.merged-text {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
word-break: break-word;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.35s ease;
|
||||
|
||||
&.text-user {
|
||||
color: #303030ab;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
&.text-ai {
|
||||
color: #ffffff;
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
max-height: 4.8em;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2em;
|
||||
background: linear-gradient(transparent, rgba(10, 14, 30, 0.5));
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -14,8 +14,8 @@ const SILENCE_TIMEOUT_MS = 5000;
|
||||
const MIN_VALID_DURATION_SEC = 0.5;
|
||||
// 播报超时保护(毫秒):如果FINISHED回调未触发,自动恢复状态
|
||||
const BROADCAST_TIMEOUT_MS = 60000;
|
||||
const APPID = "i-sfkmd7h8ex6nd";
|
||||
const APP_KEY = "hs6b9nu9b080ap9hswgp";
|
||||
const APPID = "i-sfqpfp8qayj8e";
|
||||
const APP_KEY = "rdjqhnp5q50e010wvw0z";
|
||||
|
||||
/**
|
||||
* 检测文本是否包含实质内容(去除空白和标点后仍有字符)
|
||||
@ -496,8 +496,8 @@ export function useDigitalHuman(options?: {
|
||||
token: token.value,
|
||||
wrapperId: "human-wrapper",
|
||||
connectParams: {
|
||||
ttsPer: "CAP_4189",
|
||||
figureId: "3999826",
|
||||
ttsPer: "5135",
|
||||
figureId: "4000288",
|
||||
resolutionHeight: 1920,
|
||||
resolutionWidth: 1080,
|
||||
inactiveDisconnectSec: 300,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user