|
|
<script setup lang="ts">
|
|
|
import { ref, onMounted, onUnmounted, watchEffect } from "vue";
|
|
|
import { InfoFilled } from "@element-plus/icons-vue";
|
|
|
import { message } from "@/utils/message";
|
|
|
import { interruptTalk } from "@/api/chat";
|
|
|
const liveList = ref([
|
|
|
{
|
|
|
name: "livetalking",
|
|
|
status: "1"
|
|
|
},
|
|
|
{
|
|
|
name: "gptsovits",
|
|
|
status: "1"
|
|
|
},
|
|
|
{
|
|
|
name: "chat",
|
|
|
status: "0"
|
|
|
}
|
|
|
]);
|
|
|
|
|
|
const livetlking_enable_status = ref<any>("0");
|
|
|
const gptsovits_enable_status = ref<any>("0");
|
|
|
const chat_enable_status = ref<any>("0");
|
|
|
const statusMap = [
|
|
|
{ key: "livetalking", statusRef: livetlking_enable_status },
|
|
|
{ key: "gptsovits", statusRef: gptsovits_enable_status },
|
|
|
{ key: "chat", statusRef: chat_enable_status }
|
|
|
];
|
|
|
// 监听所有状态变化,同步到liveList
|
|
|
watchEffect(() => {
|
|
|
// 遍历映射关系,逐个同步
|
|
|
statusMap.forEach(({ key, statusRef }) => {
|
|
|
// 找到liveList中name匹配的项
|
|
|
const targetItem = liveList.value.find(item => item.name === key);
|
|
|
if (targetItem) {
|
|
|
// 同步status值(如果需要转换状态格式,可在此处处理)
|
|
|
targetItem.status = statusRef.value;
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
const bulletSetList = ref([
|
|
|
{
|
|
|
title: "进入直播间",
|
|
|
key: "reply_prob_enter_live_room",
|
|
|
value: 100
|
|
|
},
|
|
|
{
|
|
|
title: "关注",
|
|
|
key: "reply_prob_follow",
|
|
|
value: 100
|
|
|
},
|
|
|
{
|
|
|
title: "送礼物",
|
|
|
key: "reply_prob_gift",
|
|
|
value: 100
|
|
|
},
|
|
|
{
|
|
|
title: "点赞",
|
|
|
key: "reply_prob_like",
|
|
|
value: 100
|
|
|
},
|
|
|
{
|
|
|
title: "弹幕回复",
|
|
|
key: "reply_prob_chat",
|
|
|
value: 100
|
|
|
}
|
|
|
]);
|
|
|
const interactiveNode = ref<any>("1");
|
|
|
//是否显示弹幕
|
|
|
const isShowBullet = ref(false);
|
|
|
// 是否启动ai互动
|
|
|
const isAIInteractionEnabled = ref(false);
|
|
|
//启用人工接管
|
|
|
const isManualTakeoverEnabled = ref(false);
|
|
|
const getStatusText = val => {
|
|
|
switch (val) {
|
|
|
case "2":
|
|
|
return "启动成功";
|
|
|
case "1":
|
|
|
return "启动中";
|
|
|
case "3":
|
|
|
return "启动失败";
|
|
|
case "0":
|
|
|
return "未启动";
|
|
|
}
|
|
|
};
|
|
|
const start = async item => {
|
|
|
if (item.name === "livetalking") {
|
|
|
window.electronAPI.startProcess("LiveTalking", "LiveTalking.exe");
|
|
|
} else if (item.name === "gptsovits") {
|
|
|
window.electronAPI.startProcess("gptsovits", "go-gptsovits.bat");
|
|
|
} else if (item.name === "chat") {
|
|
|
const liveRoom = await window.electronAPI.getConfig(
|
|
|
"live_config",
|
|
|
"live_id"
|
|
|
);
|
|
|
if (liveRoom) {
|
|
|
window.electronAPI.startProcess("chat", "chat.exe");
|
|
|
} else {
|
|
|
message("请填写房间号", { type: "error" });
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
const getLiveTalkingResult = async () => {
|
|
|
livetlking_enable_status.value = await window.electronAPI.getConfig(
|
|
|
"live_config",
|
|
|
"livetlking_enable_status"
|
|
|
);
|
|
|
gptsovits_enable_status.value = await window.electronAPI.getConfig(
|
|
|
"live_config",
|
|
|
"gptsovits_enable_status"
|
|
|
);
|
|
|
chat_enable_status.value = await window.electronAPI.getConfig(
|
|
|
"live_config",
|
|
|
"chat_enable_status"
|
|
|
);
|
|
|
};
|
|
|
const resetStart = async item => {
|
|
|
if (item.name === "chat") {
|
|
|
window.electronAPI.stopChatProcesses();
|
|
|
setTimeout(() => {
|
|
|
start(item);
|
|
|
}, 500);
|
|
|
} else {
|
|
|
window.electronAPI.updateConfig(
|
|
|
"live_config",
|
|
|
"livetlking_enable_status",
|
|
|
"0"
|
|
|
);
|
|
|
window.electronAPI.updateConfig(
|
|
|
"live_config",
|
|
|
"gptsovits_enable_status",
|
|
|
"0"
|
|
|
);
|
|
|
window.electronAPI.updateConfig("live_config", "chat_enable_status", "0");
|
|
|
window.electronAPI.stopAllProcesses();
|
|
|
setTimeout(async () => {
|
|
|
init();
|
|
|
}, 2000);
|
|
|
}
|
|
|
};
|
|
|
const changeNumber = (e, item) => {
|
|
|
window.electronAPI.updateConfig("config", item.key, e);
|
|
|
};
|
|
|
const changeType = async e => {
|
|
|
const livetalking_sessionid = await window.electronAPI.getConfig(
|
|
|
"live_config",
|
|
|
"livetalking_sessionid"
|
|
|
);
|
|
|
const res = await interruptTalk({
|
|
|
current_control_mode: e ? 0 : 1,
|
|
|
sessionid: Number(livetalking_sessionid)
|
|
|
});
|
|
|
if (res.code === 200) {
|
|
|
message("设置成功", { type: "success" });
|
|
|
} else {
|
|
|
message(res.message || "设置失败", { type: "error" });
|
|
|
}
|
|
|
};
|
|
|
const init = () => {
|
|
|
window.electronAPI.startProcess("LiveTalking", "LiveTalking.exe");
|
|
|
window.electronAPI.startProcess("gptsovits", "go-gptsovits.bat");
|
|
|
// 启动定时器,每3秒执行一次
|
|
|
const checkInterval = setInterval(() => {
|
|
|
getLiveTalkingResult();
|
|
|
if (
|
|
|
livetlking_enable_status.value === "2" &&
|
|
|
gptsovits_enable_status.value === "2" &&
|
|
|
chat_enable_status.value === "2"
|
|
|
) {
|
|
|
clearInterval(checkInterval); // 清除定时器,停止检查
|
|
|
}
|
|
|
}, 500);
|
|
|
};
|
|
|
onMounted(() => {
|
|
|
init();
|
|
|
bulletSetList.value.forEach(async item => {
|
|
|
item.value = Number(await window.electronAPI.getConfig("config", item.key));
|
|
|
});
|
|
|
});
|
|
|
</script>
|
|
|
<template>
|
|
|
<div class="live-monitor">
|
|
|
<div class="monitor-top">
|
|
|
<div class="card-title">
|
|
|
<span>直播服务监测</span>
|
|
|
</div>
|
|
|
<div class="live-list">
|
|
|
<div class="live-item" v-for="(item, index) in liveList" :key="index">
|
|
|
<div
|
|
|
class="live-item-left"
|
|
|
:class="{
|
|
|
'status-success': item.status === '2',
|
|
|
'status-pending': item.status === '0',
|
|
|
'status-loading': item.status === '1',
|
|
|
'status-error': item.status === '3'
|
|
|
}"
|
|
|
>
|
|
|
<span>{{ item.name }}</span>
|
|
|
<span class="point" />
|
|
|
<span class="status">{{ getStatusText(item.status) }}</span>
|
|
|
<span />
|
|
|
</div>
|
|
|
|
|
|
<!-- 根据不同状态显示不同按钮文本 -->
|
|
|
<div class="btn" @click="start(item)" v-if="item.status === '0'">
|
|
|
启动
|
|
|
</div>
|
|
|
<div class="loading btn" v-else-if="item.status === '1'">启动中</div>
|
|
|
<div
|
|
|
class="btn"
|
|
|
@click="resetStart(item)"
|
|
|
v-else-if="item.status === '3' || item.status === '2'"
|
|
|
>
|
|
|
重启
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="monitor-mid">
|
|
|
<div class="card-title">
|
|
|
<div class="title">弹幕与互动</div>
|
|
|
<div class="card-title-right">
|
|
|
<span>显示弹幕</span>
|
|
|
<el-switch v-model="isShowBullet" />
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="monitor-mid-content">
|
|
|
<div class="bullet-content">
|
|
|
<span>启动后弹幕会打印在屏幕上 ,未启用默认为空</span>
|
|
|
</div>
|
|
|
<div class="interaction-item">
|
|
|
<div class="set-title">互动设置</div>
|
|
|
<div class="set-content">
|
|
|
<el-icon><InfoFilled /></el-icon>
|
|
|
<span>启动AI互动</span>
|
|
|
<el-switch v-model="isAIInteractionEnabled" />
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="mid-content-title">回复方式</div>
|
|
|
<el-radio-group v-model="interactiveNode">
|
|
|
<el-radio label="1" size="large">话术类型</el-radio>
|
|
|
<el-radio label="2" size="large">话术段落</el-radio>
|
|
|
</el-radio-group>
|
|
|
<div class="bullet-set">
|
|
|
<div
|
|
|
class="bullet-set-item"
|
|
|
v-for="(item, index) in bulletSetList"
|
|
|
:key="index"
|
|
|
>
|
|
|
<span>{{ item.title }}</span>
|
|
|
<el-input-number
|
|
|
@change="e => changeNumber(e, item)"
|
|
|
v-model="item.value"
|
|
|
:min="0"
|
|
|
:max="100"
|
|
|
/>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="monitor-bottom">
|
|
|
<div class="card-title">
|
|
|
<div class="title">人工接管</div>
|
|
|
</div>
|
|
|
<div class="monitor-bottom-main">
|
|
|
<span>启用人工接管</span>
|
|
|
<el-switch @change="changeType" v-model="isManualTakeoverEnabled" />
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
<style lang="scss" scoped>
|
|
|
.live-monitor {
|
|
|
width: 528px;
|
|
|
margin-left: 16px;
|
|
|
.monitor-top {
|
|
|
background: #ffffff;
|
|
|
height: 236px;
|
|
|
box-shadow: 0px 4px 8px 0px rgba(46, 128, 250, 0.1);
|
|
|
border-radius: 8px 8px 8px 8px;
|
|
|
|
|
|
.card-title {
|
|
|
border-bottom: 1px solid rgba(46, 128, 250, 0.1);
|
|
|
line-height: 54px;
|
|
|
height: 54px;
|
|
|
padding-left: 16px;
|
|
|
font-weight: bold;
|
|
|
font-size: 16px;
|
|
|
color: #1d2129;
|
|
|
}
|
|
|
.live-list {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
padding: 16px 16px 0 16px;
|
|
|
.live-item {
|
|
|
height: 40px;
|
|
|
background: rgba(46, 128, 250, 0.1);
|
|
|
border-radius: 2px 2px 2px 2px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
padding: 0 16px;
|
|
|
justify-content: space-between;
|
|
|
margin-bottom: 16px;
|
|
|
.live-item-left {
|
|
|
font-weight: 500;
|
|
|
font-size: 14px;
|
|
|
color: #1d2129;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
.point {
|
|
|
width: 6px;
|
|
|
height: 6px;
|
|
|
// background: #c9cdd4;
|
|
|
margin-left: 12px;
|
|
|
margin-right: 4px;
|
|
|
border-radius: 50%;
|
|
|
display: block;
|
|
|
}
|
|
|
}
|
|
|
.btn {
|
|
|
width: 50px;
|
|
|
height: 28px;
|
|
|
background: #ffffff;
|
|
|
border-radius: 4px 4px 4px 4px;
|
|
|
border: 1px solid #2e80fa;
|
|
|
font-weight: 500;
|
|
|
font-size: 14px;
|
|
|
color: #2e80fa;
|
|
|
text-align: center;
|
|
|
line-height: 28px;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
.loading {
|
|
|
opacity: 0.6;
|
|
|
}
|
|
|
.status-success {
|
|
|
.point {
|
|
|
background: rgba(0, 180, 42, 1);
|
|
|
}
|
|
|
.status {
|
|
|
color: rgba(0, 180, 42, 1);
|
|
|
}
|
|
|
}
|
|
|
.status-pending {
|
|
|
.point {
|
|
|
background: #c9cdd4;
|
|
|
}
|
|
|
.status {
|
|
|
color: #c9cdd4;
|
|
|
}
|
|
|
}
|
|
|
.status-loading {
|
|
|
.point {
|
|
|
background: #2e80fa;
|
|
|
}
|
|
|
.status {
|
|
|
color: #2e80fa;
|
|
|
}
|
|
|
}
|
|
|
.status-error {
|
|
|
.point {
|
|
|
background: rgba(234, 42, 42, 1);
|
|
|
}
|
|
|
.status {
|
|
|
color: rgba(234, 42, 42, 1);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.monitor-mid {
|
|
|
margin-top: 16px;
|
|
|
background: #ffffff;
|
|
|
box-shadow: 0px 4px 8px 0px rgba(46, 128, 250, 0.1);
|
|
|
border-radius: 8px 8px 8px 8px;
|
|
|
.card-title {
|
|
|
border-bottom: 1px solid rgba(46, 128, 250, 0.1);
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
height: 54px;
|
|
|
padding: 0 16px;
|
|
|
font-weight: bold;
|
|
|
font-size: 16px;
|
|
|
color: #1d2129;
|
|
|
.card-title-right {
|
|
|
font-weight: 500;
|
|
|
font-size: 14px;
|
|
|
color: #1d2129;
|
|
|
span {
|
|
|
margin-right: 12px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.monitor-mid-content {
|
|
|
padding: 16px;
|
|
|
.bullet-content {
|
|
|
width: 496px;
|
|
|
height: 200px;
|
|
|
background: #000000;
|
|
|
border-radius: 4px 4px 4px 4px;
|
|
|
padding: 12px;
|
|
|
span {
|
|
|
font-family: Alibaba PuHuiTi 2, Alibaba PuHuiTi 20;
|
|
|
font-weight: normal;
|
|
|
font-size: 14px;
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
}
|
|
|
.interaction-item {
|
|
|
display: flex;
|
|
|
margin-top: 16px;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
.set-title {
|
|
|
font-weight: bold;
|
|
|
font-size: 14px;
|
|
|
color: #1d2129;
|
|
|
}
|
|
|
.set-content {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
font-weight: 500;
|
|
|
font-size: 14px;
|
|
|
color: #1d2129;
|
|
|
span {
|
|
|
margin: 0 4px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.mid-content-title {
|
|
|
margin-top: 12px;
|
|
|
font-weight: 500;
|
|
|
font-size: 14px;
|
|
|
color: #1d2129;
|
|
|
}
|
|
|
.bullet-set {
|
|
|
display: flex;
|
|
|
flex-wrap: wrap;
|
|
|
.bullet-set-item {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
width: 150px;
|
|
|
font-weight: 500;
|
|
|
font-size: 14px;
|
|
|
color: #1e1e1e;
|
|
|
margin: 0 12px 12px 0;
|
|
|
span {
|
|
|
margin-bottom: 6px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.monitor-bottom {
|
|
|
margin-top: 16px;
|
|
|
background: #ffffff;
|
|
|
box-shadow: 0px 4px 8px 0px rgba(46, 128, 250, 0.1);
|
|
|
border-radius: 8px 8px 8px 8px;
|
|
|
.card-title {
|
|
|
border-bottom: 1px solid rgba(46, 128, 250, 0.1);
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
height: 54px;
|
|
|
padding: 0 16px;
|
|
|
font-weight: bold;
|
|
|
font-size: 16px;
|
|
|
color: #1d2129;
|
|
|
}
|
|
|
.monitor-bottom-main {
|
|
|
padding: 16px;
|
|
|
span {
|
|
|
margin-right: 10px;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</style>
|