mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 15:13:55 +08:00
@@ -0,0 +1,105 @@
|
||||
# get_news_from_newsnow 插件新闻源配置指南
|
||||
|
||||
## 概述
|
||||
|
||||
`get_news_from_newsnow` 插件现在支持通过Web管理界面动态配置新闻源,不再需要修改代码。用户可以在智控台中为每个智能体配置不同的新闻源。
|
||||
|
||||
## 配置方式
|
||||
|
||||
### 1. 通过Web管理界面配置(推荐)
|
||||
|
||||
1. 登录智控台
|
||||
2. 进入"角色配置"页面
|
||||
3. 选择要配置的智能体
|
||||
4. 点击"编辑功能"按钮
|
||||
5. 在右侧参数配置区域找到"newsnow新闻聚合"插件
|
||||
6. 在"新闻源配置"字段中输入分号分隔的中文名称
|
||||
|
||||
### 2. 配置文件方式
|
||||
|
||||
在 `config.yaml` 中配置:
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
get_news_from_newsnow:
|
||||
url: "https://newsnow.busiyi.world/api/s?id="
|
||||
news_sources: "澎湃新闻;百度热搜;财联社;微博;抖音"
|
||||
```
|
||||
|
||||
## 新闻源配置格式
|
||||
|
||||
新闻源配置使用分号分隔的中文名称,格式为:
|
||||
|
||||
```
|
||||
中文名称1;中文名称2;中文名称3
|
||||
```
|
||||
|
||||
### 配置示例
|
||||
|
||||
```
|
||||
澎湃新闻;百度热搜;财联社;微博;抖音;知乎;36氪
|
||||
```
|
||||
|
||||
## 支持的新闻源
|
||||
|
||||
插件支持以下新闻源的中文名称:
|
||||
|
||||
- 澎湃新闻
|
||||
- 百度热搜
|
||||
- 财联社
|
||||
- 微博
|
||||
- 抖音
|
||||
- 知乎
|
||||
- 36氪
|
||||
- 华尔街见闻
|
||||
- IT之家
|
||||
- 今日头条
|
||||
- 虎扑
|
||||
- 哔哩哔哩
|
||||
- 快手
|
||||
- 雪球
|
||||
- 格隆汇
|
||||
- 法布财经
|
||||
- 金十数据
|
||||
- 牛客
|
||||
- 少数派
|
||||
- 稀土掘金
|
||||
- 凤凰网
|
||||
- 虫部落
|
||||
- 联合早报
|
||||
- 酷安
|
||||
- 远景论坛
|
||||
- 参考消息
|
||||
- 卫星通讯社
|
||||
- 百度贴吧
|
||||
- 靠谱新闻
|
||||
- 以及更多...
|
||||
|
||||
## 默认配置
|
||||
|
||||
如果未配置新闻源,插件将使用以下默认配置:
|
||||
|
||||
```
|
||||
澎湃新闻;百度热搜;财联社
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. **配置新闻源**:在Web界面或配置文件中设置新闻源的中文名称,用分号分隔
|
||||
2. **调用插件**:用户可以说"播报新闻"或"获取新闻"
|
||||
3. **指定新闻源**:用户可以说"播报澎湃新闻"或"获取百度热搜"
|
||||
4. **获取详情**:用户可以说"详细介绍这条新闻"
|
||||
|
||||
## 工作原理
|
||||
|
||||
1. 插件接受中文名称作为参数(如"澎湃新闻")
|
||||
2. 根据配置的新闻源列表,将中文名称转换为对应的英文ID(如"thepaper")
|
||||
3. 使用英文ID调用API获取新闻数据
|
||||
4. 返回新闻内容给用户
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 配置的中文名称必须与 CHANNEL_MAP 中定义的名称完全一致
|
||||
2. 配置更改后需要重启服务或重新加载配置
|
||||
3. 如果配置的新闻源无效,插件会自动使用默认新闻源
|
||||
4. 多个新闻源之间使用英文分号(;)分隔,不要使用中文分号(;)
|
||||
+12
@@ -19,6 +19,8 @@ import xiaozhi.modules.agent.service.AgentChatAudioService;
|
||||
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
||||
import xiaozhi.modules.agent.service.AgentService;
|
||||
import xiaozhi.modules.agent.service.biz.AgentChatHistoryBizService;
|
||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||
import xiaozhi.modules.device.service.DeviceService;
|
||||
|
||||
/**
|
||||
* {@link AgentChatHistoryBizService} impl
|
||||
@@ -35,6 +37,7 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
|
||||
private final AgentChatHistoryService agentChatHistoryService;
|
||||
private final AgentChatAudioService agentChatAudioService;
|
||||
private final RedisUtils redisUtils;
|
||||
private final DeviceService deviceService;
|
||||
|
||||
/**
|
||||
* 处理聊天记录上报,包括文件上传和相关信息记录
|
||||
@@ -68,6 +71,15 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
|
||||
|
||||
// 更新设备最后对话时间
|
||||
redisUtils.set(RedisKeys.getAgentDeviceLastConnectedAtById(agentId), new Date());
|
||||
|
||||
// 更新设备最后连接时间
|
||||
DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
|
||||
if (device != null) {
|
||||
deviceService.updateDeviceConnectionInfo(agentId, device.getId(), null);
|
||||
} else {
|
||||
log.warn("聊天记录上报时,未找到mac地址为 {} 的设备", macAddress);
|
||||
}
|
||||
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
|
||||
+26
-12
@@ -72,6 +72,8 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
agent.getVadModelId(),
|
||||
agent.getAsrModelId(),
|
||||
null,
|
||||
@@ -108,9 +110,13 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
}
|
||||
// 获取音色信息
|
||||
String voice = null;
|
||||
String referenceAudio = null;
|
||||
String referenceText = null;
|
||||
TimbreDetailsVO timbre = timbreService.get(agent.getTtsVoiceId());
|
||||
if (timbre != null) {
|
||||
voice = timbre.getTtsVoice();
|
||||
referenceAudio = timbre.getReferenceAudio();
|
||||
referenceText = timbre.getReferenceText();
|
||||
}
|
||||
// 构建返回数据
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
@@ -163,6 +169,8 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
agent.getSystemPrompt(),
|
||||
agent.getSummaryMemory(),
|
||||
voice,
|
||||
referenceAudio,
|
||||
referenceText,
|
||||
agent.getVadModelId(),
|
||||
agent.getAsrModelId(),
|
||||
agent.getLlmModelId(),
|
||||
@@ -179,7 +187,7 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
/**
|
||||
* 构建配置信息
|
||||
*
|
||||
* @param paramsList 系统参数列表
|
||||
* @param config 系统参数列表
|
||||
* @return 配置信息
|
||||
*/
|
||||
private Object buildConfig(Map<String, Object> config) {
|
||||
@@ -250,21 +258,25 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
/**
|
||||
* 构建模块配置
|
||||
*
|
||||
* @param prompt 提示词
|
||||
* @param voice 音色
|
||||
* @param vadModelId VAD模型ID
|
||||
* @param asrModelId ASR模型ID
|
||||
* @param llmModelId LLM模型ID
|
||||
* @param ttsModelId TTS模型ID
|
||||
* @param memModelId 记忆模型ID
|
||||
* @param intentModelId 意图模型ID
|
||||
* @param result 结果Map
|
||||
* @param prompt 提示词
|
||||
* @param voice 音色
|
||||
* @param referenceAudio 参考音频路径
|
||||
* @param referenceText 参考文本
|
||||
* @param vadModelId VAD模型ID
|
||||
* @param asrModelId ASR模型ID
|
||||
* @param llmModelId LLM模型ID
|
||||
* @param ttsModelId TTS模型ID
|
||||
* @param memModelId 记忆模型ID
|
||||
* @param intentModelId 意图模型ID
|
||||
* @param result 结果Map
|
||||
*/
|
||||
private void buildModuleConfig(
|
||||
String assistantName,
|
||||
String prompt,
|
||||
String summaryMemory,
|
||||
String voice,
|
||||
String referenceAudio,
|
||||
String referenceText,
|
||||
String vadModelId,
|
||||
String asrModelId,
|
||||
String llmModelId,
|
||||
@@ -290,8 +302,10 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
if (model.getConfigJson() != null) {
|
||||
typeConfig.put(model.getId(), model.getConfigJson());
|
||||
// 如果是TTS类型,添加private_voice属性
|
||||
if ("TTS".equals(modelTypes[i]) && voice != null) {
|
||||
((Map<String, Object>) model.getConfigJson()).put("private_voice", voice);
|
||||
if ("TTS".equals(modelTypes[i])){
|
||||
if (voice != null) ((Map<String, Object>) model.getConfigJson()).put("private_voice", voice);
|
||||
if (referenceAudio != null) ((Map<String, Object>) model.getConfigJson()).put("ref_audio", referenceAudio);
|
||||
if (referenceText != null) ((Map<String, Object>) model.getConfigJson()).put("ref_text", referenceText);
|
||||
}
|
||||
// 如果是Intent类型,且type=intent_llm,则给他添加附加模型
|
||||
if ("Intent".equals(modelTypes[i])) {
|
||||
|
||||
+10
@@ -25,6 +25,7 @@ import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.modules.device.dto.DeviceRegisterDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceUnBindDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceUpdateDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||
import xiaozhi.modules.device.service.DeviceService;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
@@ -99,4 +100,13 @@ public class DeviceController {
|
||||
deviceService.updateById(entity);
|
||||
return new Result<Void>();
|
||||
}
|
||||
|
||||
@PostMapping("/manual-add")
|
||||
@Operation(summary = "手动添加设备")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<Void> manualAddDevice(@RequestBody @Valid DeviceManualAddDTO dto) {
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
deviceService.manualAddDevice(user.getId(), dto);
|
||||
return new Result<>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package xiaozhi.modules.device.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceManualAddDTO {
|
||||
private String agentId;
|
||||
private String board; // 设备型号
|
||||
private String appVersion; // 固件版本
|
||||
private String macAddress; // Mac地址
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import xiaozhi.common.service.BaseService;
|
||||
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
||||
|
||||
@@ -87,5 +88,14 @@ public interface DeviceService extends BaseService<DeviceEntity> {
|
||||
*/
|
||||
Date getLatestLastConnectionTime(String agentId);
|
||||
|
||||
/**
|
||||
* 手动添加设备
|
||||
*/
|
||||
void manualAddDevice(Long userId, DeviceManualAddDTO dto);
|
||||
|
||||
/**
|
||||
* 更新设备连接信息
|
||||
*/
|
||||
void updateDeviceConnectionInfo(String agentId, String deviceId, String appVersion);
|
||||
|
||||
}
|
||||
+27
@@ -44,6 +44,7 @@ import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
import xiaozhi.modules.sys.service.SysParamsService;
|
||||
import xiaozhi.modules.sys.service.SysUserUtilService;
|
||||
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@@ -410,4 +411,30 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void manualAddDevice(Long userId, DeviceManualAddDTO dto) {
|
||||
// 检查mac是否已存在
|
||||
QueryWrapper<DeviceEntity> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("mac_address", dto.getMacAddress());
|
||||
DeviceEntity exist = baseDao.selectOne(wrapper);
|
||||
if (exist != null) {
|
||||
throw new RenException("该Mac地址已存在");
|
||||
}
|
||||
Date now = new Date();
|
||||
DeviceEntity entity = new DeviceEntity();
|
||||
entity.setId(dto.getMacAddress());
|
||||
entity.setUserId(userId);
|
||||
entity.setAgentId(dto.getAgentId());
|
||||
entity.setBoard(dto.getBoard());
|
||||
entity.setAppVersion(dto.getAppVersion());
|
||||
entity.setMacAddress(dto.getMacAddress());
|
||||
entity.setCreateDate(now);
|
||||
entity.setUpdateDate(now);
|
||||
entity.setLastConnectedAt(now);
|
||||
entity.setCreator(userId);
|
||||
entity.setUpdater(userId);
|
||||
entity.setAutoUpdate(1);
|
||||
baseDao.insert(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,12 @@ public class TimbreDataDTO {
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "参考音频路径")
|
||||
private String referenceAudio;
|
||||
|
||||
@Schema(description = "參考文本")
|
||||
private String referenceText;
|
||||
|
||||
@Schema(description = "排序")
|
||||
@Min(value = 0, message = "{sort.number}")
|
||||
private long sort;
|
||||
|
||||
@@ -34,6 +34,12 @@ public class TimbreEntity {
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "参考音频路径")
|
||||
private String referenceAudio;
|
||||
|
||||
@Schema(description = "參考文本")
|
||||
private String referenceText;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private long sort;
|
||||
|
||||
|
||||
@@ -25,6 +25,12 @@ public class TimbreDetailsVO implements Serializable {
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "参考音频路径")
|
||||
private String referenceAudio;
|
||||
|
||||
@Schema(description = "參考文本")
|
||||
private String referenceText;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private long sort;
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE `ai_tts_voice`
|
||||
ADD COLUMN `reference_audio` VARCHAR(500) DEFAULT NULL COMMENT '参考音频路径' AFTER `remark`,
|
||||
ADD COLUMN `reference_text` VARCHAR(500) DEFAULT NULL COMMENT '参考文本' AFTER `reference_audio`;
|
||||
@@ -0,0 +1,18 @@
|
||||
-- 更新现有的 get_news_from_newsnow 插件配置
|
||||
UPDATE ai_model_provider
|
||||
SET fields = JSON_ARRAY(
|
||||
JSON_OBJECT(
|
||||
'key', 'url',
|
||||
'type', 'string',
|
||||
'label', '接口地址',
|
||||
'default', 'https://newsnow.busiyi.world/api/s?id='
|
||||
),
|
||||
JSON_OBJECT(
|
||||
'key', 'news_sources',
|
||||
'type', 'string',
|
||||
'label', '新闻源配置',
|
||||
'default', '澎湃新闻;百度热搜;财联社'
|
||||
)
|
||||
)
|
||||
WHERE provider_code = 'get_news_from_newsnow'
|
||||
AND model_type = 'Plugin';
|
||||
@@ -205,6 +205,13 @@ databaseChangeLog:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202506080955.sql
|
||||
- changeSet:
|
||||
id: 202506091720
|
||||
author: shane0411
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202506091720.sql
|
||||
- changeSet:
|
||||
id: 202506161101
|
||||
author: hrz
|
||||
@@ -219,6 +226,13 @@ databaseChangeLog:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202506191643.sql
|
||||
- changeSet:
|
||||
id: 202506251620
|
||||
author: Tink
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202506251620.sql
|
||||
- changeSet:
|
||||
id: 202506261637
|
||||
author: hrz
|
||||
|
||||
@@ -7,6 +7,7 @@ import model from './module/model.js'
|
||||
import ota from './module/ota.js'
|
||||
import timbre from "./module/timbre.js"
|
||||
import user from './module/user.js'
|
||||
|
||||
/**
|
||||
* 接口地址
|
||||
* 开发时自动读取使用.env.development文件
|
||||
@@ -22,7 +23,6 @@ export function getServiceUrl() {
|
||||
return DEV_API_SERVICE
|
||||
}
|
||||
|
||||
|
||||
/** request服务封装 */
|
||||
export default {
|
||||
getServiceUrl,
|
||||
|
||||
@@ -68,4 +68,21 @@ export default {
|
||||
})
|
||||
}).send()
|
||||
},
|
||||
// 手动添加设备
|
||||
manualAddDevice(params, callback) {
|
||||
RequestService.sendRequest()
|
||||
.url(`${getServiceUrl()}/device/manual-add`)
|
||||
.method('POST')
|
||||
.data(params)
|
||||
.success((res) => {
|
||||
RequestService.clearRequestTime();
|
||||
callback(res);
|
||||
})
|
||||
.networkFail((err) => {
|
||||
console.error('手动添加设备失败:', err);
|
||||
RequestService.reAjaxFun(() => {
|
||||
this.manualAddDevice(params, callback);
|
||||
});
|
||||
}).send();
|
||||
},
|
||||
}
|
||||
@@ -34,6 +34,8 @@ export default {
|
||||
languages: params.languageType,
|
||||
name: params.voiceName,
|
||||
remark: params.remark,
|
||||
referenceAudio: params.referenceAudio,
|
||||
referenceText: params.referenceText,
|
||||
sort: params.sort,
|
||||
ttsModelId: params.ttsModelId,
|
||||
ttsVoice: params.voiceCode,
|
||||
@@ -75,6 +77,8 @@ export default {
|
||||
languages: params.languageType,
|
||||
name: params.voiceName,
|
||||
remark: params.remark,
|
||||
referenceAudio: params.referenceAudio,
|
||||
referenceText: params.referenceText,
|
||||
ttsModelId: params.ttsModelId,
|
||||
ttsVoice: params.voiceCode,
|
||||
voiceDemo: params.voiceDemo || ''
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<el-dialog title="手动添加设备" :visible="visible" @close="handleClose" width="30%" center>
|
||||
<div class="dialog-content">
|
||||
<el-form :model="deviceForm" :rules="rules" ref="deviceForm" label-width="100px">
|
||||
<el-form-item label="设备型号" prop="board">
|
||||
<el-select v-model="deviceForm.board" placeholder="请选择设备型号" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in firmwareTypes"
|
||||
:key="item.key"
|
||||
:label="item.name"
|
||||
:value="item.key">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="固件版本" prop="appVersion">
|
||||
<el-input v-model="deviceForm.appVersion" placeholder="请输入固件版本"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Mac地址" prop="macAddress">
|
||||
<el-input v-model="deviceForm.macAddress" placeholder="请输入Mac地址"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div style="display: flex;margin: 15px 15px;gap: 7px;">
|
||||
<div class="dialog-btn" @click="submitForm">确定</div>
|
||||
<div class="dialog-btn cancel-btn" @click="cancel">取消</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Api from '@/apis/api';
|
||||
|
||||
export default {
|
||||
name: 'ManualAddDeviceDialog',
|
||||
props: {
|
||||
visible: { type: Boolean, required: true },
|
||||
agentId: { type: String, required: true }
|
||||
},
|
||||
data() {
|
||||
// MAC地址验证规则
|
||||
const validateMac = (rule, value, callback) => {
|
||||
const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
|
||||
if (!value) {
|
||||
callback(new Error('请输入Mac地址'));
|
||||
} else if (!macRegex.test(value)) {
|
||||
callback(new Error('请输入正确的Mac地址格式,例如:00:1A:2B:3C:4D:5E'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
deviceForm: {
|
||||
board: '',
|
||||
appVersion: '',
|
||||
macAddress: ''
|
||||
},
|
||||
firmwareTypes: [],
|
||||
rules: {
|
||||
board: [
|
||||
{ required: true, message: '请选择设备型号', trigger: 'change' }
|
||||
],
|
||||
appVersion: [
|
||||
{ required: true, message: '请输入固件版本', trigger: 'blur' }
|
||||
],
|
||||
macAddress: [
|
||||
{ required: true, validator: validateMac, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getFirmwareTypes();
|
||||
},
|
||||
methods: {
|
||||
async getFirmwareTypes() {
|
||||
try {
|
||||
const res = await Api.dict.getDictDataByType('FIRMWARE_TYPE');
|
||||
this.firmwareTypes = res.data;
|
||||
} catch (error) {
|
||||
console.error('获取固件类型失败:', error);
|
||||
this.$message.error(error.message || '获取固件类型失败');
|
||||
}
|
||||
},
|
||||
submitForm() {
|
||||
this.$refs.deviceForm.validate((valid) => {
|
||||
if (valid) {
|
||||
this.addDevice();
|
||||
}
|
||||
});
|
||||
},
|
||||
addDevice() {
|
||||
const params = {
|
||||
agentId: this.agentId,
|
||||
...this.deviceForm
|
||||
};
|
||||
|
||||
Api.device.manualAddDevice(params, ({ data }) => {
|
||||
if (data.code === 0) {
|
||||
this.$message.success('设备添加成功');
|
||||
this.$emit('refresh');
|
||||
this.closeDialog();
|
||||
} else {
|
||||
this.$message.error(data.msg || '添加失败');
|
||||
}
|
||||
});
|
||||
},
|
||||
closeDialog() {
|
||||
this.$emit('update:visible', false);
|
||||
this.$refs.deviceForm.resetFields();
|
||||
},
|
||||
cancel() {
|
||||
this.closeDialog();
|
||||
},
|
||||
handleClose() {
|
||||
this.closeDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-content {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.dialog-btn {
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
border-radius: 23px;
|
||||
background: #5778ff;
|
||||
height: 40px;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background: #e6ebff;
|
||||
border: 1px solid #adbdff;
|
||||
color: #5778ff;
|
||||
}
|
||||
|
||||
::v-deep .el-dialog {
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
::v-deep .el-dialog__body {
|
||||
padding: 20px 6px;
|
||||
}
|
||||
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,79 +1,66 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible.sync="localVisible"
|
||||
width="85%"
|
||||
@close="handleClose"
|
||||
:show-close="false"
|
||||
:append-to-body="true"
|
||||
:close-on-click-modal="true">
|
||||
<el-dialog :visible.sync="localVisible" width="90%" @close="handleClose" :show-close="false" :append-to-body="true"
|
||||
:close-on-click-modal="true">
|
||||
<button class="custom-close-btn" @click="handleClose">
|
||||
×
|
||||
</button>
|
||||
<div class="scroll-wrapper">
|
||||
<div
|
||||
class="table-container"
|
||||
ref="tableContainer"
|
||||
@scroll="handleScroll">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="filteredTtsModels"
|
||||
style="width: 100%;"
|
||||
class="data-table"
|
||||
header-row-class-name="table-header"
|
||||
:fit="true"
|
||||
element-loading-text="拼命加载中"
|
||||
element-loading-spinner="el-icon-loading"
|
||||
element-loading-background="rgba(0, 0, 0, 0.8)">
|
||||
<div class="table-container" ref="tableContainer" @scroll="handleScroll">
|
||||
<el-table v-loading="loading" :data="filteredTtsModels" style="width: 100%;" class="data-table"
|
||||
header-row-class-name="table-header" :fit="true" element-loading-text="拼命加载中"
|
||||
element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)">
|
||||
<el-table-column label="选择" width="50" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-checkbox v-model="scope.row.selected"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="音色编码" align="center" min-width="50">
|
||||
<el-table-column label="音色编码" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-if="scope.row.editing" v-model="scope.row.voiceCode"></el-input>
|
||||
<span v-else>{{ scope.row.voiceCode }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="音色名称" align="center" min-width="50">
|
||||
<el-table-column label="音色名称" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-if="scope.row.editing" v-model="scope.row.voiceName"></el-input>
|
||||
<span v-else>{{ scope.row.voiceName }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="语言类型" align="center" min-width="50">
|
||||
<el-table-column label="语言类型" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-if="scope.row.editing" v-model="scope.row.languageType"></el-input>
|
||||
<span v-else>{{ scope.row.languageType }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="试听" align="center" min-width="100px" class-name="audio-column">
|
||||
<el-table-column v-if="!showReferenceColumns" label="试听" align="center" class-name="audio-column">
|
||||
<template slot-scope="scope">
|
||||
<div class="custom-audio-container">
|
||||
<el-input
|
||||
v-if="scope.row.editing"
|
||||
v-model="scope.row.voiceDemo"
|
||||
placeholder="请输入MP3地址"
|
||||
size="mini"
|
||||
class="audio-input">
|
||||
<el-input v-if="scope.row.editing" v-model="scope.row.voiceDemo" placeholder="请输入MP3地址"
|
||||
class="audio-input">
|
||||
</el-input>
|
||||
<AudioPlayer v-else-if="isValidAudioUrl(scope.row.voiceDemo)" :audioUrl="scope.row.voiceDemo"/>
|
||||
<AudioPlayer v-else-if="isValidAudioUrl(scope.row.voiceDemo)" :audioUrl="scope.row.voiceDemo" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center">
|
||||
<el-table-column v-if="!showReferenceColumns" label="备注" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-if="scope.row.editing"
|
||||
type="textarea"
|
||||
:rows="1"
|
||||
autosize
|
||||
v-model="scope.row.remark"
|
||||
placeholder="这里是备注"
|
||||
class="remark-input"></el-input>
|
||||
<el-input v-if="scope.row.editing" type="textarea" :rows="1" autosize v-model="scope.row.remark"
|
||||
placeholder="这里是备注" class="remark-input"></el-input>
|
||||
<span v-else>{{ scope.row.remark }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="showReferenceColumns" label="克隆音频路径" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-if="scope.row.editing" v-model="scope.row.referenceAudio" placeholder="这里是克隆音频路径"></el-input>
|
||||
<span v-else>{{ scope.row.referenceAudio }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="showReferenceColumns" label="克隆音频文本" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-if="scope.row.editing" v-model="scope.row.referenceText" placeholder="这里是克隆音频对应文本"></el-input>
|
||||
<span v-else>{{ scope.row.referenceText }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="150">
|
||||
<template slot-scope="scope">
|
||||
<template v-if="!scope.row.editing">
|
||||
@@ -84,12 +71,7 @@
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
<el-button
|
||||
v-else
|
||||
type="success"
|
||||
size="mini"
|
||||
@click="saveEdit(scope.row)"
|
||||
class="save-Tts">保存
|
||||
<el-button v-else type="success" size="mini" @click="saveEdit(scope.row)" class="save-Tts">保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -110,21 +92,19 @@
|
||||
<el-button type="primary" size="mini" @click="addNew" style="background: #5bc98c;border: None;">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button type="primary"
|
||||
size="mini"
|
||||
@click="deleteRow(filteredTtsModels.filter(row => row.selected))"
|
||||
style="background: red;border:None">删除
|
||||
<el-button type="primary" size="mini" @click="deleteRow(filteredTtsModels.filter(row => row.selected))"
|
||||
style="background: red;border:None">删除
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AudioPlayer from './AudioPlayer.vue'
|
||||
import Api from "@/apis/api";
|
||||
import AudioPlayer from './AudioPlayer.vue';
|
||||
|
||||
export default {
|
||||
components: {AudioPlayer},
|
||||
components: { AudioPlayer },
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
@@ -133,6 +113,10 @@ export default {
|
||||
ttsModelId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
modelConfig: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -151,6 +135,7 @@ export default {
|
||||
selectAll: false,
|
||||
selectedRows: [],
|
||||
loading: false,
|
||||
showReferenceColumns: false, // 控制是否显示参考列
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -158,12 +143,19 @@ export default {
|
||||
this.localVisible = newVal;
|
||||
if (newVal) {
|
||||
this.currentPage = 1;
|
||||
this.updateShowReferenceColumns(); // 更新显示状态
|
||||
this.loadData(); // 对话框显示时加载数据
|
||||
this.$nextTick(() => {
|
||||
this.updateScrollbar();
|
||||
});
|
||||
}
|
||||
},
|
||||
modelConfig: {
|
||||
handler(newVal) {
|
||||
this.updateShowReferenceColumns();
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
filteredTtsModels() {
|
||||
this.$nextTick(() => {
|
||||
this.updateScrollbar();
|
||||
@@ -173,7 +165,7 @@ export default {
|
||||
computed: {
|
||||
filteredTtsModels() {
|
||||
return this.ttsModels.filter(model =>
|
||||
model.voiceName.toLowerCase().includes(this.searchQuery.toLowerCase())
|
||||
model.voiceName.toLowerCase().includes(this.searchQuery.toLowerCase())
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -189,6 +181,16 @@ export default {
|
||||
window.removeEventListener('mousemove', this.handleDrag);
|
||||
},
|
||||
methods: {
|
||||
// 更新是否显示参考列
|
||||
updateShowReferenceColumns() {
|
||||
if (this.modelConfig && this.modelConfig.configJson) {
|
||||
const providerType = this.modelConfig.configJson.type;
|
||||
this.showReferenceColumns = ['fishspeech', 'gpt_sovits_v2', 'gpt_sovits_v3'].includes(providerType);
|
||||
} else {
|
||||
this.showReferenceColumns = false;
|
||||
}
|
||||
},
|
||||
|
||||
loadData() {
|
||||
this.loading = true;
|
||||
const params = {
|
||||
@@ -206,6 +208,8 @@ export default {
|
||||
voiceName: item.name || '未命名音色',
|
||||
languageType: item.languages || '',
|
||||
remark: item.remark || '',
|
||||
referenceAudio: item.referenceAudio || '',
|
||||
referenceText: item.referenceText || '',
|
||||
voiceDemo: item.voiceDemo || '',
|
||||
selected: false,
|
||||
editing: false,
|
||||
@@ -237,6 +241,7 @@ export default {
|
||||
this.total = 0;
|
||||
this.selectAll = false;
|
||||
this.searchQuery = '';
|
||||
this.showReferenceColumns = false;
|
||||
|
||||
this.localVisible = false;
|
||||
this.$emit('update:visible', false);
|
||||
@@ -249,7 +254,7 @@ export default {
|
||||
|
||||
if (!container || !scrollbarThumb || !scrollbarTrack) return;
|
||||
|
||||
const {scrollHeight, clientHeight} = container;
|
||||
const { scrollHeight, clientHeight } = container;
|
||||
const trackHeight = scrollbarTrack.clientHeight;
|
||||
const thumbHeight = Math.max((clientHeight / scrollHeight) * trackHeight, 20);
|
||||
|
||||
@@ -264,7 +269,7 @@ export default {
|
||||
|
||||
if (!container || !scrollbarThumb || !scrollbarTrack) return;
|
||||
|
||||
const {scrollHeight, clientHeight, scrollTop} = container;
|
||||
const { scrollHeight, clientHeight, scrollTop } = container;
|
||||
const trackHeight = scrollbarTrack.clientHeight;
|
||||
const thumbHeight = scrollbarThumb.clientHeight;
|
||||
const maxTop = trackHeight - thumbHeight;
|
||||
@@ -332,7 +337,7 @@ export default {
|
||||
|
||||
startEdit(row) {
|
||||
row.editing = true;
|
||||
this.$set(row, 'originalData', {...row});
|
||||
this.$set(row, 'originalData', { ...row });
|
||||
},
|
||||
|
||||
saveEdit(row) {
|
||||
@@ -356,6 +361,12 @@ export default {
|
||||
sort: row.sort
|
||||
};
|
||||
|
||||
// 只有在显示参考列的情况下才添加参考字段
|
||||
if (this.showReferenceColumns) {
|
||||
params.referenceAudio = row.referenceAudio;
|
||||
params.referenceText = row.referenceText;
|
||||
}
|
||||
|
||||
let res;
|
||||
if (row.id) {
|
||||
// 已有ID,执行更新操作
|
||||
@@ -423,8 +434,8 @@ export default {
|
||||
}
|
||||
|
||||
const maxSort = this.ttsModels.length > 0
|
||||
? Math.max(...this.ttsModels.map(item => Number(item.sort) || 0))
|
||||
: 0;
|
||||
? Math.max(...this.ttsModels.map(item => Number(item.sort) || 0))
|
||||
: 0;
|
||||
|
||||
const newRow = {
|
||||
voiceCode: '',
|
||||
@@ -432,6 +443,8 @@ export default {
|
||||
languageType: '中文',
|
||||
voiceDemo: '',
|
||||
remark: '',
|
||||
referenceAudio: '',
|
||||
referenceText: '',
|
||||
selected: false,
|
||||
editing: true,
|
||||
sort: maxSort + 1
|
||||
@@ -441,56 +454,56 @@ export default {
|
||||
},
|
||||
|
||||
deleteRow(row) {
|
||||
// 处理单个音色或音色数组
|
||||
const voices = Array.isArray(row) ? row : [row];
|
||||
// 处理单个音色或音色数组
|
||||
const voices = Array.isArray(row) ? row : [row];
|
||||
|
||||
if (Array.isArray(row) && row.length === 0) {
|
||||
this.$message.warning("请先选择需要删除的音色");
|
||||
return;
|
||||
if (Array.isArray(row) && row.length === 0) {
|
||||
this.$message.warning("请先选择需要删除的音色");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const voiceCount = voices.length;
|
||||
this.$confirm(`确定要删除选中的${voiceCount}个音色吗?`, "警告", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
distinguishCancelAndClose: true
|
||||
}).then(() => {
|
||||
const ids = voices.map(voice => voice.id);
|
||||
if (ids.some(id => !id)) {
|
||||
this.$message.error("存在无效的音色ID");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const voiceCount = voices.length;
|
||||
this.$confirm(`确定要删除选中的${voiceCount}个音色吗?`, "警告", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
distinguishCancelAndClose: true
|
||||
}).then(() => {
|
||||
const ids = voices.map(voice => voice.id);
|
||||
if (ids.some(id => !id)) {
|
||||
this.$message.error("存在无效的音色ID");
|
||||
return;
|
||||
}
|
||||
|
||||
Api.timbre.deleteVoice(ids, ({data}) => {
|
||||
if (data.code === 0) {
|
||||
this.$message.success({
|
||||
message: `成功删除${voiceCount}个参数`,
|
||||
showClose: true
|
||||
});
|
||||
this.loadData(); // 刷新参数列表
|
||||
} else {
|
||||
this.$message.error({
|
||||
message: data.msg || '删除失败,请重试',
|
||||
showClose: true
|
||||
});
|
||||
}
|
||||
Api.timbre.deleteVoice(ids, ({ data }) => {
|
||||
if (data.code === 0) {
|
||||
this.$message.success({
|
||||
message: `成功删除${voiceCount}个参数`,
|
||||
showClose: true
|
||||
});
|
||||
this.loadData(); // 刷新参数列表
|
||||
} else {
|
||||
this.$message.error({
|
||||
message: data.msg || '删除失败,请重试',
|
||||
showClose: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}).catch(action => {
|
||||
if (action === 'cancel') {
|
||||
this.$message({
|
||||
type: 'info',
|
||||
message: '已取消删除操作',
|
||||
duration: 1000
|
||||
});
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'info',
|
||||
message: '操作已关闭',
|
||||
duration: 1000
|
||||
});
|
||||
}
|
||||
if (action === 'cancel') {
|
||||
this.$message({
|
||||
type: 'info',
|
||||
message: '已取消删除操作',
|
||||
duration: 1000
|
||||
});
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'info',
|
||||
message: '操作已关闭',
|
||||
duration: 1000
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -502,7 +515,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
::v-deep .el-dialog {
|
||||
border-radius: 8px !important;
|
||||
overflow: hidden;
|
||||
@@ -695,6 +707,7 @@ export default {
|
||||
white-space: pre-wrap !important;
|
||||
word-break: break-all !important;
|
||||
}
|
||||
|
||||
/* 按钮组定位调整 */
|
||||
.action-buttons {
|
||||
position: static;
|
||||
@@ -719,5 +732,4 @@ export default {
|
||||
flex-shrink: 0;
|
||||
margin: 2px !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -77,7 +77,10 @@
|
||||
{{ isAllSelected ? '取消全选' : '全选' }}
|
||||
</el-button>
|
||||
<el-button type="success" size="mini" class="add-device-btn" @click="handleAddDevice">
|
||||
新增
|
||||
验证码绑定
|
||||
</el-button>
|
||||
<el-button type="success" size="mini" class="add-device-btn" @click="handleManualAddDevice">
|
||||
手动添加
|
||||
</el-button>
|
||||
<el-button size="mini" type="danger" icon="el-icon-delete" @click="deleteSelected">解绑</el-button>
|
||||
</div>
|
||||
@@ -103,6 +106,8 @@
|
||||
|
||||
<AddDeviceDialog :visible.sync="addDeviceDialogVisible" :agent-id="currentAgentId"
|
||||
@refresh="fetchBindDevices(currentAgentId)"/>
|
||||
<ManualAddDeviceDialog :visible.sync="manualAddDeviceDialogVisible" :agent-id="currentAgentId"
|
||||
@refresh="fetchBindDevices(currentAgentId)"/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@@ -110,13 +115,19 @@
|
||||
<script>
|
||||
import Api from '@/apis/api';
|
||||
import AddDeviceDialog from "@/components/AddDeviceDialog.vue";
|
||||
import ManualAddDeviceDialog from "@/components/ManualAddDeviceDialog.vue";
|
||||
import HeaderBar from "@/components/HeaderBar.vue";
|
||||
|
||||
export default {
|
||||
components: {HeaderBar, AddDeviceDialog},
|
||||
components: {
|
||||
HeaderBar,
|
||||
AddDeviceDialog,
|
||||
ManualAddDeviceDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
addDeviceDialogVisible: false,
|
||||
manualAddDeviceDialogVisible: false,
|
||||
selectedDevices: [],
|
||||
isAllSelected: false,
|
||||
searchKeyword: "",
|
||||
@@ -252,6 +263,9 @@ export default {
|
||||
handleAddDevice() {
|
||||
this.addDeviceDialogVisible = true;
|
||||
},
|
||||
handleManualAddDevice() {
|
||||
this.manualAddDeviceDialogVisible = true;
|
||||
},
|
||||
submitRemark(row) {
|
||||
if (row._submitting) return;
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
|
||||
<ModelEditDialog :modelType="activeTab" :visible.sync="editDialogVisible" :modelData="editModelData"
|
||||
@save="handleModelSave" />
|
||||
<TtsModel :visible.sync="ttsDialogVisible" :ttsModelId="selectedTtsModelId" />
|
||||
<TtsModel :visible.sync="ttsDialogVisible" :ttsModelId="selectedTtsModelId" :modelConfig="selectedModelConfig" />
|
||||
<AddModelDialog :modelType="activeTab" :visible.sync="addDialogVisible" @confirm="handleAddConfirm" />
|
||||
</div>
|
||||
<el-footer>
|
||||
@@ -162,7 +162,8 @@ export default {
|
||||
total: 0,
|
||||
selectedModels: [],
|
||||
isAllSelected: false,
|
||||
loading: false
|
||||
loading: false,
|
||||
selectedModelConfig: {}
|
||||
};
|
||||
},
|
||||
|
||||
@@ -211,6 +212,7 @@ export default {
|
||||
},
|
||||
openTtsDialog(row) {
|
||||
this.selectedTtsModelId = row.id;
|
||||
this.selectedModelConfig = row;
|
||||
this.ttsDialogVisible = true;
|
||||
},
|
||||
headerCellClassName({ column, columnIndex }) {
|
||||
|
||||
@@ -121,7 +121,9 @@ plugins:
|
||||
society_rss_url: "https://www.chinanews.com.cn/rss/society.xml"
|
||||
world_rss_url: "https://www.chinanews.com.cn/rss/world.xml"
|
||||
finance_rss_url: "https://www.chinanews.com.cn/rss/finance.xml"
|
||||
get_news_from_newsnow: {"url": "https://newsnow.busiyi.world/api/s?id="}
|
||||
get_news_from_newsnow:
|
||||
url: "https://newsnow.busiyi.world/api/s?id="
|
||||
news_sources: "澎湃新闻;百度热搜;财联社"
|
||||
home_assistant:
|
||||
devices:
|
||||
- 客厅,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1
|
||||
@@ -159,7 +161,7 @@ end_prompt:
|
||||
enable: true # 是否开启结束语
|
||||
# 结束语
|
||||
prompt: |
|
||||
请你以“时间过得真快”未来头,用富有感情、依依不舍的话来结束这场对话吧!
|
||||
请你以"时间过得真快"未来头,用富有感情、依依不舍的话来结束这场对话吧!
|
||||
|
||||
# 具体处理时选择的模块(The module selected for specific processing)
|
||||
selected_module:
|
||||
@@ -196,7 +198,7 @@ Intent:
|
||||
# 如果你的不想使用selected_module.LLM意图识别,这里最好使用独立的LLM作为意图识别,例如使用免费的ChatGLMLLM
|
||||
llm: ChatGLMLLM
|
||||
# plugins_func/functions下的模块,可以通过配置,选择加载哪个模块,加载后对话支持相应的function调用
|
||||
# 系统默认已经记载“handle_exit_intent(退出识别)”、“play_music(音乐播放)”插件,请勿重复加载
|
||||
# 系统默认已经记载"handle_exit_intent(退出识别)"、"play_music(音乐播放)"插件,请勿重复加载
|
||||
# 下面是加载查天气、角色切换、加载查新闻的插件示例
|
||||
functions:
|
||||
- get_weather
|
||||
@@ -206,7 +208,7 @@ Intent:
|
||||
# 不需要动type
|
||||
type: function_call
|
||||
# plugins_func/functions下的模块,可以通过配置,选择加载哪个模块,加载后对话支持相应的function调用
|
||||
# 系统默认已经记载“handle_exit_intent(退出识别)”、“play_music(音乐播放)”插件,请勿重复加载
|
||||
# 系统默认已经记载"handle_exit_intent(退出识别)"、"play_music(音乐播放)"插件,请勿重复加载
|
||||
# 下面是加载查天气、角色切换、加载查新闻的插件示例
|
||||
functions:
|
||||
- change_role
|
||||
@@ -766,4 +768,4 @@ TTS:
|
||||
# 支持声音克隆,可自行上传音频,填入voice参数,voice参数为空时,使用默认声音
|
||||
access_token: "U4YdYXVfpwWnk2t5Gp822zWPCuORyeJL"
|
||||
voice: "OUeAo1mhq6IBExi"
|
||||
output_dir: tmp/
|
||||
output_dir: tmp/
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import httpx
|
||||
import openai
|
||||
from openai.types import CompletionUsage
|
||||
from config.logger import setup_logging
|
||||
@@ -16,6 +17,9 @@ class LLMProvider(LLMProviderBase):
|
||||
self.base_url = config.get("base_url")
|
||||
else:
|
||||
self.base_url = config.get("url")
|
||||
# 增加timeout的配置项,单位为秒
|
||||
timeout = config.get("timeout", 300)
|
||||
self.timeout = int(timeout) if timeout else 300
|
||||
|
||||
param_defaults = {
|
||||
"max_tokens": (500, int),
|
||||
@@ -42,7 +46,7 @@ class LLMProvider(LLMProviderBase):
|
||||
model_key_msg = check_model_key("LLM", self.api_key)
|
||||
if model_key_msg:
|
||||
logger.bind(tag=TAG).error(model_key_msg)
|
||||
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
|
||||
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url, timeout=httpx.Timeout(self.timeout))
|
||||
|
||||
def response(self, session_id, dialogue, **kwargs):
|
||||
try:
|
||||
|
||||
@@ -85,8 +85,12 @@ class TTSProvider(TTSProviderBase):
|
||||
self.reference_id = (
|
||||
None if not config.get("reference_id") else config.get("reference_id")
|
||||
)
|
||||
self.reference_audio = parse_string_to_list(config.get("reference_audio"))
|
||||
self.reference_text = parse_string_to_list(config.get("reference_text"))
|
||||
self.reference_audio = parse_string_to_list(
|
||||
config.get('ref_audio')if config.get('ref_audio') else config.get("reference_audio")
|
||||
)
|
||||
self.reference_text = parse_string_to_list(
|
||||
config.get('ref_text')if config.get('ref_text') else config.get("reference_text")
|
||||
)
|
||||
self.format = config.get("response_format", "wav")
|
||||
self.audio_file_type = config.get("response_format", "wav")
|
||||
self.api_key = config.get("api_key", "YOUR_API_KEY")
|
||||
|
||||
@@ -12,8 +12,8 @@ class TTSProvider(TTSProviderBase):
|
||||
super().__init__(config, delete_audio_file)
|
||||
self.url = config.get("url")
|
||||
self.text_lang = config.get("text_lang", "zh")
|
||||
self.ref_audio_path = config.get("ref_audio_path")
|
||||
self.prompt_text = config.get("prompt_text")
|
||||
self.ref_audio_path = config.get('ref_audio') if config.get('ref_audio') else config.get("ref_audio_path")
|
||||
self.prompt_text = config.get('ref_text') if config.get('ref_text') else config.get("prompt_text")
|
||||
self.prompt_lang = config.get("prompt_lang", "zh")
|
||||
|
||||
# 处理空字符串的情况
|
||||
|
||||
@@ -11,8 +11,8 @@ class TTSProvider(TTSProviderBase):
|
||||
def __init__(self, config, delete_audio_file):
|
||||
super().__init__(config, delete_audio_file)
|
||||
self.url = config.get("url")
|
||||
self.refer_wav_path = config.get("refer_wav_path")
|
||||
self.prompt_text = config.get("prompt_text")
|
||||
self.refer_wav_path = config.get('ref_audio')if config.get('ref_audio') else config.get("refer_wav_path")
|
||||
self.prompt_text = config.get('ref_text')if config.get('ref_text') else config.get("prompt_text")
|
||||
self.prompt_language = config.get("prompt_language")
|
||||
self.text_language = config.get("text_language", "audo")
|
||||
|
||||
|
||||
@@ -8,30 +8,90 @@ from markitdown import MarkItDown
|
||||
TAG = __name__
|
||||
logger = setup_logging()
|
||||
|
||||
# 新闻来源字典,包含名称和对应的API ID
|
||||
NEWS_SOURCES = {
|
||||
"thepaper": "澎湃新闻",
|
||||
"baidu": "百度热搜",
|
||||
"cls-depth": "财联社",
|
||||
CHANNEL_MAP = {
|
||||
"V2EX": "v2ex-share",
|
||||
"知乎": "zhihu",
|
||||
"微博": "weibo",
|
||||
"联合早报": "zaobao",
|
||||
"酷安": "coolapk",
|
||||
"MKTNews": "mktnews-flash",
|
||||
"华尔街见闻": "wallstreetcn-quick",
|
||||
"36氪": "36kr-quick",
|
||||
"抖音": "douyin",
|
||||
"虎扑": "hupu",
|
||||
"百度贴吧": "tieba",
|
||||
"今日头条": "toutiao",
|
||||
"IT之家": "ithome",
|
||||
"澎湃新闻": "thepaper",
|
||||
"卫星通讯社": "sputniknewscn",
|
||||
"参考消息": "cankaoxiaoxi",
|
||||
"远景论坛": "pcbeta-windows11",
|
||||
"财联社": "cls-depth",
|
||||
"雪球": "xueqiu-hotstock",
|
||||
"格隆汇": "gelonghui",
|
||||
"法布财经": "fastbull-express",
|
||||
"Solidot": "solidot",
|
||||
"Hacker News": "hackernews",
|
||||
"Product Hunt": "producthunt",
|
||||
"Github": "github-trending-today",
|
||||
"哔哩哔哩": "bilibili-hot-search",
|
||||
"快手": "kuaishou",
|
||||
"靠谱新闻": "kaopu",
|
||||
"金十数据": "jin10",
|
||||
"百度热搜": "baidu",
|
||||
"牛客": "nowcoder",
|
||||
"少数派": "sspai",
|
||||
"稀土掘金": "juejin",
|
||||
"凤凰网": "ifeng",
|
||||
"虫部落": "chongbuluo-latest",
|
||||
}
|
||||
|
||||
|
||||
# 动态生成新闻源描述
|
||||
def generate_news_sources_description():
|
||||
sources_desc = []
|
||||
for source_id, source_name in NEWS_SOURCES.items():
|
||||
sources_desc.append(f"{source_name}({source_id})")
|
||||
return "、".join(sources_desc)
|
||||
# 默认新闻来源字典,当配置中没有指定时使用
|
||||
DEFAULT_NEWS_SOURCES = "澎湃新闻;百度热搜;财联社"
|
||||
|
||||
|
||||
def get_news_sources_from_config(conn):
|
||||
"""从配置中获取新闻源字符串"""
|
||||
try:
|
||||
# 尝试从插件配置中获取新闻源
|
||||
if (
|
||||
conn.config.get("plugins")
|
||||
and conn.config["plugins"].get("get_news_from_newsnow")
|
||||
and conn.config["plugins"]["get_news_from_newsnow"].get("news_sources")
|
||||
):
|
||||
# 获取配置的新闻源字符串
|
||||
news_sources_config = conn.config["plugins"]["get_news_from_newsnow"][
|
||||
"news_sources"
|
||||
]
|
||||
|
||||
if isinstance(news_sources_config, str) and news_sources_config.strip():
|
||||
logger.bind(tag=TAG).debug(f"使用配置的新闻源: {news_sources_config}")
|
||||
return news_sources_config
|
||||
else:
|
||||
logger.bind(tag=TAG).warning("新闻源配置为空或格式错误,使用默认配置")
|
||||
else:
|
||||
logger.bind(tag=TAG).debug("未找到新闻源配置,使用默认配置")
|
||||
|
||||
return DEFAULT_NEWS_SOURCES
|
||||
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"获取新闻源配置失败: {e},使用默认配置")
|
||||
return DEFAULT_NEWS_SOURCES
|
||||
|
||||
|
||||
# 从CHANNEL_MAP获取所有可用的新闻源名称
|
||||
available_sources = list(CHANNEL_MAP.keys())
|
||||
example_sources_str = "、".join(available_sources)
|
||||
|
||||
GET_NEWS_FROM_NEWSNOW_FUNCTION_DESC = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_news_from_newsnow",
|
||||
"description": (
|
||||
"获取最新新闻,随机选择一条新闻进行播报。"
|
||||
f"用户可以选择不同的新闻源,如{generate_news_sources_description()}等。"
|
||||
"如果没有指定,默认从澎湃新闻获取。"
|
||||
f"用户可以选择不同的新闻源,标准的名称是:{example_sources_str}"
|
||||
"例如用户要求百度新闻,其实就是百度热搜。如果没有指定,默认从澎湃新闻获取。"
|
||||
"用户可以要求获取详细内容,此时会获取新闻的详细内容。"
|
||||
),
|
||||
"parameters": {
|
||||
@@ -39,7 +99,7 @@ GET_NEWS_FROM_NEWSNOW_FUNCTION_DESC = {
|
||||
"properties": {
|
||||
"source": {
|
||||
"type": "string",
|
||||
"description": f"新闻源,例如{generate_news_sources_description()}等。可选参数,如果不提供则使用默认新闻源",
|
||||
"description": f"新闻源的标准中文名称,例如{example_sources_str}等。可选参数,如果不提供则使用默认新闻源",
|
||||
},
|
||||
"detail": {
|
||||
"type": "boolean",
|
||||
@@ -111,10 +171,13 @@ def fetch_news_detail(url):
|
||||
ToolType.SYSTEM_CTL,
|
||||
)
|
||||
def get_news_from_newsnow(
|
||||
conn, source: str = "thepaper", detail: bool = False, lang: str = "zh_CN"
|
||||
conn, source: str = "澎湃新闻", detail: bool = False, lang: str = "zh_CN"
|
||||
):
|
||||
"""获取新闻并随机选择一条进行播报,或获取上一条新闻的详细内容"""
|
||||
try:
|
||||
# 获取当前配置的新闻源
|
||||
news_sources = get_news_sources_from_config(conn)
|
||||
|
||||
# 如果detail为True,获取上一条新闻的详细内容
|
||||
detail = str(detail).lower() == "true"
|
||||
if detail:
|
||||
@@ -132,7 +195,7 @@ def get_news_from_newsnow(
|
||||
url = conn.last_newsnow_link.get("url")
|
||||
title = conn.last_newsnow_link.get("title", "未知标题")
|
||||
source_id = conn.last_newsnow_link.get("source_id", "thepaper")
|
||||
source_name = NEWS_SOURCES.get(source_id, "未知来源")
|
||||
source_name = CHANNEL_MAP.get(source_id, "未知来源")
|
||||
|
||||
if not url or url == "#":
|
||||
return ActionResponse(
|
||||
@@ -166,21 +229,32 @@ def get_news_from_newsnow(
|
||||
return ActionResponse(Action.REQLLM, detail_report, None)
|
||||
|
||||
# 否则,获取新闻列表并随机选择一条
|
||||
# 验证新闻源是否有效,如果无效则使用默认源
|
||||
if source not in NEWS_SOURCES:
|
||||
logger.bind(tag=TAG).warning(f"无效的新闻源: {source},使用默认源thepaper")
|
||||
source = "thepaper"
|
||||
# 将中文名称转换为英文ID
|
||||
english_source_id = None
|
||||
|
||||
source_name = NEWS_SOURCES.get(source, "澎湃新闻")
|
||||
logger.bind(tag=TAG).info(f"获取新闻: 新闻源={source}({source_name})")
|
||||
# 检查输入的中文名称是否在配置的新闻源中
|
||||
news_sources_list = [
|
||||
name.strip() for name in news_sources.split(";") if name.strip()
|
||||
]
|
||||
if source in news_sources_list:
|
||||
# 如果输入的中文名称在配置的新闻源中,在 CHANNEL_MAP 中查找对应的英文ID
|
||||
english_source_id = CHANNEL_MAP.get(source)
|
||||
|
||||
# 如果找不到对应的英文ID,使用默认源
|
||||
if not english_source_id:
|
||||
logger.bind(tag=TAG).warning(f"无效的新闻源: {source},使用默认源澎湃新闻")
|
||||
english_source_id = "thepaper"
|
||||
source = "澎湃新闻"
|
||||
|
||||
logger.bind(tag=TAG).info(f"获取新闻: 新闻源={source}({english_source_id})")
|
||||
|
||||
# 获取新闻列表
|
||||
news_items = fetch_news_from_api(conn, source)
|
||||
news_items = fetch_news_from_api(conn, english_source_id)
|
||||
|
||||
if not news_items:
|
||||
return ActionResponse(
|
||||
Action.REQLLM,
|
||||
f"抱歉,未能从{source_name}获取到新闻信息,请稍后再试或尝试其他新闻源。",
|
||||
f"抱歉,未能从{source}获取到新闻信息,请稍后再试或尝试其他新闻源。",
|
||||
None,
|
||||
)
|
||||
|
||||
@@ -193,14 +267,14 @@ def get_news_from_newsnow(
|
||||
conn.last_newsnow_link = {
|
||||
"url": selected_news.get("url", "#"),
|
||||
"title": selected_news.get("title", "未知标题"),
|
||||
"source_id": source,
|
||||
"source_id": english_source_id,
|
||||
}
|
||||
|
||||
# 构建新闻报告
|
||||
news_report = (
|
||||
f"根据下列数据,用{lang}回应用户的新闻查询请求:\n\n"
|
||||
f"新闻标题: {selected_news['title']}\n"
|
||||
# f"新闻来源: {source_name}\n"
|
||||
# f"新闻来源: {source}\n"
|
||||
f"(请以自然、流畅的方式向用户播报这条新闻标题,"
|
||||
f"提示用户可以要求获取详细内容,此时会获取新闻的详细内容。)"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user