mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
Merge branch 'py_test_end' into fix-end
This commit is contained in:
@@ -83,6 +83,7 @@ VAD:
|
||||
9、[知识库ragflow集成指南](./ragflow-integration.md)<br/>
|
||||
10、[如何部署上下文源](./context-provider-integration.md)<br/>
|
||||
11、[如何集成PowerMem智能记忆](./powermem-integration.md)<br/>
|
||||
12、[如何配置天气插件查询天气](./weather-integration.md)<br/>
|
||||
|
||||
### 11、语音克隆、本地语音部署相关教程
|
||||
1、[如何在智控台克隆音色](./huoshan-streamTTS-voice-cloning.md)<br/>
|
||||
|
||||
@@ -151,6 +151,11 @@ public interface Constant {
|
||||
*/
|
||||
String MEMORY_NO_MEM = "Memory_nomem";
|
||||
|
||||
/**
|
||||
* 仅上报聊天记录(不总结记忆)
|
||||
*/
|
||||
String MEMORY_MEM_REPORT_ONLY = "Memory_mem_report_only";
|
||||
|
||||
/**
|
||||
* 火山引擎双声道语音克隆
|
||||
*/
|
||||
@@ -304,7 +309,7 @@ public interface Constant {
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
public static final String VERSION = "0.9.1";
|
||||
public static final String VERSION = "0.9.2";
|
||||
|
||||
/**
|
||||
* 无效固件URL
|
||||
|
||||
@@ -251,4 +251,9 @@ public interface ErrorCode {
|
||||
int AGENT_TAG_NOT_EXIST = 10198; // 标签不存在
|
||||
|
||||
int RAG_DOCUMENT_PARSING_DELETE_ERROR = 10199; // 文档解析中,禁止删除
|
||||
|
||||
// 智能体MCP相关错误码
|
||||
int MCP_ACCESS_POINT_ADDRESS_NO_PERMISSION = 10200; // 没有权限查看该智能体的MCP接入点地址
|
||||
int MCP_ACCESS_POINT_ADDRESS_NOT_CONFIGURED = 10201; // 请联系管理员进入参数管理配置mcp接入点地址
|
||||
int MCP_ACCESS_POINT_TOOLS_LIST_NO_PERMISSION = 10202; // 没有权限查看该智能体的MCP工具列表
|
||||
}
|
||||
|
||||
@@ -12,28 +12,29 @@ import xiaozhi.modules.sys.service.SysParamsService;
|
||||
* 封装了重复的SM2解密、验证码提取和验证逻辑
|
||||
*/
|
||||
public class Sm2DecryptUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 验证码长度
|
||||
*/
|
||||
private static final int CAPTCHA_LENGTH = 5;
|
||||
|
||||
|
||||
/**
|
||||
* 解密SM2加密内容,提取验证码并验证
|
||||
*
|
||||
* @param encryptedPassword SM2加密的密码字符串
|
||||
* @param captchaId 验证码ID
|
||||
* @param captchaService 验证码服务
|
||||
* @param sysParamsService 系统参数服务
|
||||
* @param captchaId 验证码ID
|
||||
* @param captchaService 验证码服务
|
||||
* @param sysParamsService 系统参数服务
|
||||
* @return 解密后的实际密码
|
||||
*/
|
||||
public static String decryptAndValidateCaptcha(String encryptedPassword, String captchaId,
|
||||
CaptchaService captchaService, SysParamsService sysParamsService) {
|
||||
public static String decryptAndValidateCaptcha(String encryptedPassword, String captchaId,
|
||||
CaptchaService captchaService, SysParamsService sysParamsService) {
|
||||
// 获取SM2私钥
|
||||
String privateKeyStr = sysParamsService.getValue(Constant.SM2_PRIVATE_KEY, true);
|
||||
if (StringUtils.isBlank(privateKeyStr)) {
|
||||
throw new RenException(ErrorCode.SM2_KEY_NOT_CONFIGURED);
|
||||
}
|
||||
|
||||
|
||||
// 使用SM2私钥解密密码
|
||||
String decryptedContent;
|
||||
try {
|
||||
@@ -41,19 +42,20 @@ public class Sm2DecryptUtil {
|
||||
} catch (Exception e) {
|
||||
throw new RenException(ErrorCode.SM2_DECRYPT_ERROR);
|
||||
}
|
||||
|
||||
|
||||
// 分离验证码和密码:前5位是验证码,后面是密码
|
||||
if (decryptedContent.length() > CAPTCHA_LENGTH) {
|
||||
String embeddedCaptcha = decryptedContent.substring(0, CAPTCHA_LENGTH);
|
||||
String actualPassword = decryptedContent.substring(CAPTCHA_LENGTH);
|
||||
|
||||
// 验证嵌入的验证码是否正确
|
||||
|
||||
boolean embeddedCaptchaValid = captchaService.validate(captchaId, embeddedCaptcha, true);
|
||||
if (!embeddedCaptchaValid) {
|
||||
throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR);
|
||||
}
|
||||
|
||||
|
||||
return actualPassword;
|
||||
} else if (decryptedContent.length() > 0) {
|
||||
throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR);
|
||||
} else {
|
||||
throw new RenException(ErrorCode.SM2_DECRYPT_ERROR);
|
||||
}
|
||||
|
||||
+4
-3
@@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.modules.agent.service.AgentMcpAccessPointService;
|
||||
@@ -40,11 +41,11 @@ public class AgentMcpAccessPointController {
|
||||
|
||||
// 检查权限
|
||||
if (!agentService.checkAgentPermission(agentId, user.getId())) {
|
||||
return new Result<String>().error("没有权限查看该智能体的MCP接入点地址");
|
||||
return new Result<String>().error(ErrorCode.MCP_ACCESS_POINT_ADDRESS_NO_PERMISSION);
|
||||
}
|
||||
String agentMcpAccessAddress = agentMcpAccessPointService.getAgentMcpAccessAddress(agentId);
|
||||
if (agentMcpAccessAddress == null) {
|
||||
return new Result<String>().ok("请联系管理员进入参数管理配置mcp接入点地址");
|
||||
return new Result<String>().error(ErrorCode.MCP_ACCESS_POINT_ADDRESS_NOT_CONFIGURED);
|
||||
}
|
||||
return new Result<String>().ok(agentMcpAccessAddress);
|
||||
}
|
||||
@@ -58,7 +59,7 @@ public class AgentMcpAccessPointController {
|
||||
|
||||
// 检查权限
|
||||
if (!agentService.checkAgentPermission(agentId, user.getId())) {
|
||||
return new Result<List<String>>().error("没有权限查看该智能体的MCP工具列表");
|
||||
return new Result<List<String>>().error(ErrorCode.MCP_ACCESS_POINT_TOOLS_LIST_NO_PERMISSION);
|
||||
}
|
||||
List<String> agentMcpToolsList = agentMcpAccessPointService.getAgentMcpToolsList(agentId);
|
||||
return new Result<List<String>>().ok(agentMcpToolsList);
|
||||
|
||||
+17
-9
@@ -14,6 +14,7 @@ import org.springframework.stereotype.Service;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
||||
import xiaozhi.modules.agent.dto.AgentChatSummaryDTO;
|
||||
import xiaozhi.modules.agent.dto.AgentMemoryDTO;
|
||||
@@ -90,21 +91,28 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
||||
@Override
|
||||
public boolean generateAndSaveChatSummary(String sessionId) {
|
||||
try {
|
||||
// 1. 生成总结
|
||||
AgentChatSummaryDTO summaryDTO = generateChatSummary(sessionId);
|
||||
if (!summaryDTO.isSuccess()) {
|
||||
log.info("生成总结失败: {}", summaryDTO.getErrorMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 获取设备信息(通过会话关联的设备)
|
||||
// 1. 获取设备信息(通过会话关联的设备)
|
||||
DeviceEntity device = getDeviceBySessionId(sessionId);
|
||||
if (device == null) {
|
||||
log.info("未找到与会话 {} 关联的设备", sessionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 更新智能体记忆
|
||||
// 2. 检查记忆模型类型,如果是仅上报聊天记录模式则跳过总结
|
||||
String memModelId = agentService.getAgentById(device.getAgentId()).getMemModelId();
|
||||
if (memModelId != null && memModelId.equals(Constant.MEMORY_MEM_REPORT_ONLY)) {
|
||||
log.info("会话 {} 使用仅上报聊天记录模式,跳过记忆总结", sessionId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. 生成总结
|
||||
AgentChatSummaryDTO summaryDTO = generateChatSummary(sessionId);
|
||||
if (!summaryDTO.isSuccess()) {
|
||||
log.info("生成总结失败: {}", summaryDTO.getErrorMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. 更新智能体记忆
|
||||
AgentMemoryDTO memoryDTO = new AgentMemoryDTO();
|
||||
memoryDTO.setSummaryMemory(summaryDTO.getSummary());
|
||||
|
||||
|
||||
+2
-1
@@ -57,7 +57,8 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
||||
.eq("model_type", modelType)
|
||||
.eq("is_enabled", 1)
|
||||
.like(StringUtils.isNotBlank(modelName), "model_name", modelName)
|
||||
.select("id", "model_name"));
|
||||
.select("id", "model_name")
|
||||
.orderByAsc("sort"));
|
||||
return ConvertUtils.sourceToTarget(entities, ModelBasicInfoDTO.class);
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -95,6 +95,7 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
|
||||
|
||||
QueryWrapper<ModelProviderEntity> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("model_type", StringUtils.isBlank(modelType) ? "" : modelType);
|
||||
queryWrapper.orderByAsc("sort");
|
||||
List<ModelProviderEntity> providerEntities = modelProviderDao.selectList(queryWrapper);
|
||||
return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class);
|
||||
}
|
||||
@@ -147,7 +148,8 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
modelProviderDTO.setUpdater(user.getId());
|
||||
modelProviderDTO.setUpdateDate(new Date());
|
||||
if (modelProviderDao.updateById(ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class)) == 0) {
|
||||
if (modelProviderDao
|
||||
.updateById(ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class)) == 0) {
|
||||
throw new RenException(ErrorCode.UPDATE_DATA_FAILED);
|
||||
}
|
||||
return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class);
|
||||
|
||||
@@ -17,4 +17,7 @@ public class VoiceCloneDTO {
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "语言")
|
||||
private String languages;
|
||||
}
|
||||
|
||||
+3
@@ -28,6 +28,9 @@ public class VoiceCloneResponseDTO {
|
||||
@Schema(description = "声音id")
|
||||
private String voiceId;
|
||||
|
||||
@Schema(description = "语言")
|
||||
private String languages;
|
||||
|
||||
@Schema(description = "用户ID(关联用户表)")
|
||||
private Long userId;
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ public class VoiceCloneEntity {
|
||||
@Schema(description = "声音id")
|
||||
private String voiceId;
|
||||
|
||||
@Schema(description = "语言")
|
||||
private String languages;
|
||||
|
||||
@Schema(description = "用户 ID(关联用户表)")
|
||||
private Long userId;
|
||||
|
||||
|
||||
+1
@@ -116,6 +116,7 @@ public class VoiceCloneServiceImpl extends BaseServiceImpl<VoiceCloneDao, VoiceC
|
||||
entity.setVoiceId(voiceId);
|
||||
entity.setName(namePrefix + "_" + index);
|
||||
entity.setUserId(dto.getUserId());
|
||||
entity.setLanguages(dto.getLanguages());
|
||||
entity.setTrainStatus(0); // 默认训练中
|
||||
batchInsertList.add(entity);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- 给声音克隆表添加语言字段
|
||||
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_voice_clone' AND COLUMN_NAME = 'languages');
|
||||
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `ai_voice_clone` ADD COLUMN `languages` VARCHAR(50) DEFAULT NULL COMMENT ''语言'' AFTER `voice_id`', 'SELECT ''Column languages already exists'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
@@ -0,0 +1,7 @@
|
||||
-- 新增仅上报聊天记录记忆模型供应器
|
||||
|
||||
delete from `ai_model_provider` where `id` = 'SYSTEM_Memory_mem_report_only';
|
||||
delete from `ai_model_config` where `id` = 'Memory_mem_report_only';
|
||||
|
||||
INSERT INTO `ai_model_provider` VALUES ('SYSTEM_Memory_mem_report_only', 'Memory', 'mem_report_only', '仅上报聊天记录', '[]', 4, 1, NOW(), 1, NOW());
|
||||
INSERT INTO `ai_model_config` VALUES ('Memory_mem_report_only', 'Memory', 'mem_report_only', '仅上报聊天记录', 0, 1, '{"type": "mem_report_only"}', NULL, '仅上报聊天记录,不总结记忆', 3, NULL, NULL, NULL, NULL);
|
||||
@@ -0,0 +1,2 @@
|
||||
-- 修改聊天内容字段类型
|
||||
ALTER TABLE ai_agent_chat_history MODIFY COLUMN content TEXT COMMENT '聊天内容';
|
||||
@@ -557,6 +557,13 @@ databaseChangeLog:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202603091051.sql
|
||||
- changeSet:
|
||||
id: 202603101723
|
||||
author: RanChen
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202603101723.sql
|
||||
- changeSet:
|
||||
id: 202603111131
|
||||
author: DaGou12138
|
||||
@@ -564,4 +571,18 @@ databaseChangeLog:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202603111131.sql
|
||||
|
||||
- changeSet:
|
||||
id: 202603231037
|
||||
author: rainv123
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202603231037.sql
|
||||
- changeSet:
|
||||
id: 202603311200
|
||||
author: cgd
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202603311200.sql
|
||||
|
||||
|
||||
@@ -206,4 +206,7 @@
|
||||
10197=\u6807\u7B7E\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
||||
10198=\u6807\u7B7E\u4E0D\u5B58\u5728
|
||||
10199=\u89E3\u6790\u4E2D\u6587\u4EF6\u6682\u4E0D\u652F\u6301\u6B64\u64CD\u4F5C
|
||||
10200=\u6CA1\u6709\u6743\u9650\u67E5\u770B\u8BE5\u667A\u80FD\u4F53\u7684MCP\u63A5\u5165\u70B9\u5730\u5740
|
||||
10201=\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u8FDB\u5165\u53C2\u6570\u7BA1\u7406\u914D\u7F6Emcp\u63A5\u5165\u70B9\u5730\u5740
|
||||
10202=\u6CA1\u6709\u6743\u9650\u67E5\u770B\u8BE5\u667A\u80FD\u4F53\u7684MCP\u5DE5\u5177\u5217\u8868
|
||||
|
||||
|
||||
@@ -206,4 +206,7 @@
|
||||
10197=Tag-Name darf nicht leer sein
|
||||
10198=Tag nicht gefunden
|
||||
10199=Dateianalyse l\u00E4uft, dieser Vorgang wird nicht unterst\u00FCtzt
|
||||
10200=Keine Berechtigung, die MCP-Endpunktadresse dieses Agenten anzuzeigen
|
||||
10201=Bitte kontaktieren Sie den Administrator, um die MCP-Endpunktadresse in der Parameterverwaltung zu konfigurieren
|
||||
10202=Keine Berechtigung, die MCP-Tool-Liste dieses Agenten anzuzeigen
|
||||
|
||||
|
||||
@@ -206,4 +206,7 @@
|
||||
10197=Tag name cannot be empty
|
||||
10198=Tag not found
|
||||
10199=Parsing in progress, this operation is not supported
|
||||
10200=No permission to view the MCP endpoint address of this agent
|
||||
10201=Please contact the administrator to configure the MCP endpoint address in parameter management
|
||||
10202=No permission to view the MCP tool list of this agent
|
||||
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
#Portugu\u00EAs do Brasil
|
||||
500=Erro interno do servidor
|
||||
401=N\u00E3o autorizado
|
||||
403=Acesso negado, sem permiss\u00E3o
|
||||
|
||||
10001={0} n\u00E3o pode ser vazio
|
||||
10002=O registro j\u00E1 existe no banco de dados
|
||||
10003=Falha ao obter par\u00E2metros
|
||||
10004=Conta ou senha incorretos
|
||||
10005=Conta desativada
|
||||
10006=Identificador \u00FAnico n\u00E3o pode ser vazio
|
||||
10007=C\u00F3digo de verifica\u00E7\u00E3o incorreto
|
||||
10008=Exclua primeiro o submenu ou bot\u00E3o
|
||||
10009=Senha atual incorreta
|
||||
10010=Conta ou senha incorretos, voc\u00EA ainda tem {0} tentativas
|
||||
|
||||
10011=Erro na sele\u00E7\u00E3o do departamento superior
|
||||
10012=O menu superior n\u00E3o pode ser ele mesmo
|
||||
10013=Interface de permiss\u00E3o de dados, apenas par\u00E2metros do tipo Map
|
||||
10014=Exclua primeiro o departamento inferior
|
||||
10015=Exclua primeiro os usu\u00E1rios do departamento
|
||||
10016=Falha na implanta\u00E7\u00E3o, sem fluxo
|
||||
10017=Diagrama incorreto, verifique
|
||||
10018=Falha na exporta\u00E7\u00E3o, ID do modelo � {0}
|
||||
|
||||
10019=Por favor, fa\u00E7a upload do arquivo
|
||||
10020=Token n\u00E3o pode ser vazio
|
||||
10021=Token expirado, fa\u00E7a login novamente
|
||||
10022=Conta bloqueada
|
||||
10023=Por favor, fa\u00E7a upload de arquivos nos formatos zip, bar, bpmn, bpmn20.xml
|
||||
|
||||
10024=Falha no upload do arquivo {0}
|
||||
10025=Falha ao enviar SMS {0}
|
||||
10026=Modelo de e-mail n\u00E3o existe
|
||||
|
||||
10027=Erro no servi\u00E7o Redis
|
||||
10028=Falha na tarefa agendada
|
||||
10029=N\u00E3o pode conter caracteres ilegais
|
||||
10030=Comprimento da senha insuficiente, m\u00EDnimo de {0} caracteres
|
||||
10031=A senha deve conter n\u00FAmeros, letras mai\u00FAsculas e min\u00FAsculas e caracteres especiais
|
||||
10032=Erro ao excluir dados
|
||||
10033=C\u00F3digo de verifica\u00E7\u00E3o do dispositivo incorreto
|
||||
|
||||
10034=Valor do par\u00E2metro n\u00E3o pode ser vazio
|
||||
10035=Tipo do par\u00E2metro n\u00E3o pode ser vazio
|
||||
10036=Tipo de par\u00E2metro n\u00E3o suportado
|
||||
10037=Valor do par\u00E2metro deve ser um n\u00FAmero v\u00E1lido
|
||||
10038=Valor do par\u00E2metro deve ser true ou false
|
||||
10039=Valor do par\u00E2metro deve ser um formato de array JSON v\u00E1lido
|
||||
10040=Valor do par\u00E2metro deve ser um formato JSON v\u00E1lido
|
||||
|
||||
10041=Dispositivo n\u00E3o encontrado
|
||||
10042={0}
|
||||
10043=Falha ao excluir dados
|
||||
10044=Usu\u00E1rio n\u00E3o logged in
|
||||
10045=Falha na conex\u00E3o WebSocket ou conex\u00E3o expirada
|
||||
10046=Erro ao salvar impress\u00E3o vocal, entre em contato com o administrador
|
||||
10047=Limite di\u00E1rio de envio atingido
|
||||
10048=Erro na inser\u00E7\u00E3o da senha antiga
|
||||
10049=LLM configurado n\u00E3o � openai ou Collama
|
||||
10050=Falha na gera\u00E7\u00E3o do token
|
||||
10051=Recurso n\u00E3o existe
|
||||
10052=Agente padr\u00E3o n\u00E3o encontrado
|
||||
10053=Agente n\u00E3o encontrado
|
||||
10054=Interface de impress\u00E3o vocal n\u00E3o configurada, configure primeiro o endere\u00E7o da interface no par\u00E2metro (server.voice_print)
|
||||
10055=Falha no envio do SMS
|
||||
10056=Falha ao estabelecer conex\u00E3o SMS
|
||||
10057=Falha ao criar impress\u00E3o vocal do agente
|
||||
10058=Falha ao atualizar a impress\u00E3o vocal correspondente do agente
|
||||
10059=Falha ao excluir a impress\u00E3o vocal correspondente do agente
|
||||
10060=Envio muito frequente, tente novamente ap\u00F3s {0} segundos
|
||||
10061=C\u00F3digo de ativa\u00E7\u00E3o n\u00E3o pode ser vazio
|
||||
10062=C\u00F3digo de ativa\u00E7\u00E3o incorreto
|
||||
10063=Dispositivo j\u00E1 ativado
|
||||
10064=Este modelo � o modelo padr\u00E3o, configure primeiro outro modelo como padr\u00E3o
|
||||
10065=Falha ao adicionar dados
|
||||
10066=Falha ao modificar dados
|
||||
10067=C\u00F3digo de verifica\u00E7\u00E3o gr\u00E1fico incorreto
|
||||
10068=Registro por telefone n\u00E3o habilitado, fun\u00E7\u00E3o de c\u00F3digo de verifica\u00E7\u00E3o por SMS indispon\u00EDvel
|
||||
10069=Nome de usu\u00E1rio n\u00E3o � n\u00FAmero de telefone, por favor insira novamente
|
||||
10070=Este n\u00FAmero de telefone j\u00E1 est\u00E1 registrado
|
||||
10071=N\u00FAmero de telefone inserido n\u00E3o est\u00E1 registrado
|
||||
10072=Registro de usu\u00E1rio comum n\u00E3o permitido atualmente
|
||||
10073=Registro por telefone n\u00E3o habilitado, fun\u00E7\u00E3o de recupera\u00E7\u00E3o de senha indispon\u00EDvel
|
||||
10074=Formato de n\u00FAmero de telefone incorreto
|
||||
10075=C\u00F3digo de verifica\u00E7\u00E3o do telefone incorreto
|
||||
10076=Tipo de dicion\u00E1rio n\u00E3o existe
|
||||
10077=C\u00F3digo de tipo de dicion\u00E1rio duplicado
|
||||
10078=Falha ao carregar recurso
|
||||
10079=Par\u00E2metros de sele\u00E7\u00E3o incompat\u00EDveis entre LLM e reconhecimento de inten\u00E7\u00E3o Intent
|
||||
10080=A pessoa ({0}) correspondente a esta impress\u00E3o vocal j\u00E1 est\u00E1 registrada, escolha outra voz para registrar
|
||||
10081=Erro ao excluir impress\u00E3o vocal
|
||||
10082=Esta modifica\u00E7\u00E3o n\u00E3o � permitida, esta voz j\u00E1 est\u00E1 registrada como impress\u00E3o vocal ({0})
|
||||
10083=Erro ao modificar impress\u00E3o vocal, entre em contato com o administrador
|
||||
10084=Erro no endere\u00E7o da interface de impress\u00E3o vocal, acesse o gerenciamento de par\u00E2metros para modificar o endere\u00E7o
|
||||
10085=Dados de \u00E1udio n\u00E3o pertencem a este agente
|
||||
10086=Dados de \u00E1udio est\u00E3o vazios, verifique os dados enviados
|
||||
10087=Falha ao salvar impress\u00E3o vocal, falha na requisi\u00E7\u00E3o
|
||||
10088=Falha ao salvar impress\u00E3o vocal, falha no processamento
|
||||
10089=Falha ao cancelar impress\u00E3o vocal, falha na requisi\u00E7\u00E3o
|
||||
10090=Falha ao cancelar impress\u00E3o vocal, falha no processamento
|
||||
10091=Provedor n\u00E3o existe
|
||||
10092=LLM configurado n\u00E3o existe
|
||||
10093=Esta configura\u00E7\u00E3o de modelo foi referenciada pelo agente {0}, n\u00E3o pode ser exclu\u00EDda
|
||||
10094=Este modelo LLM foi referenciado pela configura\u00E7\u00E3o de reconhecimento de inten\u00E7\u00E3o, n\u00E3o pode ser exclu\u00EDdo
|
||||
10095=Opera\u00E7\u00E3o de servidor inv\u00E1lida
|
||||
10096=Endere\u00E7o WebSocket do servidor n\u00E3o configurado
|
||||
10097=Endere\u00E7o WebSocket de destino n\u00E3o existe
|
||||
10098=Lista de endere\u00E7os WebSocket n\u00E3o pode ser vazia
|
||||
10099=Endere\u00E7o WebSocket n\u00E3o pode usar localhost ou 127.0.0.1
|
||||
10100=Formato de endere\u00E7o WebSocket incorreto
|
||||
10101=Falha no teste de conex\u00E3o WebSocket
|
||||
10102=Endere\u00E7o OTA n\u00E3o pode ser vazio
|
||||
10103=Endere\u00E7o OTA n\u00E3o pode usar localhost ou 127.0.0.1
|
||||
10104=Endere\u00E7o OTA deve come\u00E7ar com http ou https
|
||||
10105=Endere\u00E7o OTA deve terminar com /ota/
|
||||
10106=Falha ao acessar interface OTA
|
||||
10107=Formato de conte\u00FAdo retornado pela interface OTA incorreto
|
||||
10108=Falha na valida\u00E7\u00E3o da interface OTA
|
||||
10109=Endere\u00E7o MCP n\u00E3o pode ser vazio
|
||||
10110=Endere\u00E7o MCP n\u00E3o pode usar localhost ou 127.0.0.1
|
||||
10111=Endere\u00E7o MCP incorreto
|
||||
10112=Falha ao acessar interface MCP
|
||||
10113=Formato de conte\u00FAdo retornado pela interface MCP incorreto
|
||||
10114=Falha na valida\u00E7\u00E3o da interface MCP
|
||||
10115=Endere\u00E7o da interface de impress\u00E3o vocal n\u00E3o pode ser vazio
|
||||
10116=Endere\u00E7o da interface de impress\u00E3o vocal n\u00E3o pode usar localhost ou 127.0.0.1
|
||||
10117=Endere\u00E7o da interface de impress\u00E3o vocal incorreto
|
||||
10118=Endere\u00E7o da interface de impress\u00E3o vocal deve come\u00E7ar com http ou https
|
||||
10119=Falha ao acessar interface de impress\u00E3o vocal
|
||||
10120=Formato de conte\u00FAdo retornado pela interface de impress\u00E3o vocal incorreto
|
||||
10121=Falha na valida\u00E7\u00E3o da interface de impress\u00E3o vocal
|
||||
10122=Chave mqtt n\u00E3o pode ser vazia
|
||||
10123=Comprimento da chave mqtt inseguro, deve ter pelo menos 8 caracteres e conter letras mai\u00FAsculas e min\u00FAsculas
|
||||
10124=Comprimento da chave mqtt inseguro, deve conter letras mai\u00FAsculas e min\u00FAsculas
|
||||
10125=Sua chave mqtt cont\u00E9m senha fraca
|
||||
10128=Etiqueta de dicion\u00E1rio duplicada
|
||||
10129=Chave SM2 n\u00E3o configurada
|
||||
10130=Falha na descriptografia SM2
|
||||
10131=modelType e provideCode n\u00E3o podem ser vazios
|
||||
10132=Sem permiss\u00E3o para visualizar registros de chat deste agente
|
||||
10133=ID da sess\u00E3o n\u00E3o pode ser vazio
|
||||
10134=ID do agente n\u00E3o pode ser vazio
|
||||
10135=Falha ao baixar registros de chat
|
||||
10136=Link de download expirado ou inv\u00E1lido
|
||||
10137=Link de download inv\u00E1lido
|
||||
10138=Usu\u00E1rio
|
||||
10139=Agente
|
||||
10140=Arquivo de \u00E1udio n\u00E3o pode ser vazio
|
||||
10141=Apenas arquivos de \u00E1udio s\u00E3o suportados
|
||||
10142=Tamanho do arquivo de \u00E1udio n\u00E3o pode exceder 10MB
|
||||
10143=Falha no upload
|
||||
10144=Registro de clonagem de voz n\u00E3o existe
|
||||
10145=Informa\u00E7\u00F5es de recurso de tom de voz n\u00E3o podem ser vazias
|
||||
10146=Nome da plataforma n\u00E3o pode ser vazio
|
||||
10147=ID do tom de voz n\u00E3o pode ser vazio
|
||||
10148=N\u00FAmero da conta atribu\u00EDda n\u00E3o pode ser vazio
|
||||
10149=IDs de recursos de tom de voz a serem exclu\u00EDdos n\u00E3o podem ser vazios
|
||||
10150=Voc\u00EA n\u00E3o tem permiss\u00E3o para operar este registro
|
||||
10151=Por favor, fa\u00E7a upload do arquivo de \u00E1udio primeiro
|
||||
10152=Configura\u00E7\u00E3o do modelo n\u00E3o encontrada
|
||||
10153=Tipo de modelo n\u00E3o encontrado
|
||||
10154=Falha no treinamento: {0}
|
||||
10155=Configura\u00E7\u00E3o ausente do mecanismo Huoshan
|
||||
10156=Erro de formato de resposta, campo BaseResp ausente
|
||||
10157=Falha na requisi\u00E7\u00E3o
|
||||
10158=Clonagem de tom de voz:
|
||||
10159=ID do tom de voz j\u00E1 existe
|
||||
10160=Formato de ID do tom de voz do mecanismo Huoshan incorreto, deve come\u00E7ar com S_
|
||||
10161=Endere\u00E7o Mac j\u00E1 existe
|
||||
10162=Provedor de modelos n\u00E3o existe
|
||||
10163=Registro da base de conhecimento n\u00E3o existe
|
||||
10164=Configura\u00E7\u00E3o RAG n\u00E3o encontrada
|
||||
10165=Erro de tipo de configura\u00E7\u00E3o RAG
|
||||
10166=Configura\u00E7\u00E3o RAG padr\u00E3o n\u00E3o encontrada
|
||||
10167=Falha na chamada RAG, {0}
|
||||
10168=Falha no upload do arquivo
|
||||
10169=Voc\u00EA n\u00E3o tem permiss\u00E3o para operar este registro
|
||||
10170=Nome da base de conhecimento duplicado
|
||||
10171=base_url da configura\u00E7\u00E3o RAG n\u00E3o pode ser vazio
|
||||
10172=api_key da configura\u00E7\u00E3o RAG n\u00E3o pode ser vazio
|
||||
10173=api_key da configura\u00E7\u00E3o RAG n\u00E3o pode ser vazio, substitua pelo par\u00E2metro API obtido
|
||||
10174=Formato de base_url da configura\u00E7\u00E3o RAG incorreto, deve come\u00E7ar com http ou https
|
||||
10175=Endere\u00E7o mac n\u00E3o pode ser vazio
|
||||
10176=dataset_id da configura\u00E7\u00E3o RAG n\u00E3o pode ser vazio
|
||||
10177=model_id da configura\u00E7\u00E3o RAG n\u00E3o pode ser vazio
|
||||
10178=dataset_id e model_id da configura\u00E7\u00E3o RAG n\u00E3o podem ser vazios
|
||||
10179=Nome do arquivo n\u00E3o pode ser vazio
|
||||
10180=Conte\u00FAdo do arquivo n\u00E3o pode ser vazio
|
||||
10181=Nome da clonagem de tom de voz n\u00E3o pode ser vazio
|
||||
10182=\u00C1udio de clonagem de tom de voz n\u00E3o existe
|
||||
10183=Agente padr\u00E3o n\u00E3o encontrado
|
||||
10184=Tipo de adaptador n\u00E3o suportado
|
||||
10185=Falha na valida\u00E7\u00E3o da configura\u00E7\u00E3o RAG
|
||||
10186=Falha ao criar adaptador
|
||||
10187=Falha ao inicializar adaptador
|
||||
10188=Falha no teste de conex\u00E3o do adaptador
|
||||
10189=Falha na opera\u00E7\u00E3o do adaptador
|
||||
10190=Adaptador n\u00E3o encontrado
|
||||
10191=Erro de cache do adaptador
|
||||
10192=Tipo de adaptador n\u00E3o encontrado
|
||||
10193=ID do dispositivo n\u00E3o pode ser vazio
|
||||
10194=Dispositivo n\u00E3o existe ou est\u00E1 offline
|
||||
10195=Limite de uploads OTA excedido
|
||||
10196=Nome da etiqueta j\u00E1 existe
|
||||
10197=Nome da etiqueta n\u00E3o pode ser vazio
|
||||
10198=Etiqueta n\u00E3o existe
|
||||
10199=Esta opera\u00E7\u00E3o n\u00E3o � suportada na an\u00E1lise de arquivos chineses
|
||||
10200=Sem permiss\u00e3o para visualizar o endere\u00e7o do endpoint MCP deste agente
|
||||
10201=Por favor, contate o administrador para configurar o endere\u00e7o do endpoint MCP no gerenciamento de par\u00e2metros
|
||||
10202=Sem permiss\u00e3o para visualizar a lista de ferramentas MCP deste agente
|
||||
@@ -206,4 +206,6 @@
|
||||
10197=T\u00EAn th\u1EB9\uFFFD kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
|
||||
10198=Kh\u00F4ng t\u00ECm th\u1EA5y th\u1EB9\uFFFD
|
||||
10199=T\u1EC7p \u0111ang \u0111\u01B0\u1EE3c ph\u00E2n t\u00EDch, thao t\u00E1c n\u00E0y kh\u00F4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3
|
||||
|
||||
10200=Kh\u00f4ng c\u00f3 quy\u1ec1n xem \u0111\u1ecba ch\u1ec9 \u0111i\u1ec3m cu\u1ed1i MCP c\u1ee7a \u0111\u1ea1i l\u00fd n\u00e0y
|
||||
10201=Vui l\u00f2ng li\u00ean h\u1ec7 qu\u1ea3n tr\u1ecb vi\u00ean \u0111\u1ec3 c\u1ea5u h\u00ecnh \u0111\u1ecba ch\u1ec9 \u0111i\u1ec3m cu\u1ed1i MCP trong qu\u1ea3n l\u00fd tham s\u1ed1
|
||||
10202=Kh\u00f4ng c\u00f3 quy\u1ec1n xem danh s\u00e1ch c\u00f4ng c\u1ee5 MCP c\u1ee7a \u0111\u1ea1i l\u00fd n\u00e0y
|
||||
@@ -202,4 +202,10 @@
|
||||
10193=\u8BBE\u5907ID\u4E0D\u80FD\u4E3A\u7A7A
|
||||
10194=\u8BBE\u5907\u4E0D\u5B58\u5728\u6216\u4E0D\u5728\u7EBF
|
||||
10195=OTA\u4E0A\u4F20\u6B21\u6570\u8D85\u8FC7\u9650\u5236
|
||||
10196=\u89E3\u6790\u4E2D\u6587\u4EF6\u6682\u4E0D\u652F\u6301\u6B64\u64CD\u4F5C
|
||||
10196=\u6807\u7B7E\u540D\u79F0\u5DF2\u5B58\u5728
|
||||
10197=\u6807\u7B7E\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
||||
10198=\u6807\u7B7E\u4E0D\u5B58\u5728
|
||||
10199=\u89E3\u6790\u4E2D\u6587\u4EF6\u6682\u4E0D\u652F\u6301\u6B64\u64CD\u4F5C
|
||||
10200=\u6CA1\u6709\u6743\u9650\u67E5\u770B\u8BE5\u667A\u80FD\u4F53\u7684MCP\u63A5\u5165\u70B9\u5730\u5740
|
||||
10201=\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u8FDB\u5165\u53C2\u6570\u7BA1\u7406\u914D\u7F6Emcp\u63A5\u5165\u70B9\u5730\u5740
|
||||
10202=\u6CA1\u6709\u6743\u9650\u67E5\u770B\u8BE5\u667A\u80FD\u4F53\u7684MCP\u5DE5\u5177\u5217\u8868
|
||||
|
||||
@@ -206,4 +206,6 @@
|
||||
10197=\u6A19\u7C64\u540D\u7A31\u4E0D\u80FD\u4E3A\u7A7A
|
||||
10198=\u6A19\u7C64\u4E0D\u5B58\u5728
|
||||
10199=\u89E3\u6790\u4E2D\u6587\u4EF6\u66AB\u4E0D\u652F\u6301\u6B64\u64CD\u4F5C
|
||||
|
||||
10200=\u6C92\u6709\u6B0A\u9650\u67E5\u770B\u8A72\u667A\u80FD\u9AD4\u7684MCP\u63A5\u5165\u9EDE\u5730\u5740
|
||||
10201=\u8ACB\u806F\u7E6B\u7BA1\u7406\u54E1\u9032\u5165\u53C3\u6578\u7BA1\u7406\u914D\u7F6Emcp\u63A5\u5165\u9EDE\u5730\u5740
|
||||
10202=\u6C92\u6709\u6B0A\u9650\u67E5\u770B\u8A72\u667A\u80FD\u9AD4\u7684MCP\u5DE5\u5177\u5217\u8868
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#Portugu\u00eas
|
||||
id.require=O ID n\u00e3o pode estar vazio
|
||||
id.null=O ID deve estar vazio
|
||||
|
||||
sort.number=O valor de classifica\u00e7\u00e3o n\u00e3o pode ser menor que 0
|
||||
page.number=O n\u00famero da p\u00e1gina n\u00e3o pode ser menor que 0
|
||||
limit.number=O n\u00famero de registros n\u00e3o pode ser menor que 0
|
||||
|
||||
sysdict.type.require=O tipo de dicion\u00e1rio n\u00e3o pode estar vazio
|
||||
sysdict.name.require=O nome do dicion\u00e1rio n\u00e3o pode estar vazio
|
||||
sysdict.label.require=A etiqueta do dicion\u00e1rio n\u00e3o pode estar vazio
|
||||
|
||||
sysparams.paramcode.require=A codifica\u00e7\u00e3o do par\u00e2metro n\u00e3o pode estar vazia
|
||||
sysparams.paramvalue.require=O valor do par\u00e2metro n\u00e3o pode estar vazio
|
||||
sysparams.valuetype.require=O tipo de valor n\u00e3o pode estar vazio
|
||||
sysparams.valuetype.pattern=O tipo de valor deve ser string, number, boolean ou array
|
||||
|
||||
sysuser.username.require=O nome de usu\u00e1rio n\u00e3o pode estar vazio
|
||||
sysuser.password.require=A senha n\u00e3o pode estar vazio
|
||||
sysuser.realname.require=O nome completo n\u00e3o pode estar vazio
|
||||
sysuser.gender.range=G\u00eanero varia de 0 a 2
|
||||
sysuser.email.error=Formato de e-mail incorreto
|
||||
sysuser.deptId.require=O departamento n\u00e3o pode estar vazio
|
||||
sysuser.status.range=Status varia de 0 a 1
|
||||
sysuser.captcha.require=O c\u00f3digo de verifica\u00e7\u00e3o n\u00e3o pode estar vazio
|
||||
sysuser.uuid.require=O identificador \u00fanico n\u00e3o pode estar vazio
|
||||
|
||||
timbre.languages.require=O idioma do timbre n\u00e3o pode estar vazio
|
||||
timbre.name.require=O nome do timbre n\u00e3o pode estar vazio
|
||||
timbre.ttsModelId.require=O ID do modelo TTS do timbre n\u00e3o pode estar vazio
|
||||
timbre.ttsVoice.require=O c\u00f3digo do timbre n\u00e3o pode estar vazio
|
||||
|
||||
ota.device.not.found=Dispositivo n\u00e3o encontrado
|
||||
ota.device.need.bind={0}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<mapper namespace="xiaozhi.modules.voiceclone.dao.VoiceCloneDao">
|
||||
<select id="getTrainSuccess" resultType="xiaozhi.modules.model.dto.VoiceDTO">
|
||||
select id, name, voice_id as voiceDemo
|
||||
select id, name, voice_id as voiceDemo, languages
|
||||
from ai_voice_clone
|
||||
where model_id = #{modelId} and user_id = #{userId} and train_status = 2
|
||||
</select>
|
||||
|
||||
@@ -131,6 +131,7 @@ export function getMcpAddress(agentId: string) {
|
||||
meta: {
|
||||
ignoreAuth: false,
|
||||
toast: false,
|
||||
isExposeError: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -208,11 +209,8 @@ export function updateAgentTags(agentId: string, data) {
|
||||
}
|
||||
|
||||
// 获取所有语言
|
||||
export function getAllLanguage(modelId, voiceName) {
|
||||
const queryParams = new URLSearchParams({
|
||||
voiceName: voiceName || '',
|
||||
}).toString()
|
||||
return http.Get(`/models/${modelId}/voices?${queryParams}`, {
|
||||
export function getAllLanguage(modelId: string) {
|
||||
return http.Get<{ id: string, name: string, languages: string }[]>(`/models/${modelId}/voices`, {
|
||||
meta: {
|
||||
ignoreAuth: false,
|
||||
toast: false,
|
||||
|
||||
@@ -48,6 +48,15 @@ export interface AgentDetail {
|
||||
ttsRate: number
|
||||
ttsPitch: number
|
||||
functions: AgentFunction[]
|
||||
contextProviders: Providers[]
|
||||
}
|
||||
|
||||
export interface Providers {
|
||||
url: string
|
||||
headers: Array<{
|
||||
key: string
|
||||
value: string
|
||||
}>
|
||||
}
|
||||
|
||||
export interface AgentFunction {
|
||||
|
||||
@@ -33,6 +33,22 @@ export function bindDevice(agentId: string, code: string) {
|
||||
return http.Post(`/device/bind/${agentId}/${code}`, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动添加设备
|
||||
* @param agentId 智能体ID
|
||||
* @param board 设备类型
|
||||
* @param appVersion 固件版本
|
||||
* @param macAddress MAC地址
|
||||
*/
|
||||
export function bindDeviceManual(data: {
|
||||
agentId: string
|
||||
board: string
|
||||
appVersion: string
|
||||
macAddress: string
|
||||
}) {
|
||||
return http.Post('/device/manual-add', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置设备OTA升级开关
|
||||
* @param deviceId 设备ID (MAC地址)
|
||||
|
||||
@@ -60,3 +60,13 @@ export function updateVoicePrint(data: VoicePrint) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 获取音频下载ID
|
||||
export function getAudioDownloadId(audioId: string) {
|
||||
return http.Post<string>(`/agent/audio/${audioId}`, {}, {
|
||||
meta: {
|
||||
ignoreAuth: false,
|
||||
toast: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,467 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { t } from '@/i18n'
|
||||
|
||||
interface Props {
|
||||
visible?: boolean
|
||||
providers?: Array<{
|
||||
url: string
|
||||
headers: Record<string, string>
|
||||
}>
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
providers: () => [],
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
'confirm': [value: Array<{
|
||||
url: string
|
||||
headers: Record<string, string>
|
||||
}>]
|
||||
}>()
|
||||
|
||||
const localProviders = ref<Array<{
|
||||
url: string
|
||||
headers: Array<{
|
||||
key: string
|
||||
value: string
|
||||
}>
|
||||
}>>([])
|
||||
|
||||
function initLocalData() {
|
||||
localProviders.value = props.providers.map((p) => {
|
||||
const headers = p.headers || {}
|
||||
return {
|
||||
url: p.url || '',
|
||||
headers: Object.entries(headers).map(([key, value]) => ({ key, value })),
|
||||
}
|
||||
})
|
||||
|
||||
if (localProviders.value.length === 0) {
|
||||
localProviders.value.push({ url: '', headers: [{ key: '', value: '' }] })
|
||||
}
|
||||
}
|
||||
|
||||
function addProvider(index: number) {
|
||||
localProviders.value.splice(index, 0, {
|
||||
url: '',
|
||||
headers: [{ key: '', value: '' }],
|
||||
})
|
||||
}
|
||||
|
||||
function removeProvider(index: number) {
|
||||
localProviders.value.splice(index, 1)
|
||||
}
|
||||
|
||||
function addHeader(pIndex: number, hIndex: number) {
|
||||
localProviders.value[pIndex].headers.splice(hIndex, 0, { key: '', value: '' })
|
||||
}
|
||||
|
||||
function removeHeader(pIndex: number, hIndex: number) {
|
||||
localProviders.value[pIndex].headers.splice(hIndex, 1)
|
||||
}
|
||||
|
||||
function handleConfirm() {
|
||||
const result = localProviders.value
|
||||
.filter(p => p.url.trim() !== '')
|
||||
.map((p) => {
|
||||
const headersObj: Record<string, string> = {}
|
||||
p.headers.forEach((h) => {
|
||||
if (h.key.trim()) {
|
||||
headersObj[h.key.trim()] = h.value
|
||||
}
|
||||
})
|
||||
return {
|
||||
url: p.url.trim(),
|
||||
headers: headersObj,
|
||||
}
|
||||
})
|
||||
|
||||
emit('confirm', result)
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
watch(() => props.visible, (val) => {
|
||||
if (val) {
|
||||
initLocalData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view v-if="visible" class="context-provider-dialog-mask" @click="handleClose">
|
||||
<view class="context-provider-dialog" @click.stop>
|
||||
<view class="dialog-header">
|
||||
<text class="dialog-title">
|
||||
{{ t('contextProviderDialog.title') }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="dialog-content">
|
||||
<view v-if="localProviders.length === 0" class="empty-container">
|
||||
<text class="empty-text">
|
||||
{{ t('contextProviderDialog.noContextApi') }}
|
||||
</text>
|
||||
<wd-button type="primary" size="small" @click="addProvider(0)">
|
||||
{{ t('contextProviderDialog.add') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
|
||||
<view v-else class="providers-list">
|
||||
<view v-for="(provider, pIndex) in localProviders" :key="pIndex" class="provider-item">
|
||||
<view class="provider-card">
|
||||
<view class="card-header">
|
||||
<view class="card-controls">
|
||||
<wd-button
|
||||
type="icon"
|
||||
icon="add"
|
||||
size="small"
|
||||
class="!h-[60rpx] !w-[60rpx] !bg-[#66b1ff] !text-[#fff]"
|
||||
@click="addProvider(pIndex + 1)"
|
||||
/>
|
||||
<wd-button
|
||||
type="icon"
|
||||
icon="decrease"
|
||||
size="small"
|
||||
class="!h-[60rpx] !w-[60rpx] !bg-[#F56C6C] !text-[#fff]"
|
||||
@click="removeProvider(pIndex)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="input-row">
|
||||
<text class="label-text">
|
||||
{{ t('contextProviderDialog.apiUrl') }}
|
||||
</text>
|
||||
<wd-input
|
||||
v-model="provider.url"
|
||||
class="flex-1"
|
||||
:placeholder="t('contextProviderDialog.apiUrlPlaceholder')"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="headers-section">
|
||||
<view class="label-text" style="margin-top: 6px;">
|
||||
{{ t('contextProviderDialog.requestHeaders') }}
|
||||
</view>
|
||||
<view class="headers-list">
|
||||
<view
|
||||
v-for="(header, hIndex) in provider.headers"
|
||||
:key="hIndex"
|
||||
class="header-row-vertical"
|
||||
>
|
||||
<view class="header-input-group">
|
||||
<wd-input
|
||||
v-model="header.key"
|
||||
placeholder="key"
|
||||
class="header-input-full"
|
||||
/>
|
||||
</view>
|
||||
<view class="header-input-group">
|
||||
<wd-input
|
||||
v-model="header.value"
|
||||
placeholder="value"
|
||||
class="header-input-full"
|
||||
/>
|
||||
</view>
|
||||
<view class="header-controls">
|
||||
<wd-button
|
||||
type="icon"
|
||||
icon="add"
|
||||
size="small"
|
||||
class="!h-[50rpx] !w-[50rpx] !border-[1rpx] !border-solid !bg-[#ecf5ff] !text-[#b3d8ff]"
|
||||
@click="addHeader(pIndex, hIndex + 1)"
|
||||
/>
|
||||
<wd-button
|
||||
type="icon"
|
||||
icon="decrease"
|
||||
size="small"
|
||||
class="!h-[50rpx] !w-[50rpx] !border-[1rpx] !border-solid !bg-[#fef0f0] !text-[#F56C6C]"
|
||||
@click="removeHeader(pIndex, hIndex)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="provider.headers.length === 0" class="header-row empty-header">
|
||||
<text class="no-header-text">
|
||||
{{ t('contextProviderDialog.noHeaders') }}
|
||||
</text>
|
||||
<wd-button type="text" size="small" @click="addHeader(pIndex, 0)">
|
||||
{{ t('contextProviderDialog.addHeader') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="dialog-footer">
|
||||
<wd-button class="cancel-btn" @click="handleClose">
|
||||
{{ t('contextProviderDialog.cancel') }}
|
||||
</wd-button>
|
||||
<wd-button type="primary" class="confirm-btn" @click="handleConfirm">
|
||||
{{ t('contextProviderDialog.confirm') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.context-provider-dialog-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.context-provider-dialog {
|
||||
background: #fff;
|
||||
// border-radius: 20rpx;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #232338;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
font-size: 40rpx;
|
||||
color: #9d9ea3;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
gap: 30rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #9d9ea3;
|
||||
}
|
||||
|
||||
.providers-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.provider-item {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.provider-card {
|
||||
flex: 1;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
border: 1px solid #eee;
|
||||
border-left: 6rpx solid #336cff;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #232338;
|
||||
}
|
||||
|
||||
.card-controls {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.headers-section {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.headers-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
background: #fcfcfc;
|
||||
padding: 4rpx;
|
||||
border-radius: 12rpx;
|
||||
border: 1px dashed #dcdfe6;
|
||||
}
|
||||
|
||||
.header-row-vertical {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
background: #fff;
|
||||
// padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
// border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.header-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.header-key-label,
|
||||
.header-value-label {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.header-input-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
// margin-top: 8rpx;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.block-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
padding-top: 10rpx;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.label-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #606266;
|
||||
width: 140rpx;
|
||||
}
|
||||
|
||||
.headers-section {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.headers-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
background: #fcfcfc;
|
||||
padding: 16rpx;
|
||||
border-radius: 12rpx;
|
||||
border: 1px dashed #dcdfe6;
|
||||
}
|
||||
|
||||
.header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.header-input {
|
||||
width: 200rpx;
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: #909399;
|
||||
font-weight: bold;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.row-controls {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
margin-left: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.empty-header {
|
||||
justify-content: center;
|
||||
padding: 20rpx;
|
||||
color: #909399;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.no-header-text {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.cancel-btn,
|
||||
.confirm-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -1,270 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { t } from '@/i18n'
|
||||
|
||||
interface Props {
|
||||
visible?: boolean
|
||||
settings?: {
|
||||
volume: number
|
||||
speed: number
|
||||
pitch: number
|
||||
}
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
visible: false,
|
||||
settings: () => ({
|
||||
volume: 0,
|
||||
speed: 0,
|
||||
pitch: 0,
|
||||
}),
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
'confirm': [value: {
|
||||
volume: number
|
||||
speed: number
|
||||
pitch: number
|
||||
}]
|
||||
}>()
|
||||
|
||||
const localSettings = ref({
|
||||
volume: 0,
|
||||
speed: 0,
|
||||
pitch: 0,
|
||||
})
|
||||
|
||||
function initLocalData() {
|
||||
localSettings.value = {
|
||||
volume: props.settings.volume || 0,
|
||||
speed: props.settings.speed || 0,
|
||||
pitch: props.settings.pitch || 0,
|
||||
}
|
||||
}
|
||||
|
||||
function handleConfirm() {
|
||||
emit('confirm', localSettings.value)
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
emit('update:visible', false)
|
||||
}
|
||||
|
||||
watch(() => props.visible, (val) => {
|
||||
if (val) {
|
||||
initLocalData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view v-if="visible" class="voice-settings-dialog-mask" @click="handleClose">
|
||||
<view class="voice-settings-dialog" @click.stop>
|
||||
<view class="dialog-header">
|
||||
<text class="dialog-title">
|
||||
{{ t('agent.languageConfig') }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="dialog-content">
|
||||
<!-- 音量调节 -->
|
||||
<view class="setting-item">
|
||||
<text class="setting-label">
|
||||
{{ t('agent.ttsVolume') }}
|
||||
</text>
|
||||
<view class="slider-container">
|
||||
<wd-slider
|
||||
v-model="localSettings.volume"
|
||||
:min="-100"
|
||||
:max="100"
|
||||
:step="1"
|
||||
:show-value="false"
|
||||
custom-class="voice-slider"
|
||||
/>
|
||||
<text class="slider-value">
|
||||
{{ localSettings.volume }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="setting-desc">
|
||||
{{ t('agent.volumeHint') }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 语速调节 -->
|
||||
<view class="setting-item">
|
||||
<text class="setting-label">
|
||||
{{ t('agent.ttsRate') }}
|
||||
</text>
|
||||
<view class="slider-container">
|
||||
<wd-slider
|
||||
v-model="localSettings.speed"
|
||||
:min="-100"
|
||||
:max="100"
|
||||
:step="1"
|
||||
:show-value="false"
|
||||
custom-class="voice-slider"
|
||||
/>
|
||||
<text class="slider-value">
|
||||
{{ localSettings.speed }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="setting-desc">
|
||||
{{ t('agent.speedHint') }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 音调调节 -->
|
||||
<view class="setting-item">
|
||||
<text class="setting-label">
|
||||
{{ t('agent.ttsPitch') }}
|
||||
</text>
|
||||
<view class="slider-container">
|
||||
<wd-slider
|
||||
v-model="localSettings.pitch"
|
||||
:min="-100"
|
||||
:max="100"
|
||||
:step="1"
|
||||
:show-value="false"
|
||||
custom-class="voice-slider"
|
||||
/>
|
||||
<text class="slider-value">
|
||||
{{ localSettings.pitch }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="setting-desc">
|
||||
{{ t('agent.pitchHint') }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="dialog-footer">
|
||||
<wd-button class="cancel-btn" @click="handleClose">
|
||||
{{ t('agent.cancel') }}
|
||||
</wd-button>
|
||||
<wd-button type="primary" class="confirm-btn" @click="handleConfirm">
|
||||
{{ t('agent.save') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.voice-settings-dialog-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.voice-settings-dialog {
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #232338;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
font-size: 40rpx;
|
||||
color: #9d9ea3;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 50rpx;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #232338;
|
||||
}
|
||||
|
||||
.slider-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.voice-slider {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.slider-value {
|
||||
font-size: 28rpx;
|
||||
color: #336cff;
|
||||
font-weight: 500;
|
||||
min-width: 80rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.setting-desc {
|
||||
font-size: 24rpx;
|
||||
color: #9d9ea3;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 30rpx;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.cancel-btn,
|
||||
.confirm-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background-color: #336cff !important;
|
||||
}
|
||||
|
||||
/* 自定义滑块样式 */
|
||||
:deep(.wd-slider) {
|
||||
--wd-slider-bar-background: #e6ebff;
|
||||
--wd-slider-bar-active-background: #336cff;
|
||||
--wd-slider-thumb-border-color: #336cff;
|
||||
--wd-slider-thumb-background: #336cff;
|
||||
--wd-slider-thumb-size: 32rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { uniappRequestAdapter } from '@alova/adapter-uniapp'
|
||||
import type { IResponse } from './types'
|
||||
import type { Language } from '@/store/lang'
|
||||
import AdapterUniapp from '@alova/adapter-uniapp'
|
||||
import { createAlova } from 'alova'
|
||||
import { createServerTokenAuthentication } from 'alova/client'
|
||||
@@ -8,6 +9,16 @@ import { getEnvBaseUrl } from '@/utils'
|
||||
import { toast } from '@/utils/toast'
|
||||
import { ContentTypeEnum, ResultEnum, ShowMessage } from './enum'
|
||||
|
||||
// 语言映射, 用于设置 Accept-language 头
|
||||
const langMap: Record<Language, string> = {
|
||||
zh_CN: 'zh-CN',
|
||||
en: 'en-US',
|
||||
zh_TW: 'zh-TW',
|
||||
de: 'de',
|
||||
vi: 'vi',
|
||||
pt_BR: 'pt-BR',
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建请求实例
|
||||
*/
|
||||
@@ -42,10 +53,26 @@ const alovaInstance = createAlova({
|
||||
statesHook: VueHook,
|
||||
|
||||
beforeRequest: onAuthRequired((method) => {
|
||||
// h5动态获取最新的 baseURL,确保使用用户设置的服务器地址
|
||||
const currentBaseUrl = getEnvBaseUrl()
|
||||
if (currentBaseUrl !== method.baseURL) {
|
||||
method.baseURL = currentBaseUrl
|
||||
}
|
||||
|
||||
// 检查混合内容错误(HTTPS页面请求HTTP接口)
|
||||
const currentProtocol = typeof window !== 'undefined' && window.location.protocol
|
||||
const requestProtocol = method.baseURL?.split(':')[0]
|
||||
const currentLang = langMap[uni.getStorageSync('app_language') as Language || 'zh_CN']
|
||||
if (currentProtocol === 'https:' && requestProtocol === 'http') {
|
||||
const errorMessage = '无法配置http协议地址,请检查接口地址'
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
// 设置默认 Content-Type
|
||||
method.config.headers = {
|
||||
'Content-Type': ContentTypeEnum.JSON,
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'Accept-language': currentLang,
|
||||
...method.config.headers,
|
||||
}
|
||||
|
||||
@@ -55,14 +82,14 @@ const alovaInstance = createAlova({
|
||||
|
||||
// 处理认证信息
|
||||
if (!ignoreAuth) {
|
||||
const token = uni.getStorageSync('token')
|
||||
if (!token) {
|
||||
const authInfo = JSON.parse(uni.getStorageSync('token') || '{}')
|
||||
if (!authInfo.token) {
|
||||
// 跳转到登录页
|
||||
uni.reLaunch({ url: '/pages/login/index' })
|
||||
throw new Error('[请求错误]:未登录')
|
||||
}
|
||||
// 添加 Authorization 头
|
||||
method.config.headers.Authorization = `Bearer ${token}`
|
||||
method.config.headers.Authorization = `Bearer ${authInfo.token}`
|
||||
}
|
||||
|
||||
// 处理动态域名
|
||||
|
||||
@@ -12,6 +12,7 @@ export enum ResultEnum {
|
||||
ServiceUnavailable = 503, // 服务不可用(原为serviceUnavailable)
|
||||
GatewayTimeout = 504, // 网关超时(原为gatewayTimeout)
|
||||
HttpVersionNotSupported = 505, // HTTP版本不支持(原为httpVersionNotSupported)
|
||||
MixedContent = 600, // 混合内容错误(HTTPS页面请求HTTP接口)
|
||||
}
|
||||
export enum ContentTypeEnum {
|
||||
JSON = 'application/json;charset=UTF-8',
|
||||
@@ -59,6 +60,9 @@ export function ShowMessage(status: number | string): string {
|
||||
case 505:
|
||||
message = 'HTTP版本不受支持(505)'
|
||||
break
|
||||
case 600:
|
||||
message = '混合内容错误(600)'
|
||||
break
|
||||
default:
|
||||
message = `连接出错(${status})!`
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
'login.navigationTitle': 'Anmelden',
|
||||
'login.fetchConfigError': 'Konfiguration konnte nicht abgerufen werden:',
|
||||
'login.selectLanguage': 'Sprache auswählen',
|
||||
'login.selectLanguageTip': 'Vi',
|
||||
'login.selectLanguageTip': 'De',
|
||||
'login.welcomeBack': 'Willkommen zurück',
|
||||
'login.pleaseLogin': 'Bitte melden Sie sich an',
|
||||
'login.enterUsername': 'Bitte Benutzernamen eingeben',
|
||||
@@ -30,6 +30,8 @@ export default {
|
||||
'login.requiredMobile': 'Bitte gültige Handynummer eingeben',
|
||||
'login.captchaError': 'Grafischer Bestätigungscode Fehler',
|
||||
'login.forgotPassword': 'Passwort vergessen',
|
||||
'login.userAgreement': 'Nutzungsbedingungen',
|
||||
'login.privacyPolicy': 'Datenschutzrichtlinie',
|
||||
|
||||
// Registrierungsseite
|
||||
'register.pageTitle': 'Registrieren',
|
||||
@@ -67,7 +69,7 @@ export default {
|
||||
'home.createFirstAgent': 'Klicken Sie auf die + Schaltfläche unten rechts, um Ihren ersten Agenten zu erstellen',
|
||||
'home.dialogTitle': 'Agent erstellen',
|
||||
'home.inputPlaceholder': 'z.B. Kundenservice-Assistent, Sprachassistent, Wissens-F&A',
|
||||
'home.createError': 'Bitte Agenten-Namen eingeben',
|
||||
'home.createError': 'Der Name muss zwischen 1 und 64 Zeichen lang sein.',
|
||||
'home.createNow': 'Jetzt erstellen',
|
||||
'home.justNow': 'Gerade eben',
|
||||
'home.minutesAgo': 'Minuten her',
|
||||
@@ -83,7 +85,7 @@ export default {
|
||||
'agent.chatHistory': 'Chat-Verlauf',
|
||||
'agent.voiceprintManagement': 'Stimmabdruckverwaltung',
|
||||
'agent.editTitle': 'Agent bearbeiten',
|
||||
'agent.toolsTitle': 'Funktionen bearbeiten',
|
||||
'agent.toolsTitle': 'Bearbeiten',
|
||||
'agent.voiceActivityDetection': 'Sprachaktivitätserkennung',
|
||||
'agent.speechRecognition': 'Spracherkennung',
|
||||
'agent.largeLanguageModel': 'Großes Sprachmodell',
|
||||
@@ -114,7 +116,7 @@ export default {
|
||||
'agent.tts': 'Text-zu-Sprache',
|
||||
'agent.voiceprint': 'Agenten-Stimme',
|
||||
'agent.plugins': 'Plugins',
|
||||
'agent.editFunctions': 'Funktionen bearbeiten',
|
||||
'agent.editFunctions': 'Bearbeiten',
|
||||
'agent.historyMemory': 'Verlaufsspeicher',
|
||||
'agent.memoryContent': 'Speicherinhalt',
|
||||
'agent.saving': 'Wird gespeichert...',
|
||||
@@ -150,6 +152,25 @@ export default {
|
||||
'contextProviderDialog.cancel': 'Abbrechen',
|
||||
'contextProviderDialog.confirm': 'Bestätigen',
|
||||
|
||||
// Manual add device dialog related
|
||||
'manualAddDeviceDialog.title': 'Manuell Gerät hinzufügen',
|
||||
'manualAddDeviceDialog.deviceType': 'Gerätetyp',
|
||||
'manualAddDeviceDialog.deviceTypePlaceholder': 'Bitte Gerätetyp auswählen',
|
||||
'manualAddDeviceDialog.firmwareVersion': 'Firmware-Version',
|
||||
'manualAddDeviceDialog.firmwareVersionPlaceholder': 'Bitte Firmware-Version eingeben',
|
||||
'manualAddDeviceDialog.macAddress': 'Mac-Adresse',
|
||||
'manualAddDeviceDialog.macAddressPlaceholder': 'Bitte Mac-Adresse eingeben',
|
||||
'manualAddDeviceDialog.confirm': 'Bestätigen',
|
||||
'manualAddDeviceDialog.cancel': 'Abbrechen',
|
||||
'manualAddDeviceDialog.requiredMacAddress': 'Bitte Mac-Adresse eingeben',
|
||||
'manualAddDeviceDialog.invalidMacAddress': 'Bitte korrektes Mac-Adressformat eingeben, z.B.: 00:1A:2B:3C:4D:5E',
|
||||
'manualAddDeviceDialog.requiredDeviceType': 'Bitte Gerätetyp auswählen',
|
||||
'manualAddDeviceDialog.requiredFirmwareVersion': 'Bitte Firmware-Version eingeben',
|
||||
'manualAddDeviceDialog.getFirmwareTypeFailed': 'Firmware-Typ konnte nicht abgerufen werden',
|
||||
'manualAddDeviceDialog.addSuccess': 'Gerät erfolgreich hinzugefügt',
|
||||
'manualAddDeviceDialog.addFailed': 'Hinzufügen fehlgeschlagen',
|
||||
'manualAddDeviceDialog.bindWithCode': 'Mit 6-stelligem Code binden',
|
||||
|
||||
// Chat-Verlauf Seite
|
||||
'chatHistory.getChatSessions': 'Chat-Sitzungsliste abrufen',
|
||||
'chatHistory.noSelectedAgent': 'Kein Agent ausgewählt',
|
||||
@@ -335,7 +356,7 @@ export default {
|
||||
'message.unbindFail': 'Entbinden fehlgeschlagen',
|
||||
'message.networkError': 'Netzwerkfehler, bitte Verbindung prüfen',
|
||||
'message.serverError': 'Serverfehler, bitte später erneut versuchen',
|
||||
'message.invalidAddress': 'Ungültige Adresse, bitte prüfen ob Server gestartet oder Netzwerkverbindung normal ist',
|
||||
'message.invalidAddress': 'Die Adresse kann ungültig sein. Bitte überprüfen Sie, ob der Server gestartet ist oder ob die Netzwerkverbindung funktioniert. Es kann auch sein, dass die Anfrage aufgrund eines Problems mit dem HTTPS-Protokoll nicht gesendet werden kann.',
|
||||
'message.languageChanged': 'Sprache geändert',
|
||||
'message.passwordError': 'Konto oder Passwort Fehler',
|
||||
'message.phoneRegistered': 'Diese Handynummer wurde bereits registriert',
|
||||
@@ -350,7 +371,7 @@ export default {
|
||||
'agent.tools.mcpAccessPoint': 'MCP-Zugangspunkt',
|
||||
'agent.tools.copy': 'Kopieren',
|
||||
'agent.tools.noTools': 'Keine Werkzeuge verfügbar',
|
||||
'agent.tools.parameterConfig': 'Parameterkonfiguration',
|
||||
'agent.tools.parameterConfig': 'Konfig',
|
||||
'agent.tools.noParamsNeeded': 'Keine Parameter benötigt',
|
||||
'agent.tools.pleaseInput': 'Bitte eingeben',
|
||||
'agent.tools.inputOneItemPerLine': 'Ein Element pro Zeile eingeben',
|
||||
@@ -471,4 +492,9 @@ export default {
|
||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'Nach erfolgreicher Konfiguration startet Gerät automatisch neu',
|
||||
'deviceConfig.audioPlaybackError': 'Audio-Wiedergabe-Fehler',
|
||||
'deviceConfig.playbackFailed': 'Wiedergabe fehlgeschlagen',
|
||||
|
||||
// Voiceprint page
|
||||
'voiceprint.audioNotExist': 'Audio existiert nicht',
|
||||
'voiceprint.getAudioFailed': 'Audio konnte nicht abgerufen werden',
|
||||
'voiceprint.audioPlayFailed': 'Audio-Wiedergabe fehlgeschlagen',
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
'login.navigationTitle': 'Login',
|
||||
'login.fetchConfigError': 'Failed to fetch configuration:',
|
||||
'login.selectLanguage': 'Select Language',
|
||||
'login.selectLanguageTip': '中文',
|
||||
'login.selectLanguageTip': 'En',
|
||||
'login.welcomeBack': 'Welcome Back',
|
||||
'login.pleaseLogin': 'Please log in to your account',
|
||||
'login.enterUsername': 'Please enter username',
|
||||
@@ -30,6 +30,8 @@ export default {
|
||||
'login.requiredMobile': 'Please enter a valid phone number',
|
||||
'login.captchaError': 'Graphic verification code error',
|
||||
'login.forgotPassword': 'Forgot Password',
|
||||
'login.userAgreement': 'User Agreement',
|
||||
'login.privacyPolicy': 'Privacy Policy',
|
||||
|
||||
// Register page
|
||||
'register.pageTitle': 'Register',
|
||||
@@ -67,7 +69,7 @@ export default {
|
||||
'home.createFirstAgent': 'Click the + button in the lower right corner to create your first agent',
|
||||
'home.dialogTitle': 'Create Agent',
|
||||
'home.inputPlaceholder': 'e.g. Customer Service Assistant, Voice Assistant, Knowledge Q&A',
|
||||
'home.createError': 'Please input agent name',
|
||||
'home.createError': 'The name length must be between 1 and 64 characters',
|
||||
'home.createNow': 'Create Now',
|
||||
'home.justNow': 'Just now',
|
||||
'home.minutesAgo': 'minutes ago',
|
||||
@@ -150,6 +152,25 @@ export default {
|
||||
'contextProviderDialog.cancel': 'Cancel',
|
||||
'contextProviderDialog.confirm': 'Confirm',
|
||||
|
||||
// Manual add device dialog related
|
||||
'manualAddDeviceDialog.title': 'Manual Add Device',
|
||||
'manualAddDeviceDialog.deviceType': 'Device Type',
|
||||
'manualAddDeviceDialog.deviceTypePlaceholder': 'Please select device type',
|
||||
'manualAddDeviceDialog.firmwareVersion': 'Firmware Version',
|
||||
'manualAddDeviceDialog.firmwareVersionPlaceholder': 'Please enter firmware version',
|
||||
'manualAddDeviceDialog.macAddress': 'Mac Address',
|
||||
'manualAddDeviceDialog.macAddressPlaceholder': 'Please enter Mac address',
|
||||
'manualAddDeviceDialog.confirm': 'Confirm',
|
||||
'manualAddDeviceDialog.cancel': 'Cancel',
|
||||
'manualAddDeviceDialog.requiredMacAddress': 'Please enter Mac address',
|
||||
'manualAddDeviceDialog.invalidMacAddress': 'Please enter correct Mac address format, e.g.: 00:1A:2B:3C:4D:5E',
|
||||
'manualAddDeviceDialog.requiredDeviceType': 'Please select device type',
|
||||
'manualAddDeviceDialog.requiredFirmwareVersion': 'Please enter firmware version',
|
||||
'manualAddDeviceDialog.getFirmwareTypeFailed': 'Failed to get firmware type',
|
||||
'manualAddDeviceDialog.addSuccess': 'Device added successfully',
|
||||
'manualAddDeviceDialog.addFailed': 'Failed to add',
|
||||
'manualAddDeviceDialog.bindWithCode': 'Bind with 6-digit code',
|
||||
|
||||
// Chat History Page
|
||||
'chatHistory.getChatSessions': 'Get chat session list',
|
||||
'chatHistory.noSelectedAgent': 'No agent selected',
|
||||
@@ -335,7 +356,7 @@ export default {
|
||||
'message.unbindFail': 'Unbinding failed',
|
||||
'message.networkError': 'Network error, please check your connection',
|
||||
'message.serverError': 'Server error, please try again later',
|
||||
'message.invalidAddress': 'Invalid address, please check if the server is started or network connection is normal',
|
||||
'message.invalidAddress': 'The address may be invalid. Please check if the server is started or if the network connection is normal; It is also possible that requests cannot be sent due to HTTPS protocol issues',
|
||||
'message.languageChanged': 'Language changed',
|
||||
'message.passwordError': 'Account or password error',
|
||||
'message.phoneRegistered': 'This phone number has been registered',
|
||||
@@ -350,7 +371,7 @@ export default {
|
||||
'agent.tools.mcpAccessPoint': 'MCP Access Point',
|
||||
'agent.tools.copy': 'Copy',
|
||||
'agent.tools.noTools': 'No tools available',
|
||||
'agent.tools.parameterConfig': 'Parameter Configuration',
|
||||
'agent.tools.parameterConfig': 'Param Config',
|
||||
'agent.tools.noParamsNeeded': 'No parameters needed',
|
||||
'agent.tools.pleaseInput': 'Please input',
|
||||
'agent.tools.inputOneItemPerLine': 'Input one item per line',
|
||||
@@ -471,4 +492,9 @@ export default {
|
||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'After successful configuration, device will automatically restart',
|
||||
'deviceConfig.audioPlaybackError': 'Audio playback error',
|
||||
'deviceConfig.playbackFailed': 'Playback failed',
|
||||
|
||||
// Voiceprint page
|
||||
'voiceprint.audioNotExist': 'Audio does not exist',
|
||||
'voiceprint.getAudioFailed': 'Failed to get audio',
|
||||
'voiceprint.audioPlayFailed': 'Audio playback failed',
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
'login.navigationTitle': 'Entrar',
|
||||
'login.fetchConfigError': 'Falha ao buscar configuração:',
|
||||
'login.selectLanguage': 'Selecionar Idioma',
|
||||
'login.selectLanguageTip': '中文',
|
||||
'login.selectLanguageTip': 'Pt',
|
||||
'login.welcomeBack': 'Bem-vindo de Volta',
|
||||
'login.pleaseLogin': 'Por favor, entre na sua conta',
|
||||
'login.enterUsername': 'Por favor, insira o nome de usuário',
|
||||
@@ -29,7 +29,9 @@ export default {
|
||||
'login.requiredCaptcha': 'O código de verificação não pode estar vazio',
|
||||
'login.requiredMobile': 'Por favor, insira um número de telefone válido',
|
||||
'login.captchaError': 'Erro no código de verificação gráfico',
|
||||
'login.forgotPassword': 'Esqueceu a Senha',
|
||||
'login.forgotPassword': 'Esqueceu a Senha',
|
||||
'login.userAgreement': 'Termos de Uso',
|
||||
'login.privacyPolicy': 'Política de Privacidade',
|
||||
|
||||
// Register page
|
||||
'register.pageTitle': 'Cadastro',
|
||||
@@ -67,7 +69,7 @@ export default {
|
||||
'home.createFirstAgent': 'Clique no botão + no canto inferior direito para criar seu primeiro agente',
|
||||
'home.dialogTitle': 'Criar Agente',
|
||||
'home.inputPlaceholder': 'ex: Assistente de Atendimento, Assistente de Voz, Perguntas e Respostas',
|
||||
'home.createError': 'Por favor, insira o nome do agente',
|
||||
'home.createError': 'O comprimento do nome deve estar entre 1 e 64 caracteres',
|
||||
'home.createNow': 'Criar Agora',
|
||||
'home.justNow': 'Agora mesmo',
|
||||
'home.minutesAgo': 'minutos atrás',
|
||||
@@ -150,6 +152,25 @@ export default {
|
||||
'contextProviderDialog.cancel': 'Cancelar',
|
||||
'contextProviderDialog.confirm': 'Confirmar',
|
||||
|
||||
// Diálogo de adição manual de dispositivo
|
||||
'manualAddDeviceDialog.title': 'Adicionar Dispositivo Manualmente',
|
||||
'manualAddDeviceDialog.deviceType': 'Tipo de Dispositivo',
|
||||
'manualAddDeviceDialog.deviceTypePlaceholder': 'Por favor, selecione o tipo de dispositivo',
|
||||
'manualAddDeviceDialog.firmwareVersion': 'Versão do Firmware',
|
||||
'manualAddDeviceDialog.firmwareVersionPlaceholder': 'Por favor, insira a versão do firmware',
|
||||
'manualAddDeviceDialog.macAddress': 'Endereço MAC',
|
||||
'manualAddDeviceDialog.macAddressPlaceholder': 'Por favor, insira o endereço MAC',
|
||||
'manualAddDeviceDialog.confirm': 'Confirmar',
|
||||
'manualAddDeviceDialog.cancel': 'Cancelar',
|
||||
'manualAddDeviceDialog.requiredMacAddress': 'Por favor, insira o endereço MAC',
|
||||
'manualAddDeviceDialog.invalidMacAddress': 'Por favor, insira o formato correto de endereço MAC, ex.: 00:1A:2B:3C:4D:5E',
|
||||
'manualAddDeviceDialog.requiredDeviceType': 'Por favor, selecione o tipo de dispositivo',
|
||||
'manualAddDeviceDialog.requiredFirmwareVersion': 'Por favor, insira a versão do firmware',
|
||||
'manualAddDeviceDialog.getFirmwareTypeFailed': 'Falha ao obter tipo de firmware',
|
||||
'manualAddDeviceDialog.addSuccess': 'Dispositivo adicionado com sucesso',
|
||||
'manualAddDeviceDialog.addFailed': 'Falha ao adicionar',
|
||||
'manualAddDeviceDialog.bindWithCode': 'Vincular com Código de 6 Dígitos',
|
||||
|
||||
// Chat History Page
|
||||
'chatHistory.getChatSessions': 'Obter lista de sessões de conversa',
|
||||
'chatHistory.noSelectedAgent': 'Nenhum agente selecionado',
|
||||
@@ -335,7 +356,7 @@ export default {
|
||||
'message.unbindFail': 'Falha na desvinculação',
|
||||
'message.networkError': 'Erro de rede, por favor verifique sua conexão',
|
||||
'message.serverError': 'Erro no servidor, por favor tente novamente mais tarde',
|
||||
'message.invalidAddress': 'Endereço inválido, por favor verifique se o servidor está iniciado ou se a conexão de rede está normal',
|
||||
'message.invalidAddress': 'O endereço pode ser inválido, verifique se o servidor está iniciado ou se a conexão de rede está correta; Também pode ser impossível enviar solicitações devido a problemas com o protocolo HTTPS',
|
||||
'message.languageChanged': 'Idioma alterado',
|
||||
'message.passwordError': 'Conta ou senha incorretos',
|
||||
'message.phoneRegistered': 'Este número de telefone já está cadastrado',
|
||||
@@ -350,7 +371,7 @@ export default {
|
||||
'agent.tools.mcpAccessPoint': 'Ponto de Acesso MCP',
|
||||
'agent.tools.copy': 'Copiar',
|
||||
'agent.tools.noTools': 'Nenhuma ferramenta disponível',
|
||||
'agent.tools.parameterConfig': 'Configuração de Parâmetros',
|
||||
'agent.tools.parameterConfig': 'Configuração',
|
||||
'agent.tools.noParamsNeeded': 'Nenhum parâmetro necessário',
|
||||
'agent.tools.pleaseInput': 'Por favor, insira',
|
||||
'agent.tools.inputOneItemPerLine': 'Insira um item por linha',
|
||||
@@ -471,4 +492,9 @@ export default {
|
||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'Após a configuração bem-sucedida, o dispositivo reiniciará automaticamente',
|
||||
'deviceConfig.audioPlaybackError': 'Erro na reprodução de áudio',
|
||||
'deviceConfig.playbackFailed': 'Falha na reprodução',
|
||||
|
||||
// Voiceprint page
|
||||
'voiceprint.audioNotExist': 'O áudio não existe',
|
||||
'voiceprint.getAudioFailed': 'Falha ao obter áudio',
|
||||
'voiceprint.audioPlayFailed': 'Falha na reprodução de áudio',
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
'login.navigationTitle': 'Đăng nhập',
|
||||
'login.fetchConfigError': 'Không thể tải cấu hình:',
|
||||
'login.selectLanguage': 'Chọn ngôn ngữ',
|
||||
'login.selectLanguageTip': 'de',
|
||||
'login.selectLanguageTip': 'Vi',
|
||||
'login.welcomeBack': 'Chào mừng trở lại',
|
||||
'login.pleaseLogin': 'Vui lòng đăng nhập vào tài khoản của bạn',
|
||||
'login.enterUsername': 'Vui lòng nhập tên đăng nhập',
|
||||
@@ -30,6 +30,8 @@ export default {
|
||||
'login.requiredMobile': 'Vui lòng nhập số điện thoại hợp lệ',
|
||||
'login.captchaError': 'Lỗi mã xác minh đồ họa',
|
||||
'login.forgotPassword': 'Quên mật khẩu',
|
||||
'login.userAgreement': 'Thỏa thuận người dùng',
|
||||
'login.privacyPolicy': 'Chính sách bảo mật',
|
||||
|
||||
// Trang đăng ký
|
||||
'register.pageTitle': 'Đăng ký',
|
||||
@@ -67,7 +69,7 @@ export default {
|
||||
'home.createFirstAgent': 'Nhấp vào nút + ở góc dưới bên phải để tạo đại lý đầu tiên của bạn',
|
||||
'home.dialogTitle': 'Tạo đại lý',
|
||||
'home.inputPlaceholder': 'ví dụ: Trợ lý chăm sóc khách hàng, Trợ lý giọng nói, Hỏi đáp kiến thức',
|
||||
'home.createError': 'Vui lòng nhập tên đại lý',
|
||||
'home.createError': 'Độ dài tên phải từ 1 đến 64 ký tự',
|
||||
'home.createNow': 'Tạo ngay',
|
||||
'home.justNow': 'Vừa xong',
|
||||
'home.minutesAgo': 'phút trước',
|
||||
@@ -150,6 +152,25 @@ export default {
|
||||
'contextProviderDialog.cancel': 'Hủy bỏ',
|
||||
'contextProviderDialog.confirm': 'Xác nhận',
|
||||
|
||||
// Manual add device dialog related
|
||||
'manualAddDeviceDialog.title': 'Thêm thiết bị thủ công',
|
||||
'manualAddDeviceDialog.deviceType': 'Loại thiết bị',
|
||||
'manualAddDeviceDialog.deviceTypePlaceholder': 'Vui lòng chọn loại thiết bị',
|
||||
'manualAddDeviceDialog.firmwareVersion': 'Phiên bản firmware',
|
||||
'manualAddDeviceDialog.firmwareVersionPlaceholder': 'Vui lòng nhập phiên bản firmware',
|
||||
'manualAddDeviceDialog.macAddress': 'Địa chỉ Mac',
|
||||
'manualAddDeviceDialog.macAddressPlaceholder': 'Vui lòng nhập địa chỉ Mac',
|
||||
'manualAddDeviceDialog.confirm': 'Xác nhận',
|
||||
'manualAddDeviceDialog.cancel': 'Hủy bỏ',
|
||||
'manualAddDeviceDialog.requiredMacAddress': 'Vui lòng nhập địa chỉ Mac',
|
||||
'manualAddDeviceDialog.invalidMacAddress': 'Vui lòng nhập đúng định dạng địa chỉ Mac, ví dụ: 00:1A:2B:3C:4D:5E',
|
||||
'manualAddDeviceDialog.requiredDeviceType': 'Vui lòng chọn loại thiết bị',
|
||||
'manualAddDeviceDialog.requiredFirmwareVersion': 'Vui lòng nhập phiên bản firmware',
|
||||
'manualAddDeviceDialog.getFirmwareTypeFailed': 'Không thể lấy loại firmware',
|
||||
'manualAddDeviceDialog.addSuccess': 'Đã thêm thiết bị thành công',
|
||||
'manualAddDeviceDialog.addFailed': 'Thêm thất bại',
|
||||
'manualAddDeviceDialog.bindWithCode': 'Liên kết bằng mã xác minh 6 chữ số',
|
||||
|
||||
// Trang lịch sử trò chuyện
|
||||
'chatHistory.getChatSessions': 'Lấy danh sách phiên trò chuyện',
|
||||
'chatHistory.noSelectedAgent': 'Chưa chọn đại lý',
|
||||
@@ -335,7 +356,7 @@ export default {
|
||||
'message.unbindFail': 'Hủy liên kết thất bại',
|
||||
'message.networkError': 'Lỗi mạng, vui lòng kiểm tra kết nối',
|
||||
'message.serverError': 'Lỗi máy chủ, vui lòng thử lại sau',
|
||||
'message.invalidAddress': 'Địa chỉ không hợp lệ, vui lòng kiểm tra máy chủ đã khởi động hoặc kết nối mạng bình thường',
|
||||
'message.invalidAddress': 'Địa chỉ có thể không hợp lệ. Vui lòng kiểm tra xem máy chủ đã khởi động hay kết nối mạng chưa; Có thể không gửi được yêu cầu do lỗi giao thức HTTPS',
|
||||
'message.languageChanged': 'Đã thay đổi ngôn ngữ',
|
||||
'message.passwordError': 'Lỗi tài khoản hoặc mật khẩu',
|
||||
'message.phoneRegistered': 'Số điện thoại này đã được đăng ký',
|
||||
@@ -350,7 +371,7 @@ export default {
|
||||
'agent.tools.mcpAccessPoint': 'Điểm truy cập MCP',
|
||||
'agent.tools.copy': 'Sao chép',
|
||||
'agent.tools.noTools': 'Không có công cụ nào',
|
||||
'agent.tools.parameterConfig': 'Cấu hình tham số',
|
||||
'agent.tools.parameterConfig': 'Cấu hình',
|
||||
'agent.tools.noParamsNeeded': 'Không cần tham số',
|
||||
'agent.tools.pleaseInput': 'Vui lòng nhập',
|
||||
'agent.tools.inputOneItemPerLine': 'Nhập một mục mỗi dòng',
|
||||
@@ -471,4 +492,9 @@ export default {
|
||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'Sau khi cấu hình thành công, thiết bị sẽ tự động khởi động lại',
|
||||
'deviceConfig.audioPlaybackError': 'Lỗi phát âm thanh',
|
||||
'deviceConfig.playbackFailed': 'Phát thất bại',
|
||||
|
||||
// Voiceprint page
|
||||
'voiceprint.audioNotExist': 'Âm thanh không tồn tại',
|
||||
'voiceprint.getAudioFailed': 'Không thể lấy âm thanh',
|
||||
'voiceprint.audioPlayFailed': 'Phát âm thanh thất bại',
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
'login.navigationTitle': '登录',
|
||||
'login.fetchConfigError': '获取配置失败:',
|
||||
'login.selectLanguage': '选择语言',
|
||||
'login.selectLanguageTip': 'En',
|
||||
'login.selectLanguageTip': '中文',
|
||||
'login.welcomeBack': '欢迎回来',
|
||||
'login.pleaseLogin': '请登录您的账户',
|
||||
'login.enterUsername': '请输入用户名',
|
||||
@@ -30,6 +30,8 @@ export default {
|
||||
'login.requiredMobile': '请输入正确的手机号码',
|
||||
'login.captchaError': '图形验证码错误',
|
||||
'login.forgotPassword': '忘记密码',
|
||||
'login.userAgreement': '用户协议',
|
||||
'login.privacyPolicy': '隐私政策',
|
||||
|
||||
// 注册页面
|
||||
'register.pageTitle': '注册',
|
||||
@@ -67,7 +69,7 @@ export default {
|
||||
'home.createFirstAgent': '点击右下角 + 号创建您的第一个智能体',
|
||||
'home.dialogTitle': '创建智能体',
|
||||
'home.inputPlaceholder': '例如:客服助手、语音助理、知识问答',
|
||||
'home.createError': '请输入智能体名称',
|
||||
'home.createError': '名称长度必须在 1 到 64 个字符之间',
|
||||
'home.createNow': '立即创建',
|
||||
'home.justNow': '刚刚',
|
||||
'home.minutesAgo': '分钟前',
|
||||
@@ -150,6 +152,25 @@ export default {
|
||||
'contextProviderDialog.cancel': '取消',
|
||||
'contextProviderDialog.confirm': '确定',
|
||||
|
||||
// 手動添加設備對話框相關
|
||||
'manualAddDeviceDialog.title': '手动添加设备',
|
||||
'manualAddDeviceDialog.deviceType': '设备型号',
|
||||
'manualAddDeviceDialog.deviceTypePlaceholder': '请选择设备型号',
|
||||
'manualAddDeviceDialog.firmwareVersion': '固件版本',
|
||||
'manualAddDeviceDialog.firmwareVersionPlaceholder': '请输入固件版本',
|
||||
'manualAddDeviceDialog.macAddress': 'Mac地址',
|
||||
'manualAddDeviceDialog.macAddressPlaceholder': '请输入Mac地址',
|
||||
'manualAddDeviceDialog.confirm': '确定',
|
||||
'manualAddDeviceDialog.cancel': '取消',
|
||||
'manualAddDeviceDialog.requiredMacAddress': '请输入Mac地址',
|
||||
'manualAddDeviceDialog.invalidMacAddress': '请输入正确的Mac地址格式,例如:00:1A:2B:3C:4D:5E',
|
||||
'manualAddDeviceDialog.requiredDeviceType': '请选择设备型号',
|
||||
'manualAddDeviceDialog.requiredFirmwareVersion': '请输入固件版本',
|
||||
'manualAddDeviceDialog.getFirmwareTypeFailed': '获取固件类型失败',
|
||||
'manualAddDeviceDialog.addSuccess': '设备添加成功',
|
||||
'manualAddDeviceDialog.addFailed': '添加失败',
|
||||
'manualAddDeviceDialog.bindWithCode': '6位验证码绑定',
|
||||
|
||||
// 聊天历史页面
|
||||
'chatHistory.getChatSessions': '获取聊天会话列表',
|
||||
'chatHistory.noSelectedAgent': '没有选中的智能体',
|
||||
@@ -335,7 +356,7 @@ export default {
|
||||
'message.unbindFail': '解绑失败',
|
||||
'message.networkError': '网络错误,请检查网络连接',
|
||||
'message.serverError': '服务器错误,请稍后再试',
|
||||
'message.invalidAddress': '无效地址,请检查服务端是否启动或网络连接是否正常',
|
||||
'message.invalidAddress': '地址可能无效,请检查服务端是否启动或网络连接是否正常;也可能因 HTTPS 协议问题导致无法发送请求',
|
||||
'message.languageChanged': '语言已切换',
|
||||
'message.passwordError': '账号或密码错误',
|
||||
'message.phoneRegistered': '此手机号码已经注册过',
|
||||
@@ -471,4 +492,9 @@ export default {
|
||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': '配网成功后设备将自动重启',
|
||||
'deviceConfig.audioPlaybackError': '音频播放错误',
|
||||
'deviceConfig.playbackFailed': '播放失败',
|
||||
|
||||
// Voiceprint page
|
||||
'voiceprint.audioNotExist': '该音频不存在',
|
||||
'voiceprint.getAudioFailed': '获取音频失败',
|
||||
'voiceprint.audioPlayFailed': '音频播放失败',
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
'login.navigationTitle': '登錄',
|
||||
'login.fetchConfigError': '獲取配置失敗:',
|
||||
'login.selectLanguage': '選擇語言',
|
||||
'login.selectLanguageTip': '简体',
|
||||
'login.selectLanguageTip': '繁體',
|
||||
'login.welcomeBack': '歡迎回來',
|
||||
'login.pleaseLogin': '請登錄您的賬戶',
|
||||
'login.enterUsername': '請輸入用戶名',
|
||||
@@ -29,7 +29,9 @@ export default {
|
||||
'login.requiredCaptcha': '驗證碼不能為空',
|
||||
'login.requiredMobile': '請輸入正確的手機號碼',
|
||||
'login.captchaError': '圖形驗證碼錯誤',
|
||||
'login.forgotPassword': '忘記密碼',
|
||||
'login.forgotPassword': '忘記密碼',
|
||||
'login.userAgreement': '用戶協議',
|
||||
'login.privacyPolicy': '隱私政策',
|
||||
|
||||
// 忘記密碼頁面
|
||||
'retrievePassword.title': '重置密碼',
|
||||
@@ -88,7 +90,7 @@ export default {
|
||||
'home.createFirstAgent': '點擊右下角 + 號創建您的第一個智能體',
|
||||
'home.dialogTitle': '創建智能體',
|
||||
'home.inputPlaceholder': '例如:客服助手、語音助理、知識問答',
|
||||
'home.createError': '請輸入智能體暱稱',
|
||||
'home.createError': '暱稱長度必須在 1 到 64 個字元之間。',
|
||||
'home.createNow': '立即創建',
|
||||
'home.justNow': '剛剛',
|
||||
'home.minutesAgo': '分鐘前',
|
||||
@@ -171,6 +173,25 @@ export default {
|
||||
'contextProviderDialog.cancel': '取消',
|
||||
'contextProviderDialog.confirm': '確定',
|
||||
|
||||
// 手動添加設備對話框相關
|
||||
'manualAddDeviceDialog.title': '手動添加設備',
|
||||
'manualAddDeviceDialog.deviceType': '設備型號',
|
||||
'manualAddDeviceDialog.deviceTypePlaceholder': '請選擇設備型號',
|
||||
'manualAddDeviceDialog.firmwareVersion': '固件版本',
|
||||
'manualAddDeviceDialog.firmwareVersionPlaceholder': '請輸入固件版本',
|
||||
'manualAddDeviceDialog.macAddress': 'Mac地址',
|
||||
'manualAddDeviceDialog.macAddressPlaceholder': '請輸入Mac地址',
|
||||
'manualAddDeviceDialog.confirm': '確定',
|
||||
'manualAddDeviceDialog.cancel': '取消',
|
||||
'manualAddDeviceDialog.requiredMacAddress': '請輸入Mac地址',
|
||||
'manualAddDeviceDialog.invalidMacAddress': '請輸入正確的Mac地址格式,例如:00:1A:2B:3C:4D:5E',
|
||||
'manualAddDeviceDialog.requiredDeviceType': '請選擇設備型號',
|
||||
'manualAddDeviceDialog.requiredFirmwareVersion': '請輸入固件版本',
|
||||
'manualAddDeviceDialog.getFirmwareTypeFailed': '獲取固件類型失敗',
|
||||
'manualAddDeviceDialog.addSuccess': '設備添加成功',
|
||||
'manualAddDeviceDialog.addFailed': '添加失敗',
|
||||
'manualAddDeviceDialog.bindWithCode': '6位驗證碼綁定',
|
||||
|
||||
// 聊天歷史頁面
|
||||
'chatHistory.getChatSessions': '獲取聊天會話列表',
|
||||
'chatHistory.noSelectedAgent': '沒有選中的智能體',
|
||||
@@ -335,7 +356,7 @@ export default {
|
||||
'message.unbindFail': '解除綁定失敗',
|
||||
'message.networkError': '網絡錯誤,請檢查網絡連接',
|
||||
'message.serverError': '服務器錯誤,請稍後再試',
|
||||
'message.invalidAddress': '無效地址,請檢查服務端是否啟動或網絡連接是否正常',
|
||||
'message.invalidAddress': '地址可能無效,請檢查服務端是否啟動或網絡連接是否正常; 也可能因HTTPS協定問題導致無法發送請求常',
|
||||
'message.languageChanged': '語言已切換',
|
||||
'message.passwordError': '帳號或密碼錯誤',
|
||||
'message.phoneRegistered': '此手機號已經註冊過',
|
||||
@@ -471,4 +492,9 @@ export default {
|
||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': '配網成功後設備將自動重啟',
|
||||
'deviceConfig.audioPlaybackError': '音頻播放錯誤',
|
||||
'deviceConfig.playbackFailed': '播放失敗',
|
||||
|
||||
// Voiceprint page
|
||||
'voiceprint.audioNotExist': '該音頻不存在',
|
||||
'voiceprint.getAudioFailed': '獲取音頻失敗',
|
||||
'voiceprint.audioPlayFailed': '音頻播放失敗',
|
||||
}
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
import type { AgentDetail, ModelOption, PluginDefinition, RoleTemplate } from '@/api/agent/types'
|
||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { getAgentDetail, getAgentTags, getAllLanguage, getModelOptions, getPluginFunctions, getRoleTemplates, updateAgent, updateAgentTags } from '@/api/agent/agent'
|
||||
import ContextProviderDialog from '@/components/ContextProviderDialog.vue'
|
||||
import VoiceSettingsDialog from '@/components/VoiceSettingsDialog.vue'
|
||||
import { t } from '@/i18n'
|
||||
import { usePluginStore } from '@/store'
|
||||
import { usePluginStore, useProvider, useSpeedPitch } from '@/store'
|
||||
import { toast } from '@/utils/toast'
|
||||
|
||||
defineOptions({
|
||||
@@ -105,29 +103,21 @@ const pickerShow = ref<{
|
||||
report: false,
|
||||
})
|
||||
|
||||
const ttsSettings = ref({
|
||||
volume: 0,
|
||||
speed: 0,
|
||||
pitch: 0,
|
||||
})
|
||||
|
||||
const allFunctions = ref<PluginDefinition[]>([])
|
||||
const dynamicTags = ref([])
|
||||
const inputValue = ref('')
|
||||
const inputVisible = ref(false)
|
||||
const showContextProviderDialog = ref(false)
|
||||
const currentContextProviders = ref([])
|
||||
const showVoiceSettingsDialog = ref(false)
|
||||
const voiceSettings = ref({
|
||||
volume: 0,
|
||||
speed: 0,
|
||||
pitch: 0,
|
||||
})
|
||||
const languageOptions = ref([])
|
||||
const isVisibleReport = ref(false)
|
||||
|
||||
// 音频播放相关
|
||||
const audioRef = ref<UniApp.InnerAudioContext | null>(null)
|
||||
const playingVoiceId = ref<string>('')
|
||||
|
||||
// 使用插件store
|
||||
const pluginStore = usePluginStore()
|
||||
const speedPitchStore = useSpeedPitch()
|
||||
const providerStore = useProvider()
|
||||
|
||||
// tabs
|
||||
const tabList = [
|
||||
@@ -172,29 +162,20 @@ function handleInputConfirm() {
|
||||
inputVisible.value = false
|
||||
}
|
||||
|
||||
// 是否禁用历史记忆输入框
|
||||
const isMemoryDisabled = computed(() => formData.value.memModelId !== 'Memory_mem_local_short')
|
||||
|
||||
// 打开上下文源编辑弹窗
|
||||
function openContextProviderDialog() {
|
||||
showContextProviderDialog.value = true
|
||||
}
|
||||
|
||||
// 处理上下文源更新
|
||||
function handleUpdateContext(providers: any[]) {
|
||||
currentContextProviders.value = providers
|
||||
}
|
||||
|
||||
function openVoiceSettingsDialog() {
|
||||
showVoiceSettingsDialog.value = true
|
||||
}
|
||||
|
||||
function handleUpdateVoiceSettings(settings: any) {
|
||||
ttsSettings.value = settings
|
||||
formData.value.ttsVolume = settings.volume
|
||||
formData.value.ttsRate = settings.speed
|
||||
formData.value.ttsPitch = settings.pitch
|
||||
uni.navigateTo({
|
||||
url: '/pages/agent/provider',
|
||||
})
|
||||
}
|
||||
|
||||
function handleRegulate() {
|
||||
openVoiceSettingsDialog()
|
||||
uni.navigateTo({
|
||||
url: '/pages/agent/speedPitch',
|
||||
})
|
||||
}
|
||||
|
||||
// 加载智能体详情
|
||||
@@ -211,14 +192,15 @@ async function loadAgentDetail() {
|
||||
pluginStore.setCurrentAgentId(agentId.value)
|
||||
pluginStore.setCurrentFunctions(detail.functions || [])
|
||||
|
||||
// 更新语速音调
|
||||
speedPitchStore.updateSpeedPitch({
|
||||
ttsVolume: detail.ttsVolume || 0,
|
||||
ttsRate: detail.ttsRate || 0,
|
||||
ttsPitch: detail.ttsPitch || 0,
|
||||
})
|
||||
|
||||
// 加载上下文配置
|
||||
currentContextProviders.value = (detail as any).contextProviders || []
|
||||
// 加载语音设置
|
||||
voiceSettings.value = {
|
||||
volume: (detail as any).volume || 0,
|
||||
speed: (detail as any).speed || 0,
|
||||
pitch: (detail as any).pitch || 0,
|
||||
}
|
||||
providerStore.updateProviders(detail.contextProviders || [])
|
||||
|
||||
// 如果有TTS模型,加载对应的音色选项
|
||||
if (detail.ttsModelId) {
|
||||
@@ -373,17 +355,17 @@ function filterVoicesByLanguage() {
|
||||
}
|
||||
|
||||
// 同步到ttsSettings(如果值为null,使用0作为显示默认值,但不修改form中的值)
|
||||
ttsSettings.value = {
|
||||
volume: formData.value.ttsVolume !== null && formData.value.ttsVolume !== undefined ? formData.value.ttsVolume : 0,
|
||||
speed: formData.value.ttsRate !== null && formData.value.ttsRate !== undefined ? formData.value.ttsRate : 0,
|
||||
pitch: formData.value.ttsPitch !== null && formData.value.ttsPitch !== undefined ? formData.value.ttsPitch : 0,
|
||||
}
|
||||
speedPitchStore.updateSpeedPitch({
|
||||
ttsVolume: formData.value.ttsVolume !== null && formData.value.ttsVolume !== undefined ? formData.value.ttsVolume : 0,
|
||||
ttsRate: formData.value.ttsRate !== null && formData.value.ttsRate !== undefined ? formData.value.ttsRate : 0,
|
||||
ttsPitch: formData.value.ttsPitch !== null && formData.value.ttsPitch !== undefined ? formData.value.ttsPitch : 0,
|
||||
})
|
||||
}
|
||||
|
||||
// 根据语音合成模型加载语言
|
||||
async function fetchAllLanguag(ttsModelId: string) {
|
||||
try {
|
||||
const res = await getAllLanguage(ttsModelId, '') as any[]
|
||||
const res = await getAllLanguage(ttsModelId)
|
||||
// 保存完整的音色信息
|
||||
voiceDetails.value = res.reduce((acc, voice) => {
|
||||
acc[voice.id] = voice
|
||||
@@ -447,15 +429,24 @@ function selectRoleTemplate(templateId: string) {
|
||||
selectedTemplateId.value = templateId
|
||||
const template = roleTemplates.value.find(t => t.id === templateId)
|
||||
if (template) {
|
||||
formData.value.systemPrompt = template.systemPrompt
|
||||
formData.value.vadModelId = template.vadModelId
|
||||
formData.value.asrModelId = template.asrModelId
|
||||
formData.value.llmModelId = template.llmModelId
|
||||
formData.value.vllmModelId = template.vllmModelId
|
||||
formData.value.intentModelId = template.intentModelId
|
||||
formData.value.memModelId = template.memModelId
|
||||
formData.value.ttsModelId = template.ttsModelId
|
||||
formData.value.ttsVoiceId = template.ttsVoiceId
|
||||
formData.value = {
|
||||
...formData.value,
|
||||
systemPrompt: template.systemPrompt || formData.value.systemPrompt,
|
||||
vadModelId: template.vadModelId || formData.value.vadModelId,
|
||||
asrModelId: template.asrModelId || formData.value.asrModelId,
|
||||
llmModelId: template.llmModelId || formData.value.llmModelId,
|
||||
vllmModelId: template.vllmModelId || formData.value.vllmModelId,
|
||||
intentModelId: template.intentModelId || formData.value.intentModelId,
|
||||
memModelId: template.memModelId || formData.value.memModelId,
|
||||
ttsModelId: template.ttsModelId || formData.value.ttsModelId,
|
||||
ttsVoiceId: template.ttsVoiceId || formData.value.ttsVoiceId,
|
||||
agentName: template.agentName || formData.value.agentName,
|
||||
chatHistoryConf: template.chatHistoryConf || formData.value.chatHistoryConf,
|
||||
summaryMemory: template.summaryMemory || formData.value.summaryMemory,
|
||||
langCode: template.langCode || formData.value.langCode,
|
||||
}
|
||||
fetchAllLanguag(template.ttsModelId || formData.value.ttsModelId)
|
||||
updateDisplayNames()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -518,6 +509,57 @@ async function onPickerConfirm(type: string, value: any, name: string) {
|
||||
// 选择器取消
|
||||
function onPickerCancel(type: string) {
|
||||
pickerShow.value[type] = false
|
||||
// 关闭时停止播放
|
||||
if (type === 'voiceprint') {
|
||||
stopAudio()
|
||||
}
|
||||
}
|
||||
|
||||
// 播放音频
|
||||
function playAudio(voiceDemo: string, voiceId: string, event: Event) {
|
||||
event.stopPropagation() // 阻止事件冒泡,防止关闭下拉框
|
||||
|
||||
if (!voiceDemo) {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果正在播放同一个音频,则停止
|
||||
if (playingVoiceId.value === voiceId) {
|
||||
stopAudio()
|
||||
return
|
||||
}
|
||||
|
||||
// 停止之前的音频
|
||||
stopAudio()
|
||||
|
||||
// 创建新的音频实例
|
||||
audioRef.value = uni.createInnerAudioContext()
|
||||
audioRef.value.src = voiceDemo
|
||||
playingVoiceId.value = voiceId
|
||||
|
||||
// 监听播放结束
|
||||
audioRef.value.onEnded(() => {
|
||||
playingVoiceId.value = ''
|
||||
})
|
||||
|
||||
// 监听播放错误
|
||||
audioRef.value.onError(() => {
|
||||
toast.error('音频播放失败')
|
||||
playingVoiceId.value = ''
|
||||
})
|
||||
|
||||
// 播放音频
|
||||
audioRef.value.play()
|
||||
}
|
||||
|
||||
// 停止音频
|
||||
function stopAudio() {
|
||||
if (audioRef.value) {
|
||||
audioRef.value.stop()
|
||||
audioRef.value.destroy()
|
||||
audioRef.value = null
|
||||
}
|
||||
playingVoiceId.value = ''
|
||||
}
|
||||
|
||||
// 获取模型显示名称
|
||||
@@ -564,8 +606,9 @@ async function saveAgent() {
|
||||
// 构建保存数据,包含上下文配置和语音设置
|
||||
const saveData = {
|
||||
...formData.value,
|
||||
...speedPitchStore.speedPitch,
|
||||
ttsLanguage: formData.value.language,
|
||||
contextProviders: currentContextProviders.value,
|
||||
contextProviders: providerStore.providers,
|
||||
}
|
||||
await updateAgent(agentId.value, saveData)
|
||||
loadAgentDetail()
|
||||
@@ -611,15 +654,6 @@ function handleTools() {
|
||||
})
|
||||
}
|
||||
|
||||
// 监听插件配置更新
|
||||
function watchPluginUpdates() {
|
||||
// 监听store中的插件配置变化
|
||||
watch(() => pluginStore.currentFunctions, (newFunctions) => {
|
||||
console.log('插件配置已更新:', newFunctions)
|
||||
formData.value.functions = newFunctions
|
||||
}, { deep: true })
|
||||
}
|
||||
|
||||
// 获取智能体标签
|
||||
async function loadAgentTags() {
|
||||
try {
|
||||
@@ -635,9 +669,12 @@ async function handleUpdateAgentTags() {
|
||||
await updateAgentTags(agentId.value, { tagNames })
|
||||
}
|
||||
|
||||
// 监听store中的插件配置变化
|
||||
watch(() => pluginStore.currentFunctions, (newFunctions) => {
|
||||
formData.value.functions = newFunctions
|
||||
}, { deep: true })
|
||||
|
||||
onMounted(async () => {
|
||||
// 初始化插件配置监听
|
||||
watchPluginUpdates()
|
||||
loadAgentTags()
|
||||
|
||||
// 先加载模型选项和角色模板
|
||||
@@ -726,7 +763,7 @@ onMounted(async () => {
|
||||
</text>
|
||||
<view class="mt-0 flex flex-wrap items-center gap-[12rpx]">
|
||||
<text class="text-[26rpx] text-[#65686f]">
|
||||
{{ t('agent.contextProviderSuccess', { count: currentContextProviders.length }) }}
|
||||
{{ t('agent.contextProviderSuccess', { count: providerStore.providers.length }) }}
|
||||
</text>
|
||||
<a class="text-[26rpx] text-[#5778ff] no-underline" href="https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/docs/context-provider-integration.md" target="_blank">
|
||||
{{ t('agent.contextProviderDocLink') }}
|
||||
@@ -908,8 +945,9 @@ onMounted(async () => {
|
||||
<textarea
|
||||
v-model="formData.summaryMemory"
|
||||
:placeholder="t('agent.memoryContent')"
|
||||
disabled
|
||||
class="box-border h-[500rpx] w-full resize-none break-words break-all border border-[#eeeeee] rounded-[12rpx] bg-[#f0f0f0] p-[20rpx] text-[26rpx] text-[#65686f] leading-[1.6] opacity-80 outline-none"
|
||||
:disabled="isMemoryDisabled"
|
||||
:style="isMemoryDisabled ? 'background: #f0f0f0' : ''"
|
||||
class="box-border h-[500rpx] w-full resize-none break-words break-all border border-[#eeeeee] rounded-[12rpx] p-[20rpx] text-[26rpx] leading-[1.6] opacity-80 outline-none"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
@@ -972,16 +1010,35 @@ onMounted(async () => {
|
||||
<wd-action-sheet
|
||||
v-model="pickerShow.tts"
|
||||
:actions="modelOptions.TTS && modelOptions.TTS.map(item => ({ name: item.modelName, value: item.id }))"
|
||||
class="custom-sheet-tts"
|
||||
@close="onPickerCancel('tts')"
|
||||
@select="({ item }) => onPickerConfirm('tts', item.value, item.name)"
|
||||
/>
|
||||
|
||||
<wd-action-sheet
|
||||
v-model="pickerShow.voiceprint"
|
||||
:actions="voiceOptions"
|
||||
@close="onPickerCancel('voiceprint')"
|
||||
@select="({ item }) => onPickerConfirm('voiceprint', item.value, item.name)"
|
||||
/>
|
||||
<!-- 自定义语音选择弹出层 -->
|
||||
<wd-popup v-model="pickerShow.voiceprint" class="custom-popup" position="bottom" @close="onPickerCancel('voiceprint')">
|
||||
<view class="overflow-hidden rounded-[20rpx] bg-white pb-[20rpx] pt-[20rpx]">
|
||||
<view class="max-h-[600rpx] overflow-y-auto">
|
||||
<view
|
||||
v-for="voice in voiceOptions"
|
||||
:key="voice.value"
|
||||
class="flex items-center justify-between border-b border-[#f5f5f5] p-[32rpx] transition-all active:bg-[#f5f7fb]"
|
||||
@click="onPickerConfirm('voiceprint', voice.value, voice.name)"
|
||||
>
|
||||
<text :class="`flex-1 text-[28rpx] text-[#232338] ${(voice.voiceDemo || voice.voice_demo) ? '' : 'text-center'}`">
|
||||
{{ voice.name }}
|
||||
</text>
|
||||
<view v-if="voice.voiceDemo || voice.voice_demo" class="ml-[20rpx]" @click.stop="playAudio(voice.voiceDemo || voice.voice_demo, voice.value, $event)">
|
||||
<wd-icon
|
||||
:name="playingVoiceId === voice.value ? 'pause-circle' : 'play-circle'"
|
||||
size="24px"
|
||||
:custom-class="playingVoiceId === voice.value ? 'text-[#336cff]' : 'text-[#9d9ea3]'"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
<wd-action-sheet
|
||||
v-model="pickerShow.language"
|
||||
:actions="languageOptions"
|
||||
@@ -994,16 +1051,6 @@ onMounted(async () => {
|
||||
@close="onPickerCancel('report')"
|
||||
@select="({ item }) => onPickerConfirm('report', item.value, item.name)"
|
||||
/>
|
||||
<ContextProviderDialog
|
||||
v-model:visible="showContextProviderDialog"
|
||||
:providers="currentContextProviders"
|
||||
@confirm="handleUpdateContext"
|
||||
/>
|
||||
<VoiceSettingsDialog
|
||||
v-model:visible="showVoiceSettingsDialog"
|
||||
:settings="ttsSettings"
|
||||
@confirm="handleUpdateVoiceSettings"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -1011,4 +1058,19 @@ onMounted(async () => {
|
||||
::v-deep .wd-tag__close {
|
||||
color: #336cff !important;
|
||||
}
|
||||
::v-deep .custom-popup {
|
||||
.wd-popup {
|
||||
padding: 20rpx !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
::v-deep .custom-sheet-tts {
|
||||
.wd-action-sheet {
|
||||
padding: 8px 0 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
.wd-action-sheet__actions {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -51,17 +51,18 @@ const currentTab = ref('agent-config')
|
||||
|
||||
// 刷新和加载状态
|
||||
const refreshing = ref(false)
|
||||
|
||||
// 计算是否启用下拉刷新(角色编辑页面不启用)
|
||||
const refresherEnabled = computed(() => {
|
||||
return currentTab.value !== 'agent-config'
|
||||
})
|
||||
const refresherEnabled = ref(false)
|
||||
|
||||
// 子组件引用
|
||||
const deviceRef = ref()
|
||||
const chatRef = ref()
|
||||
const voiceprintRef = ref()
|
||||
|
||||
// 更新刷新器状态
|
||||
function updateRefresherEnabled(value: boolean) {
|
||||
refresherEnabled.value = value
|
||||
}
|
||||
|
||||
// Tab 配置
|
||||
const tabList = [
|
||||
{
|
||||
@@ -144,6 +145,10 @@ async function onLoadMore() {
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => currentTab.value, (newTab) => {
|
||||
updateRefresherEnabled(newTab !== 'agent-config')
|
||||
})
|
||||
|
||||
// 接收页面参数
|
||||
onLoad((options) => {
|
||||
if (options?.agentId) {
|
||||
@@ -204,6 +209,7 @@ onMounted(async () => {
|
||||
v-else-if="currentTab === 'voiceprint-management'"
|
||||
ref="voiceprintRef"
|
||||
:agent-id="currentAgentId"
|
||||
@update-refresher-enabled="updateRefresherEnabled"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
<route lang="jsonc" type="page">
|
||||
{
|
||||
"layout": "default",
|
||||
"style": {
|
||||
"navigationBarTitleText": "编辑源",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Providers } from '@/api/agent/types'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { t } from '@/i18n'
|
||||
import { useProvider } from '@/store/provider'
|
||||
|
||||
defineOptions({
|
||||
name: 'Provider',
|
||||
})
|
||||
|
||||
const providerStore = useProvider()
|
||||
|
||||
const localProviders = ref<Providers[]>([])
|
||||
|
||||
function initLocalData() {
|
||||
localProviders.value = providerStore.providers.map((p) => {
|
||||
const headers = p.headers || {}
|
||||
return {
|
||||
url: p.url,
|
||||
headers: Object.entries(headers).map(([key, value]: [string, string]) => ({ key, value })),
|
||||
}
|
||||
})
|
||||
|
||||
if (localProviders.value.length === 0) {
|
||||
localProviders.value.push({ url: '', headers: [{ key: '', value: '' }] })
|
||||
}
|
||||
}
|
||||
|
||||
function addProvider(index: number) {
|
||||
localProviders.value.splice(index, 0, {
|
||||
url: '',
|
||||
headers: [{ key: '', value: '' }],
|
||||
})
|
||||
}
|
||||
|
||||
function removeProvider(index: number) {
|
||||
localProviders.value.splice(index, 1)
|
||||
}
|
||||
|
||||
function addHeader(pIndex: number, hIndex: number) {
|
||||
localProviders.value[pIndex].headers.splice(hIndex, 0, { key: '', value: '' })
|
||||
}
|
||||
|
||||
function removeHeader(pIndex: number, hIndex: number) {
|
||||
localProviders.value[pIndex].headers.splice(hIndex, 1)
|
||||
}
|
||||
|
||||
function handleConfirm() {
|
||||
const result = localProviders.value
|
||||
.filter(p => p.url.trim() !== '')
|
||||
.map((p) => {
|
||||
const headersObj = {}
|
||||
p.headers.forEach((h) => {
|
||||
if (h.key.trim()) {
|
||||
headersObj[h.key.trim()] = h.value
|
||||
}
|
||||
})
|
||||
return {
|
||||
url: p.url.trim(),
|
||||
headers: headersObj,
|
||||
}
|
||||
})
|
||||
|
||||
providerStore.updateProviders(result as Providers[])
|
||||
goBack()
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initLocalData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="h-screen flex flex-col bg-[#f5f7fb]">
|
||||
<!-- 头部导航 -->
|
||||
<wd-navbar
|
||||
:title="t('contextProviderDialog.title')"
|
||||
safe-area-inset-top
|
||||
left-arrow
|
||||
:bordered="false"
|
||||
@click-left="goBack"
|
||||
>
|
||||
<template #left>
|
||||
<wd-icon name="arrow-left" size="18" />
|
||||
</template>
|
||||
</wd-navbar>
|
||||
<view class="flex-1 overflow-y-auto px-[20rpx] pt-[20rpx]">
|
||||
<view v-if="localProviders.length === 0" class="flex flex-col items-center justify-center gap-[30rpx] py-[100rpx]">
|
||||
<text class="text-[28rpx] text-[#9d9ea3]">
|
||||
{{ t('contextProviderDialog.noContextApi') }}
|
||||
</text>
|
||||
<wd-button type="primary" size="small" @click="addProvider(0)">
|
||||
{{ t('contextProviderDialog.add') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
|
||||
<view v-else class="flex flex-col">
|
||||
<view v-for="(provider, pIndex) in localProviders" :key="pIndex" class="mb-[30rpx]">
|
||||
<view class="flex-1 border border-l-[6rpx] border-[#eee] border-l-[#336cff] rounded-[16rpx] bg-[#fff] p-[20rpx] shadow-[0_4rpx_16rpx_rgba(0,0,0,0.05)]">
|
||||
<view class="mb-[30rpx] flex items-center justify-between">
|
||||
<view class="flex gap-[16rpx]">
|
||||
<wd-button
|
||||
type="icon"
|
||||
icon="add"
|
||||
size="small"
|
||||
class="!h-[60rpx] !w-[60rpx] !bg-[#66b1ff] !text-[#fff]"
|
||||
@click="addProvider(pIndex + 1)"
|
||||
/>
|
||||
<wd-button
|
||||
type="icon"
|
||||
icon="decrease"
|
||||
size="small"
|
||||
class="!h-[60rpx] !w-[60rpx] !bg-[#F56C6C] !text-[#fff]"
|
||||
@click="removeProvider(pIndex)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="mb-[40rpx] flex items-center gap-[20rpx]">
|
||||
<text class="w-[140rpx] text-[28rpx] text-[#606266] font-semibold">
|
||||
{{ t('contextProviderDialog.apiUrl') }}
|
||||
</text>
|
||||
<wd-input
|
||||
v-model="provider.url"
|
||||
class="flex-1"
|
||||
:placeholder="t('contextProviderDialog.apiUrlPlaceholder')"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="flex items-start gap-[20rpx]">
|
||||
<view class="mt-[6rpx] w-[140rpx] text-[28rpx] text-[#606266] font-semibold">
|
||||
{{ t('contextProviderDialog.requestHeaders') }}
|
||||
</view>
|
||||
<view class="flex flex-1 flex-col gap-[20rpx] border border-[#dcdfe6] rounded-[12rpx] border-dashed bg-[#fcfcfc] p-[4rpx]">
|
||||
<view
|
||||
v-for="(header, hIndex) in provider.headers"
|
||||
:key="hIndex"
|
||||
class="flex flex-col gap-[16rpx] rounded-[12rpx] bg-[#fff]"
|
||||
>
|
||||
<view class="flex items-center gap-[8rpx]">
|
||||
<wd-input
|
||||
v-model="header.key"
|
||||
placeholder="key"
|
||||
class="w-full"
|
||||
/>
|
||||
</view>
|
||||
<view class="flex items-center gap-[8rpx]">
|
||||
<wd-input
|
||||
v-model="header.value"
|
||||
placeholder="value"
|
||||
class="w-full"
|
||||
/>
|
||||
</view>
|
||||
<view class="flex self-start gap-[12rpx]">
|
||||
<wd-button
|
||||
type="icon"
|
||||
icon="add"
|
||||
size="small"
|
||||
class="!h-[50rpx] !w-[50rpx] !border-[1rpx] !border-solid !bg-[#ecf5ff] !text-[#b3d8ff]"
|
||||
@click="addHeader(pIndex, hIndex + 1)"
|
||||
/>
|
||||
<wd-button
|
||||
type="icon"
|
||||
icon="decrease"
|
||||
size="small"
|
||||
class="!h-[50rpx] !w-[50rpx] !border-[1rpx] !border-solid !bg-[#fef0f0] !text-[#F56C6C]"
|
||||
@click="removeHeader(pIndex, hIndex)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="provider.headers.length === 0" class="flex items-center justify-center py-[20rpx] text-[26rpx] text-[#909399]">
|
||||
<text class="mr-[16rpx]">
|
||||
{{ t('contextProviderDialog.noHeaders') }}
|
||||
</text>
|
||||
<wd-button type="text" size="small" @click="addHeader(pIndex, 0)">
|
||||
{{ t('contextProviderDialog.addHeader') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="flex gap-[20rpx] border-t border-[#eee] px-[30rpx] py-[30rpx]">
|
||||
<wd-button type="primary" class="h-[80rpx] flex-1 rounded-[12rpx] text-[28rpx]" @click="handleConfirm">
|
||||
{{ t('contextProviderDialog.confirm') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
||||
@@ -0,0 +1,153 @@
|
||||
<route lang="jsonc" type="page">
|
||||
{
|
||||
"layout": "default",
|
||||
"style": {
|
||||
"navigationBarTitleText": "语音设置",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/i18n'
|
||||
import { useSpeedPitch } from '@/store'
|
||||
|
||||
defineOptions({
|
||||
name: 'SpeedPitch',
|
||||
})
|
||||
|
||||
const speedPitchStore = useSpeedPitch()
|
||||
|
||||
const localSettings = ref({
|
||||
ttsVolume: 0,
|
||||
ttsRate: 0,
|
||||
ttsPitch: 0,
|
||||
})
|
||||
|
||||
function handleConfirm() {
|
||||
speedPitchStore.updateSpeedPitch(localSettings.value)
|
||||
goBack()
|
||||
}
|
||||
|
||||
// 返回上一页并更新配置
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
localSettings.value = {
|
||||
ttsVolume: speedPitchStore.speedPitch.ttsVolume,
|
||||
ttsRate: speedPitchStore.speedPitch.ttsRate,
|
||||
ttsPitch: speedPitchStore.speedPitch.ttsPitch,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="h-screen flex flex-col bg-[#f5f7fb]">
|
||||
<!-- 头部导航 -->
|
||||
<wd-navbar
|
||||
:title="t('agent.languageConfig')"
|
||||
safe-area-inset-top
|
||||
left-arrow
|
||||
:bordered="false"
|
||||
@click-left="goBack"
|
||||
>
|
||||
<template #left>
|
||||
<wd-icon name="arrow-left" size="18" />
|
||||
</template>
|
||||
</wd-navbar>
|
||||
<view class="flex flex-1 flex-col overflow-hidden">
|
||||
<view class="flex flex-1 flex-col gap-[50rpx] overflow-y-auto px-[40rpx] py-[50rpx]">
|
||||
<!-- 音量调节 -->
|
||||
<view class="flex flex-col gap-[20rpx]">
|
||||
<text class="text-[30rpx] text-[#232338] font-semibold">
|
||||
{{ t('agent.ttsVolume') }}
|
||||
</text>
|
||||
<view class="flex items-center gap-[20rpx]">
|
||||
<wd-slider
|
||||
v-model="localSettings.ttsVolume"
|
||||
:min="-100"
|
||||
:max="100"
|
||||
:step="1"
|
||||
:show-value="false"
|
||||
custom-class="voice-slider"
|
||||
class="flex-1"
|
||||
/>
|
||||
<text class="min-w-[80rpx] text-right text-[28rpx] text-[#336cff] font-medium">
|
||||
{{ localSettings.ttsVolume }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="mt-[10rpx] text-[24rpx] text-[#9d9ea3]">
|
||||
{{ t('agent.volumeHint') }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 语速调节 -->
|
||||
<view class="flex flex-col gap-[20rpx]">
|
||||
<text class="text-[30rpx] text-[#232338] font-semibold">
|
||||
{{ t('agent.ttsRate') }}
|
||||
</text>
|
||||
<view class="flex items-center gap-[20rpx]">
|
||||
<wd-slider
|
||||
v-model="localSettings.ttsRate"
|
||||
:min="-100"
|
||||
:max="100"
|
||||
:step="1"
|
||||
:show-value="false"
|
||||
custom-class="voice-slider"
|
||||
class="flex-1"
|
||||
/>
|
||||
<text class="min-w-[80rpx] text-right text-[28rpx] text-[#336cff] font-medium">
|
||||
{{ localSettings.ttsRate }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="mt-[10rpx] text-[24rpx] text-[#9d9ea3]">
|
||||
{{ t('agent.speedHint') }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 音调调节 -->
|
||||
<view class="flex flex-col gap-[20rpx]">
|
||||
<text class="text-[30rpx] text-[#232338] font-semibold">
|
||||
{{ t('agent.ttsPitch') }}
|
||||
</text>
|
||||
<view class="flex items-center gap-[20rpx]">
|
||||
<wd-slider
|
||||
v-model="localSettings.ttsPitch"
|
||||
:min="-100"
|
||||
:max="100"
|
||||
:step="1"
|
||||
:show-value="false"
|
||||
custom-class="voice-slider"
|
||||
class="flex-1"
|
||||
/>
|
||||
<text class="min-w-[80rpx] text-right text-[28rpx] text-[#336cff] font-medium">
|
||||
{{ localSettings.ttsPitch }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="mt-[10rpx] text-[24rpx] text-[#9d9ea3]">
|
||||
{{ t('agent.pitchHint') }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="flex gap-[20rpx] border-t border-[#eee] px-[30rpx] py-[30rpx]">
|
||||
<wd-button type="primary" class="h-[80rpx] flex-1 rounded-[12rpx] text-[28rpx] !bg-[#336cff]" @click="handleConfirm">
|
||||
{{ t('agent.save') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 自定义滑块样式 */
|
||||
:deep(.wd-slider) {
|
||||
--wd-slider-bar-background: #e6ebff;
|
||||
--wd-slider-bar-active-background: #336cff;
|
||||
--wd-slider-thumb-border-color: #336cff;
|
||||
--wd-slider-thumb-background: #336cff;
|
||||
--wd-slider-thumb-size: 32rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -11,8 +11,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { useMessage } from 'wot-design-uni'
|
||||
import { getMcpAddress, getMcpTools } from '@/api/agent/agent'
|
||||
import { usePluginStore } from '@/store'
|
||||
import { t } from '@/i18n'
|
||||
import { usePluginStore } from '@/store'
|
||||
|
||||
const message = useMessage()
|
||||
const pluginStore = usePluginStore()
|
||||
@@ -30,8 +30,8 @@ const mcpAddress = ref('')
|
||||
const mcpTools = ref<string[]>([])
|
||||
|
||||
// 初始化时从本地存储加载MCP地址
|
||||
if (uni.getStorageSync('cachedMcpAddress_' + agentId.value)) {
|
||||
mcpAddress.value = uni.getStorageSync('cachedMcpAddress_' + agentId.value)
|
||||
if (uni.getStorageSync(`cachedMcpAddress_${agentId.value}`)) {
|
||||
mcpAddress.value = uni.getStorageSync(`cachedMcpAddress_${agentId.value}`)
|
||||
}
|
||||
|
||||
// 参数编辑相关
|
||||
@@ -67,16 +67,19 @@ async function mergeFunctions() {
|
||||
const address = await getMcpAddress(agentId.value)
|
||||
mcpAddress.value = address
|
||||
// 缓存到本地存储,下次打开页面可以立即显示
|
||||
uni.setStorageSync('cachedMcpAddress_' + agentId.value, address)
|
||||
} catch (error) {
|
||||
uni.setStorageSync(`cachedMcpAddress_${agentId.value}`, address)
|
||||
}
|
||||
catch (error) {
|
||||
mcpAddress.value = error
|
||||
console.error('获取MCP地址失败:', error)
|
||||
}
|
||||
|
||||
|
||||
// 异步获取MCP工具列表,不阻塞UI显示
|
||||
try {
|
||||
const tools = await getMcpTools(agentId.value)
|
||||
mcpTools.value = tools || []
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取MCP工具列表失败:', error)
|
||||
}
|
||||
}
|
||||
@@ -85,12 +88,12 @@ async function mergeFunctions() {
|
||||
// 添加插件到已选
|
||||
function selectFunction(func: any) {
|
||||
// 添加到已选列表
|
||||
selectedList.value.push({
|
||||
selectedList.value = [...selectedList.value, {
|
||||
id: func.id,
|
||||
name: func.name,
|
||||
params: { ...func.params },
|
||||
fieldsMeta: func.fieldsMeta,
|
||||
})
|
||||
}]
|
||||
|
||||
// 从未选列表中移除
|
||||
notSelectedList.value = notSelectedList.value.filter(
|
||||
@@ -206,15 +209,6 @@ function closeParamEdit() {
|
||||
|
||||
// 返回上一页并更新配置
|
||||
function goBack() {
|
||||
const finalFunctions = selectedList.value.map(f => ({
|
||||
pluginId: f.id,
|
||||
paramInfo: f.params,
|
||||
}))
|
||||
|
||||
// 更新到store中
|
||||
pluginStore.updateFunctions(finalFunctions)
|
||||
|
||||
// 直接返回
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
@@ -229,11 +223,11 @@ function copyMcpAddress() {
|
||||
data: mcpAddress.value,
|
||||
showToast: false,
|
||||
success: () => {
|
||||
message.alert(t('agent.tools.mcpAddressCopied'))
|
||||
},
|
||||
message.alert(t('agent.tools.mcpAddressCopied'))
|
||||
},
|
||||
fail: () => {
|
||||
message.alert(t('agent.tools.copyFailed'))
|
||||
},
|
||||
message.alert(t('agent.tools.copyFailed'))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -254,6 +248,15 @@ function getFieldRemark(field: any) {
|
||||
return description
|
||||
}
|
||||
|
||||
// 监听已选列表变化,实时更新插件配置,避免用户不点击返回按钮导致配置丢失
|
||||
watch(() => selectedList.value, (newSelectedList) => {
|
||||
const finalFunctions = newSelectedList.map(f => ({
|
||||
pluginId: f.id,
|
||||
paramInfo: f.params,
|
||||
}))
|
||||
pluginStore.updateFunctions(finalFunctions)
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
// 直接从store获取数据并合并
|
||||
await mergeFunctions()
|
||||
@@ -308,7 +311,7 @@ onMounted(async () => {
|
||||
v-if="notSelectedList.length === 0"
|
||||
class="h-[400rpx] flex items-center justify-center"
|
||||
>
|
||||
<wd-status-tip image="content" tip="{{ t('agent.tools.noMorePlugins') }}" />
|
||||
<wd-status-tip image="content" :tip="t('agent.tools.noMorePlugins')" />
|
||||
</view>
|
||||
<view v-else class="p-[20rpx] space-y-[20rpx]">
|
||||
<view
|
||||
@@ -344,7 +347,7 @@ onMounted(async () => {
|
||||
v-if="selectedList.length === 0"
|
||||
class="h-[400rpx] flex items-center justify-center"
|
||||
>
|
||||
<wd-status-tip image="content" tip="{{ t('agent.tools.pleaseSelectPlugin') }}" />
|
||||
<wd-status-tip image="content" :tip="t('agent.tools.pleaseSelectPlugin')" />
|
||||
</view>
|
||||
<view v-else class="p-[20rpx] space-y-[20rpx]">
|
||||
<view
|
||||
@@ -407,11 +410,11 @@ onMounted(async () => {
|
||||
class="flex-1 rounded-[10rpx] bg-[#f5f7fb] p-[20rpx]"
|
||||
>
|
||||
<view
|
||||
class="ml-[20rpx] h-[70rpx] flex items-center justify-center rounded-[10rpx] bg-[#1677ff] px-[20rpx] text-[24rpx] text-white"
|
||||
@click="copyMcpAddress"
|
||||
>
|
||||
{{ t('agent.tools.copy') }}
|
||||
</view>
|
||||
class="ml-[20rpx] h-[70rpx] flex items-center justify-center rounded-[10rpx] bg-[#1677ff] px-[20rpx] text-[24rpx] text-white"
|
||||
@click="copyMcpAddress"
|
||||
>
|
||||
{{ t('agent.tools.copy') }}
|
||||
</view>
|
||||
</view>
|
||||
<!-- 工具列表 -->
|
||||
<view class="mt-[20rpx] flex-1 overflow-hidden">
|
||||
@@ -442,7 +445,7 @@ onMounted(async () => {
|
||||
<!-- 参数编辑弹窗 -->
|
||||
<wd-action-sheet
|
||||
v-model="showParamDialog"
|
||||
:title="`${t('agent.tools.paramConfiguration')} - ${currentFunction?.name || ''}`"
|
||||
:title="`${t('agent.tools.parameterConfig')} - ${currentFunction?.name || ''}`"
|
||||
custom-header-class="h-[75vh]"
|
||||
@close="closeParamEdit"
|
||||
>
|
||||
@@ -583,3 +586,9 @@ onMounted(async () => {
|
||||
</wd-action-sheet>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep .wd-action-sheet__header {
|
||||
padding-right: 30rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,9 +13,9 @@ import type { ChatMessage, UserMessageContent } from '@/api/chat-history/types'
|
||||
import { onLoad, onUnload } from '@dcloudio/uni-app'
|
||||
import { computed, ref } from 'vue'
|
||||
import { getAudioId, getChatHistory } from '@/api/chat-history/chat-history'
|
||||
import { getEnvBaseUrl } from '@/utils'
|
||||
import { toast } from '@/utils/toast'
|
||||
import { t } from '@/i18n'
|
||||
import { debounce, getEnvBaseUrl } from '@/utils'
|
||||
import { toast } from '@/utils/toast'
|
||||
|
||||
defineOptions({
|
||||
name: 'ChatDetail',
|
||||
@@ -129,7 +129,7 @@ function formatTime(timeStr: string) {
|
||||
}
|
||||
|
||||
// 播放音频
|
||||
async function playAudio(audioId: string) {
|
||||
const playAudio = debounce(async (audioId: string) => {
|
||||
if (!audioId) {
|
||||
toast.error(t('chatHistory.invalidAudioId'))
|
||||
return
|
||||
@@ -139,8 +139,11 @@ async function playAudio(audioId: string) {
|
||||
// 如果正在播放其他音频,先停止
|
||||
if (audioContext.value) {
|
||||
audioContext.value.stop()
|
||||
audioContext.value.destroy()
|
||||
audioContext.value = null
|
||||
}
|
||||
// 如果当前音频ID与请求ID相同暂停播放
|
||||
if (playingAudioId.value === audioId) {
|
||||
playingAudioId.value = null
|
||||
return
|
||||
}
|
||||
|
||||
// 获取音频下载ID
|
||||
@@ -151,7 +154,9 @@ async function playAudio(audioId: string) {
|
||||
const audioUrl = `${baseUrl}/agent/play/${downloadId}`
|
||||
|
||||
// 创建音频上下文
|
||||
audioContext.value = uni.createInnerAudioContext()
|
||||
if (!audioContext.value) {
|
||||
audioContext.value = uni.createInnerAudioContext()
|
||||
}
|
||||
audioContext.value.src = audioUrl
|
||||
|
||||
// 设置播放状态
|
||||
@@ -185,7 +190,7 @@ async function playAudio(audioId: string) {
|
||||
toast.error(t('chatHistory.playAudioFailed'))
|
||||
playingAudioId.value = null
|
||||
}
|
||||
}
|
||||
}, 400)
|
||||
|
||||
onLoad((options) => {
|
||||
if (options?.sessionId && options?.agentId) {
|
||||
|
||||
@@ -2,23 +2,28 @@
|
||||
import type { Device, FirmwareType } from '@/api/device'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useMessage } from 'wot-design-uni'
|
||||
import { bindDevice, getBindDevices, getFirmwareTypes, unbindDevice, updateDeviceAutoUpdate } from '@/api/device'
|
||||
import { toast } from '@/utils/toast'
|
||||
import { bindDevice, bindDeviceManual, getBindDevices, getFirmwareTypes, unbindDevice, updateDeviceAutoUpdate } from '@/api/device'
|
||||
import { t } from '@/i18n'
|
||||
import { toast } from '@/utils/toast'
|
||||
|
||||
defineOptions({
|
||||
name: 'DeviceManage',
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
agentId: 'default',
|
||||
})
|
||||
|
||||
const actions = [
|
||||
{ key: 'code', name: t('manualAddDeviceDialog.bindWithCode') },
|
||||
{ key: 'manual', name: t('manualAddDeviceDialog.title') },
|
||||
]
|
||||
|
||||
// 接收props
|
||||
interface Props {
|
||||
agentId?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
agentId: 'default'
|
||||
})
|
||||
|
||||
// 获取屏幕边界到安全区域距离
|
||||
let safeAreaInsets: any
|
||||
let systemInfo: any
|
||||
@@ -44,6 +49,45 @@ safeAreaInsets = systemInfo.safeAreaInsets
|
||||
const deviceList = ref<Device[]>([])
|
||||
const firmwareTypes = ref<FirmwareType[]>([])
|
||||
const loading = ref(false)
|
||||
const isBindDevice = ref(false)
|
||||
|
||||
// 手动绑定弹窗
|
||||
const isManualBindDialog = ref(false)
|
||||
const manualBindForm = ref({
|
||||
board: '',
|
||||
appVersion: '',
|
||||
macAddress: '',
|
||||
})
|
||||
|
||||
// 表单校验错误提示
|
||||
const formErrors = ref({
|
||||
board: '',
|
||||
appVersion: '',
|
||||
macAddress: '',
|
||||
})
|
||||
|
||||
// MAC地址正则校验
|
||||
const macRegex = /^(?:[0-9A-F]{2}[:-]){5}[0-9A-F]{2}$/i
|
||||
|
||||
function selectBindMode(row) {
|
||||
if (row.item.key === 'code') {
|
||||
openBindDialog()
|
||||
}
|
||||
else if (row.item.key === 'manual') {
|
||||
// 打开弹窗前重置表单和错误提示
|
||||
manualBindForm.value = {
|
||||
board: '',
|
||||
appVersion: '',
|
||||
macAddress: '',
|
||||
}
|
||||
formErrors.value = {
|
||||
board: '',
|
||||
appVersion: '',
|
||||
macAddress: '',
|
||||
}
|
||||
isManualBindDialog.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 消息组件
|
||||
const message = useMessage()
|
||||
@@ -56,11 +100,8 @@ const currentAgentId = computed(() => {
|
||||
// 获取设备列表
|
||||
async function loadDeviceList() {
|
||||
try {
|
||||
console.log('获取设备列表')
|
||||
|
||||
// 检查是否有当前选中的智能体
|
||||
if (!currentAgentId.value) {
|
||||
console.warn('没有选中的智能体')
|
||||
deviceList.value = []
|
||||
return
|
||||
}
|
||||
@@ -164,8 +205,8 @@ async function handleBindDevice(code: string) {
|
||||
}
|
||||
catch (error: any) {
|
||||
console.error('绑定设备失败:', error)
|
||||
const errorMessage = error?.message || '绑定失败,请检查验证码是否正确'
|
||||
toast.error(errorMessage || t('device.bindFailed'))
|
||||
const errorMessage = error?.message || t('device.bindFailed')
|
||||
toast.error(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +231,128 @@ function openBindDialog() {
|
||||
})
|
||||
}
|
||||
|
||||
// 手动绑定设备
|
||||
async function handleManualBind() {
|
||||
try {
|
||||
// 先校验整个表单
|
||||
const isValid = validateForm()
|
||||
if (!isValid) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!currentAgentId.value) {
|
||||
toast.error(t('device.pleaseSelectAgent'))
|
||||
return
|
||||
}
|
||||
|
||||
await bindDeviceManual({
|
||||
agentId: currentAgentId.value,
|
||||
board: manualBindForm.value.board,
|
||||
appVersion: manualBindForm.value.appVersion,
|
||||
macAddress: manualBindForm.value.macAddress,
|
||||
})
|
||||
await loadDeviceList()
|
||||
toast.success(t('manualAddDeviceDialog.addSuccess'))
|
||||
isManualBindDialog.value = false
|
||||
// 重置表单和错误提示
|
||||
manualBindForm.value = {
|
||||
board: '',
|
||||
appVersion: '',
|
||||
macAddress: '',
|
||||
}
|
||||
formErrors.value = {
|
||||
board: '',
|
||||
appVersion: '',
|
||||
macAddress: '',
|
||||
}
|
||||
}
|
||||
catch (error: any) {
|
||||
const errorMessage = error?.message || t('manualAddDeviceDialog.addFailed')
|
||||
toast.error(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// 校验单个字段
|
||||
function validateField(field: string) {
|
||||
switch (field) {
|
||||
case 'board':
|
||||
if (!manualBindForm.value.board) {
|
||||
formErrors.value.board = t('manualAddDeviceDialog.deviceTypePlaceholder')
|
||||
}
|
||||
else {
|
||||
formErrors.value.board = ''
|
||||
}
|
||||
break
|
||||
case 'appVersion':
|
||||
if (!manualBindForm.value.appVersion) {
|
||||
formErrors.value.appVersion = t('manualAddDeviceDialog.firmwareVersionPlaceholder')
|
||||
}
|
||||
else {
|
||||
formErrors.value.appVersion = ''
|
||||
}
|
||||
break
|
||||
case 'macAddress':
|
||||
if (!manualBindForm.value.macAddress) {
|
||||
formErrors.value.macAddress = t('manualAddDeviceDialog.macAddressPlaceholder')
|
||||
}
|
||||
else if (!macRegex.test(manualBindForm.value.macAddress)) {
|
||||
formErrors.value.macAddress = t('manualAddDeviceDialog.invalidMacAddress')
|
||||
}
|
||||
else {
|
||||
formErrors.value.macAddress = ''
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 清除字段错误提示
|
||||
function clearFieldError(field: string) {
|
||||
formErrors.value[field] = ''
|
||||
}
|
||||
|
||||
// 处理选择器变化
|
||||
function handlePickerChange() {
|
||||
clearFieldError('board')
|
||||
}
|
||||
|
||||
// 校验整个表单
|
||||
function validateForm(): boolean {
|
||||
let isValid = true
|
||||
|
||||
// 校验设备类型
|
||||
if (!manualBindForm.value.board) {
|
||||
formErrors.value.board = t('manualAddDeviceDialog.deviceTypePlaceholder')
|
||||
isValid = false
|
||||
}
|
||||
else {
|
||||
formErrors.value.board = ''
|
||||
}
|
||||
|
||||
// 校验固件版本
|
||||
if (!manualBindForm.value.appVersion) {
|
||||
formErrors.value.appVersion = t('manualAddDeviceDialog.firmwareVersionPlaceholder')
|
||||
isValid = false
|
||||
}
|
||||
else {
|
||||
formErrors.value.appVersion = ''
|
||||
}
|
||||
|
||||
// 校验MAC地址
|
||||
if (!manualBindForm.value.macAddress) {
|
||||
formErrors.value.macAddress = t('manualAddDeviceDialog.macAddressPlaceholder')
|
||||
isValid = false
|
||||
}
|
||||
else if (!macRegex.test(manualBindForm.value.macAddress)) {
|
||||
formErrors.value.macAddress = t('manualAddDeviceDialog.invalidMacAddress')
|
||||
isValid = false
|
||||
}
|
||||
else {
|
||||
formErrors.value.macAddress = ''
|
||||
}
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
// 获取设备类型列表
|
||||
async function loadFirmwareTypes() {
|
||||
try {
|
||||
@@ -241,14 +404,14 @@ defineExpose({
|
||||
|
||||
<view class="mb-[20rpx]">
|
||||
<text class="mb-[12rpx] block text-[28rpx] text-[#65686f] leading-[1.4]">
|
||||
{{ t('device.macAddress') }}:{{ device.macAddress }}
|
||||
</text>
|
||||
<text class="mb-[12rpx] block text-[28rpx] text-[#65686f] leading-[1.4]">
|
||||
{{ t('device.firmwareVersion') }}:{{ device.appVersion }}
|
||||
</text>
|
||||
<text class="block text-[28rpx] text-[#65686f] leading-[1.4]">
|
||||
{{ t('device.lastConnection') }}:{{ formatTime(device.lastConnectedAt) }}
|
||||
</text>
|
||||
{{ t('device.macAddress') }}:{{ device.macAddress }}
|
||||
</text>
|
||||
<text class="mb-[12rpx] block text-[28rpx] text-[#65686f] leading-[1.4]">
|
||||
{{ t('device.firmwareVersion') }}:{{ device.appVersion }}
|
||||
</text>
|
||||
<text class="block text-[28rpx] text-[#65686f] leading-[1.4]">
|
||||
{{ t('device.lastConnection') }}:{{ formatTime(device.lastConnectedAt) }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="flex items-center justify-between border-[1rpx] border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[16rpx_20rpx]">
|
||||
@@ -295,10 +458,88 @@ defineExpose({
|
||||
</view>
|
||||
|
||||
<!-- FAB 绑定设备按钮 -->
|
||||
<wd-fab type="primary" size="small" icon="add" :draggable="true" :expandable="false" @click="openBindDialog" />
|
||||
<wd-fab type="primary" size="small" icon="add" :draggable="true" :expandable="false" @click="isBindDevice = true" />
|
||||
|
||||
<!-- MessageBox 组件 -->
|
||||
<wd-message-box />
|
||||
<wd-action-sheet v-model="isBindDevice" :actions="actions" @close="isBindDevice = false" @select="selectBindMode" />
|
||||
|
||||
<!-- 手动绑定设备弹窗 -->
|
||||
<wd-popup v-model="isManualBindDialog" position="bottom" :close-on-click-modal="false" custom-style="border-radius: 24rpx 24rpx 0 0;">
|
||||
<view class="manual-bind-dialog">
|
||||
<view class="dialog-header">
|
||||
<text class="dialog-title">
|
||||
{{ t('manualAddDeviceDialog.title') }}
|
||||
</text>
|
||||
<wd-icon name="close" size="20" @click="isManualBindDialog = false" />
|
||||
</view>
|
||||
|
||||
<view class="dialog-content">
|
||||
<view class="form-item">
|
||||
<text class="form-label">
|
||||
{{ t('manualAddDeviceDialog.deviceType') }}
|
||||
<text class="required">
|
||||
*
|
||||
</text>
|
||||
</text>
|
||||
<wd-picker
|
||||
v-model="manualBindForm.board"
|
||||
class="custom-wd-picker"
|
||||
:columns="firmwareTypes.map(item => ({ value: item.key, label: item.name }))"
|
||||
:placeholder="t('manualAddDeviceDialog.deviceTypePlaceholder')"
|
||||
:cancel-button-text="t('common.cancel')"
|
||||
:confirm-button-text="t('common.confirm')"
|
||||
@confirm="handlePickerChange"
|
||||
/>
|
||||
<text v-if="formErrors.board" class="error-text">
|
||||
{{ formErrors.board }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="form-label">
|
||||
{{ t('manualAddDeviceDialog.firmwareVersion') }}
|
||||
<text class="required">
|
||||
*
|
||||
</text>
|
||||
</text>
|
||||
<wd-input
|
||||
v-model="manualBindForm.appVersion"
|
||||
:placeholder="t('manualAddDeviceDialog.firmwareVersionPlaceholder')"
|
||||
@input="clearFieldError('appVersion')"
|
||||
@blur="validateField('appVersion')"
|
||||
/>
|
||||
<text v-if="formErrors.appVersion" class="error-text">
|
||||
{{ formErrors.appVersion }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="form-label">
|
||||
{{ t('manualAddDeviceDialog.macAddress') }}
|
||||
<text class="required">
|
||||
*
|
||||
</text>
|
||||
</text>
|
||||
<wd-input
|
||||
v-model="manualBindForm.macAddress"
|
||||
:placeholder="t('manualAddDeviceDialog.macAddressPlaceholder')"
|
||||
@input="validateField('macAddress')"
|
||||
@blur="validateField('macAddress')"
|
||||
/>
|
||||
<text v-if="formErrors.macAddress" class="error-text">
|
||||
{{ formErrors.macAddress }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="dialog-footer">
|
||||
<wd-button block type="primary" @click="handleManualBind">
|
||||
{{ t('manualAddDeviceDialog.confirm') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -327,8 +568,64 @@ defineExpose({
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
border: 1rpx solid #eeeeee;
|
||||
}
|
||||
::v-deep .wd-action-sheet__popup,
|
||||
::v-deep .wd-popup {
|
||||
z-index: 100 !important;
|
||||
}
|
||||
.custom-wd-picker ::v-deep .wd-picker__cell {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.wd-icon) {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.manual-bind-dialog {
|
||||
padding: 32rpx;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #232338;
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #65686f;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #ff4d4f;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #ff4d4f;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
padding-top: 16rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -125,7 +125,7 @@ function openCreateDialog() {
|
||||
msg: '',
|
||||
inputPlaceholder: t('home.inputPlaceholder'),
|
||||
inputValue: '',
|
||||
inputPattern: /^[\u4E00-\u9FA5a-z0-9\s]{1,50}$/i,
|
||||
inputPattern: /^.{1,64}$/i,
|
||||
inputError: t('home.createError'),
|
||||
confirmButtonText: t('home.createNow'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
@@ -159,7 +159,7 @@ function formatTime(timeStr: string) {
|
||||
onShow(() => {
|
||||
console.log('首页 onShow,刷新智能体列表')
|
||||
if (pagingRef.value) {
|
||||
pagingRef.value.reload()
|
||||
pagingRef.value.refresh()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -9,17 +9,16 @@
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { LoginData } from '@/api/auth';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { login } from '@/api/auth';
|
||||
import { useConfigStore } from '@/store';
|
||||
import { getEnvBaseUrl } from '@/utils';
|
||||
import { toast } from '@/utils/toast';
|
||||
import type { LoginData } from '@/api/auth'
|
||||
import type { Language } from '@/store/lang'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { login } from '@/api/auth'
|
||||
// 导入国际化相关功能
|
||||
import { t, changeLanguage, getSupportedLanguages, initI18n } from '@/i18n';
|
||||
import type { Language } from '@/store/lang';
|
||||
import { changeLanguage, getCurrentLanguage, getSupportedLanguages, initI18n, t } from '@/i18n'
|
||||
import { useConfigStore, useUserStore } from '@/store'
|
||||
// 导入SM2加密工具
|
||||
import { sm2Encrypt } from '@/utils'
|
||||
import { getEnvBaseUrl, sm2Encrypt } from '@/utils'
|
||||
import { toast } from '@/utils/toast'
|
||||
|
||||
// 获取屏幕边界到安全区域距离
|
||||
let safeAreaInsets
|
||||
@@ -62,6 +61,7 @@ const loginType = ref<'username' | 'mobile'>('username')
|
||||
|
||||
// 获取配置store
|
||||
const configStore = useConfigStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 区号选择相关
|
||||
const showAreaCodeSheet = ref(false)
|
||||
@@ -78,11 +78,6 @@ const areaCodeList = computed(() => {
|
||||
return configStore.config.mobileAreaList || [{ name: '中国大陆', key: '+86' }]
|
||||
})
|
||||
|
||||
// SM2公钥
|
||||
const sm2PublicKey = computed(() => {
|
||||
return configStore.config.sm2PublicKey
|
||||
})
|
||||
|
||||
// 切换登录方式
|
||||
function toggleLoginType() {
|
||||
loginType.value = loginType.value === 'username' ? 'mobile' : 'username'
|
||||
@@ -123,6 +118,22 @@ function goToForgotPassword() {
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到用户协议
|
||||
function goToUserAgreement() {
|
||||
const lang = getCurrentLanguage() === 'zh_CN' ? 'zh' : 'en'
|
||||
uni.navigateTo({
|
||||
url: `/pages/login/user-agreement-${lang}`,
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到隐私政策
|
||||
function goToPrivacyPolicy() {
|
||||
const lang = getCurrentLanguage() === 'zh_CN' ? 'zh' : 'en'
|
||||
uni.navigateTo({
|
||||
url: `/pages/login/privacy-policy-${lang}`,
|
||||
})
|
||||
}
|
||||
|
||||
// 生成UUID
|
||||
function generateUUID() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
@@ -132,9 +143,7 @@ function generateUUID() {
|
||||
})
|
||||
}
|
||||
|
||||
let skipReLaunch = false // 全局或组件作用域
|
||||
|
||||
//跳转至服务端设置页面
|
||||
// 跳转至服务端设置页面
|
||||
function goToServerSetting() {
|
||||
uni.switchTab({
|
||||
url: '/pages/settings/index',
|
||||
@@ -143,9 +152,9 @@ function goToServerSetting() {
|
||||
|
||||
// 获取验证码
|
||||
async function refreshCaptcha() {
|
||||
const uuid = generateUUID();
|
||||
formData.value.captchaId = uuid;
|
||||
captchaImage.value = `${getEnvBaseUrl()}/user/captcha?uuid=${uuid}&t=${Date.now()}`;
|
||||
const uuid = generateUUID()
|
||||
formData.value.captchaId = uuid
|
||||
captchaImage.value = `${getEnvBaseUrl()}/user/captcha?uuid=${uuid}&t=${Date.now()}`
|
||||
}
|
||||
|
||||
// 登录
|
||||
@@ -179,7 +188,8 @@ async function handleLogin() {
|
||||
}
|
||||
|
||||
// 检查SM2公钥是否配置
|
||||
if (!sm2PublicKey.value) {
|
||||
const sm2PublicKey = configStore.config.sm2PublicKey
|
||||
if (!sm2PublicKey) {
|
||||
toast.warning(t('sm2.publicKeyNotConfigured'))
|
||||
return
|
||||
}
|
||||
@@ -192,8 +202,9 @@ async function handleLogin() {
|
||||
try {
|
||||
// 拼接验证码和密码
|
||||
const captchaAndPassword = formData.value.captcha + formData.value.password
|
||||
encryptedPassword = sm2Encrypt(sm2PublicKey.value, captchaAndPassword)
|
||||
} catch (error) {
|
||||
encryptedPassword = sm2Encrypt(sm2PublicKey, captchaAndPassword)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('密码加密失败:', error)
|
||||
toast.warning(t('sm2.encryptionFailed'))
|
||||
return
|
||||
@@ -203,20 +214,21 @@ async function handleLogin() {
|
||||
const loginData: LoginData = {
|
||||
username: '',
|
||||
password: encryptedPassword,
|
||||
captchaId: formData.value.captchaId
|
||||
captchaId: formData.value.captchaId,
|
||||
}
|
||||
|
||||
// 如果是手机号登录,将区号+手机号拼接到username字段
|
||||
if (loginType.value === 'mobile') {
|
||||
loginData.username = `${selectedAreaCode.value}${formData.value.mobile}`
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
loginData.username = formData.value.username
|
||||
}
|
||||
|
||||
const response = await login(loginData)
|
||||
// 存储token
|
||||
uni.setStorageSync('token', response.token)
|
||||
uni.setStorageSync('expire', response.expire)
|
||||
uni.setStorageSync('token', JSON.stringify(response))
|
||||
await userStore.getUserInfo()
|
||||
|
||||
toast.success(t('message.loginSuccess'))
|
||||
|
||||
@@ -230,14 +242,6 @@ async function handleLogin() {
|
||||
catch (error: any) {
|
||||
// 登录失败重新获取验证码
|
||||
refreshCaptcha()
|
||||
// 处理验证码错误 - 从error.message中解析错误码
|
||||
if (error.message.includes('请求错误[10067]')) {
|
||||
toast.warning(t('login.captchaError'))
|
||||
}
|
||||
// 处理账号或密码错误
|
||||
else if (error.message.includes('请求错误[10004]')) {
|
||||
toast.warning(t('message.passwordError'))
|
||||
}
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
@@ -267,7 +271,8 @@ onMounted(async () => {
|
||||
if (!configStore.config.name) {
|
||||
try {
|
||||
await configStore.fetchPublicConfig()
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
console.error(t('login.fetchConfigError'), error)
|
||||
}
|
||||
}
|
||||
@@ -287,19 +292,21 @@ onMounted(async () => {
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右上角按钮组 -->
|
||||
<view class="top-right-buttons" :style="{ top: `${safeAreaInsets?.top + 10}px` }">
|
||||
<!-- 语言切换按钮 -->
|
||||
<view class="lang-btn" @click="showLanguageSheet = true">
|
||||
<text class="lang-text-icon">{{ t('login.selectLanguageTip') }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 服务端设置按钮 -->
|
||||
<view class="server-btn" @click="goToServerSetting">
|
||||
<wd-icon name="setting" custom-class="server-icon" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右上角按钮组 -->
|
||||
<view class="top-right-buttons" :style="{ top: `${safeAreaInsets?.top + 10}px` }">
|
||||
<!-- 语言切换按钮 -->
|
||||
<view class="lang-btn" @click="showLanguageSheet = true">
|
||||
<text class="lang-text-icon">
|
||||
{{ t('login.selectLanguageTip') }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 服务端设置按钮 -->
|
||||
<view class="server-btn" @click="goToServerSetting">
|
||||
<wd-icon name="setting" custom-class="server-icon" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-container">
|
||||
<view class="form">
|
||||
@@ -390,6 +397,16 @@ onMounted(async () => {
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="policy-links">
|
||||
<text class="policy-link" @click="goToUserAgreement">
|
||||
{{ t('login.userAgreement') }}
|
||||
</text>
|
||||
<text class="policy-divider">|</text>
|
||||
<text class="policy-link" @click="goToPrivacyPolicy">
|
||||
{{ t('login.privacyPolicy') }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 登录方式切换 -->
|
||||
<view v-if="enableMobileLogin" class="login-type-switch">
|
||||
<view class="switch-tabs">
|
||||
@@ -566,7 +583,6 @@ onMounted(async () => {
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
background: #f8f9fa;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx 16rpx;
|
||||
border: 2rpx solid #e9ecef;
|
||||
@@ -684,6 +700,29 @@ onMounted(async () => {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.policy-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.policy-link {
|
||||
color: #667eea;
|
||||
font-size: 26rpx;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.policy-divider {
|
||||
color: #999999;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
.forgot-text {
|
||||
color: #667eea;
|
||||
@@ -893,7 +932,7 @@ onMounted(async () => {
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.2);
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
@@ -901,7 +940,7 @@ onMounted(async () => {
|
||||
|
||||
.lang-text-icon {
|
||||
font-size: 18rpx;
|
||||
color: #FFFFFF;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -919,7 +958,7 @@ onMounted(async () => {
|
||||
cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.2);
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
@@ -927,7 +966,7 @@ onMounted(async () => {
|
||||
|
||||
.server-icon {
|
||||
font-size: 28rpx;
|
||||
color: #FFFFFF;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
<route lang="jsonc" type="page">
|
||||
{
|
||||
"layout": "default",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "Privacy Policy"
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/i18n'
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="h-screen flex flex-col bg-[#f5f8fd]">
|
||||
<wd-navbar
|
||||
:title="t('login.privacyPolicy')"
|
||||
safe-area-inset-top
|
||||
left-arrow
|
||||
:bordered="false"
|
||||
@click-left="goBack"
|
||||
>
|
||||
<template #left>
|
||||
<wd-icon name="arrow-left" size="18" />
|
||||
</template>
|
||||
</wd-navbar>
|
||||
<scroll-view scroll-y class="box-border flex-1 px-[30rpx] py-[30rpx]">
|
||||
<view class="rounded-[16rpx] bg-white px-[30rpx] py-[40rpx] shadow-[0_4rpx_20rpx_rgba(0,0,0,0.05)]">
|
||||
<view class="mb-[40rpx] text-center">
|
||||
<text class="text-[36rpx] text-[#1a1a1a] font-bold">
|
||||
Privacy Policy
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] text-center">
|
||||
<text class="text-[24rpx] text-[#666666]">
|
||||
Last Updated: March 10, 2026
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
Introduction
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
Welcome to use the XiaoZhi Backend Service (hereinafter referred to as "the Service"). The operator of this Service is the actual deployer and administrator of the Service (hereinafter referred to as "Operator" or "we"). We fully understand the importance of your personal information and will do our best to protect your personal information security.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
Please carefully read and fully understand all contents of this Privacy Policy before using the Service. Once you start using the Service, it signifies that you have read and agreed to this Privacy Policy.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
This Privacy Policy applies to our collection, storage, use, sharing, and protection of your personal information when you use the Service through the Admin Console (management backend), API interfaces, and other means.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
I. Information We Collect
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
To provide services to you, we may need to collect the following information:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.1 Account Registration Information: When you register an account, we collect your phone number or username, password, and other information to create and verify your account.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.2 Device Information: When you bind hardware devices, we collect device identification information (such as device code, MAC address), device model, firmware version, etc., for device management and service provision.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.3 Agent Configuration Information: When you create and configure Agents, we collect role templates, language model selections, voice parameter configurations, etc., to provide personalized AI interaction services.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.4 Voice Interaction Data: When you use the voice interaction function, the Service uses Voice Activity Detection (VAD) to determine the start and end of your voice, processes your voice input data, and transmits it to third-party Automatic Speech Recognition (ASR) and Large Language Model (LLM) services for processing to achieve voice interaction capabilities.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.5 Image Data: When you use the vision model function, we may process image data captured through the device camera and transmit it to third-party vision model services for analysis and understanding to achieve image recognition, scene understanding, and other functions.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.6 Conversation Memory Data: When you enable the memory function, the Service stores summaries of your interaction history with the Agent to provide more coherent and personalized interaction experiences in subsequent conversations.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.7 Knowledge Base Data: When you use the knowledge base function, we collect and store documents, texts, and other knowledge base content you upload for Agents to perform knowledge retrieval and Q&A during conversations.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.8 Voice Print Data: When you use the voice print recognition function, we collect and store your voice print characteristic samples for speaker identity verification and personalized services.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.9 Chat History Data: We store conversation history records between you and the Agents, including text conversation content, conversation time, interaction context, etc., to provide continuous conversation experience and history query functions.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.10 Conversation Audio Data: When you use the voice interaction function, we may store audio data of your interactions with the Agents to improve voice interaction quality and enable retrieval queries.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.11 Agent Configuration Data: When you configure Agents, we collect tags, plugin configurations, context provider settings, etc., to provide Agent customization services.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.12 Device Firmware Data: When you use the OTA upgrade function, we record device firmware version, upgrade history, etc., for device management and firmware traceability.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.13 Log Information: When you use the Service, we automatically collect service log information, including but not limited to access time, IP address, browser type, operation records, etc., for service operation and security assurance.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.14 Verification Code Information: When you use phone number login, we send verification codes via SMS service for identity verification.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
II. How We Use the Collected Information
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
The information we collect will be used for the following purposes:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(1) Providing, maintaining, and improving the Service, including core functions such as account management, device management, and Agent configuration.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(2) Processing your voice interaction requests, using Voice Activity Detection (VAD) to identify voice input, calling third-party AI model services to complete speech recognition, intent recognition, semantic understanding, and speech synthesis.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(3) Processing your image data, calling third-party vision model services to complete image recognition and scene understanding.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(4) Storing and managing your conversation memory data to provide more coherent service experiences in subsequent interactions.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(5) Storing and retrieving knowledge base content you upload so that Agents can provide more accurate knowledge Q&A during conversations.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(6) Ensuring service security and stability, including identity verification, security protection, troubleshooting, etc.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(7) Complying with applicable laws, regulations, and regulatory requirements.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
We will not use your personal information for purposes unrelated to the above. If we need to use information for other purposes not stated in this Privacy Policy, we will obtain your consent in advance.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
III. Sharing and Disclosure of Information
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
We will not sell your personal information to third parties. Only in the following circumstances may we share or disclose your information:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.1 Third-party Service Calls: To achieve voice interaction, visual understanding, and other functions, your voice data, image data, and text content may be transmitted to third-party AI service providers for processing.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.2 Legal Requirements: According to applicable laws, regulations, legal procedures, or requirements from government authorities, we may need to disclose your personal information.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.3 Security Assurance: To protect the Operator, other users, or the public from damage, we may use or disclose personal information within a reasonably necessary scope.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.4 With Consent: We may share your personal information with third parties if we obtain your explicit consent.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
IV. Storage and Protection of Information
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.1 Storage Location: Your personal information will be stored on the server where the Operator deploys the Service, including but not limited to databases (MySQL/PostgreSQL) and cache services (Redis).
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.2 Storage Period: We will retain your personal information for the period necessary to provide you with services. After you cancel your account, we will delete or anonymize your personal information within a reasonable time.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.3 Security Measures: We take reasonable technical and management measures to protect the security of your personal information, including but not limited to data encryption, access control, security auditing, etc.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.4 Security Incident Handling: If a personal information security incident occurs, we will promptly inform you of the basic situation of the security incident, possible impacts, and measures taken or to be taken.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.5 Special Note for Open-source Projects: The Service is an open-source project with two operation modes:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[8rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(1) Self-deployment: If you self-deploy the Service, the Operator is only the code provider, and actual data storage and processing are your responsibility.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[8rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(2) Test Platform: If you use the test platform deployed by the Operator, your data will be managed and protected by the Operator.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.6 Cross-border Data Transfer: When providing intelligent interaction functions, the Service may need to call interfaces from third-party AI service providers outside mainland China.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
V. Your Rights
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.1 Query and Access: You can view and manage your personal information such as account information, device information, and Agent configuration through the Admin Console.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.2 Correction and Modification: When you find that your personal information is incorrect, you can correct it through the Admin Console or contact the Operator for assistance.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.3 Deletion: Under certain circumstances, you may request us to delete your personal information.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.4 Account Cancellation: You can cancel your account through the account settings in the Admin Console or contact the Operator for processing.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.5 Withdrawal of Consent: You may withdraw your consent granted to us at any time.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
VI. Protection of Minor Information
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.1 We attach great importance to the protection of minors' personal information. If you are a minor under 18 years of age, please use the Service under the guidance and consent of your guardian.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.2 If a guardian discovers that a minor has provided personal information to us without consent, please contact the Operator. We will delete the relevant information as soon as possible.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.3 For minors who use the Service with guardian consent, we will provide stricter protection for their personal information in accordance with laws and regulations.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
VII. Third-party Services Description
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
7.1 When providing intelligent interaction functions, the Service needs to call third-party services, including but not limited to: Voice Activity Detection Service (VAD), Automatic Speech Recognition Service (ASR), Large Language Model Service (LLM), Text-to-Speech Service (TTS), Vision Model Service, SMS Verification Code Service, MQTT Message Broker Service, Database Service.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
7.2 The above third-party service providers will process data according to their respective privacy policies. We recommend that you understand the privacy policies of relevant third-party service providers before using the Service.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
VIII. Use of Cookies and Similar Technologies
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.1 The Service may use Cookies and similar technologies to save your login status, record your preference settings, etc., to provide you with a better user experience.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.2 You can manage Cookies through browser settings. However, please note that disabling Cookies may affect some functions of the Service.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
IX. Changes to the Privacy Policy
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
9.1 We may revise this Privacy Policy based on business adjustments, changes in laws and regulations, and other reasons. The revised Privacy Policy will be published through the Service's announcement mechanisms.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
9.2 If you continue to use the Service after the Privacy Policy revision, it shall be deemed that you have accepted the revised Privacy Policy.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
X. Information Security Warning
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
10.1 Please do not disclose sensitive personal information such as your property account numbers, bank card numbers, credit card numbers, passwords, ID card numbers, etc., in voice or text interactions with AI. Any losses resulting from this shall be borne by you.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
10.2 Please properly keep your account and password secure, and do not share your account information with others or share accounts with others.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
10.3 If you discover that personal information may have been leaked, please contact the Operator promptly to take measures.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
XI. Applicable Law
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
11.1 The formulation, interpretation, execution, and dispute resolution of this Privacy Policy shall be governed by the laws of the People's Republic of China (excluding the laws of Hong Kong, Macau, and Taiwan for the purposes of this Privacy Policy).
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
11.2 If any provision of this Privacy Policy conflicts with the current laws and regulations of the People's Republic of China, the provisions of laws and regulations shall prevail, and the remaining provisions shall remain valid.
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -0,0 +1,402 @@
|
||||
<route lang="jsonc" type="page">
|
||||
{
|
||||
"layout": "default",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "隐私政策"
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/i18n'
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="h-screen flex flex-col bg-[#f5f8fd]">
|
||||
<wd-navbar
|
||||
:title="t('login.privacyPolicy')"
|
||||
safe-area-inset-top
|
||||
left-arrow
|
||||
:bordered="false"
|
||||
@click-left="goBack"
|
||||
>
|
||||
<template #left>
|
||||
<wd-icon name="arrow-left" size="18" />
|
||||
</template>
|
||||
</wd-navbar>
|
||||
<scroll-view scroll-y class="box-border flex-1 px-[20rpx] py-[30rpx]" :scroll-with-animation="true">
|
||||
<view class="rounded-[16rpx] bg-white px-[30rpx] py-[40rpx] shadow-[0_4rpx_20rpx_rgba(0,0,0,0.05)]">
|
||||
<view class="mb-[40rpx] text-center">
|
||||
<text class="text-[36rpx] text-[#1a1a1a] font-bold">
|
||||
{{ t('login.privacyPolicy') }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] text-center">
|
||||
<text class="text-[24rpx] text-[#666666]">
|
||||
更新日期:2026年3月10日
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
引言
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
欢迎您使用小智后端服务(以下简称"本服务")。本服务的运营方为本服务的实际部署者和管理者(以下简称"运营者"或"我们")。我们深知个人信息对您的重要性,将尽全力保护您的个人信息安全。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
请您在使用本服务前,仔细阅读并充分理解本隐私政策的全部内容。一旦您开始使用本服务,即表示您已阅读并同意本隐私政策。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
本隐私政策适用于您通过智控台(管理后台)、API接口及其他方式使用本服务时,我们对您个人信息的收集、存储、使用、共享、保护等行为。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
一、我们收集的信息
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
为向您提供服务,我们可能需要收集以下信息:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.1 账号注册信息:当您注册账号时,我们会收集您的手机号码或用户名、密码等信息,用于创建和验证您的账号。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.2 设备信息:当您绑定硬件设备时,我们会收集设备的标识信息(如设备编码、MAC地址等)、设备型号、固件版本等,用于设备管理和服务提供。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.3 智能体配置信息:当您创建和配置智能体时,我们会收集您设定的角色模板、语言模型选择、语音参数配置等信息,用于提供个性化的AI交互服务。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.4 语音交互数据:在您使用语音交互功能时,本服务会通过语音活动检测(VAD)判断您的语音起止状态,并处理您的语音输入数据,将其传输至第三方语音识别(ASR)和语言模型(LLM)服务进行处理,以实现语音交互功能。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.5 图像数据:当您使用视觉模型功能时,我们可能会处理您通过设备摄像头采集的图像数据,并将其传输至第三方视觉模型服务进行分析和理解,用于实现图像识别、场景理解等功能。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.6 对话记忆数据:当您开启记忆功能时,本服务会存储您与智能体的交互历史摘要,用于在后续对话中提供更连贯、个性化的交互体验。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.7 知识库数据:当您使用知识库功能时,我们会收集和存储您上传的文档、文本等知识库内容,用于智能体在对话中进行知识检索和问答。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.8 声纹数据:当您使用声纹识别功能时,我们会收集和存储您的声纹特征样本,用于说话人身份验证和个性化服务。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.9 聊天历史数据:我们会存储您与智能体的对话历史记录,包括文本对话内容、对话时间、交互上下文等信息,用于提供连续性对话体验和历史查询功能。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.10 对话音频数据:在您使用语音交互功能时,我们可能会存储您与智能体交互的音频数据,用于优化语音交互质量和回溯查询。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.11 智能体配置数据:当您配置智能体时,我们会收集您设定的标签、插件配置、上下文提供者设置等信息,用于提供智能体定制化服务。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.12 设备固件数据:当您使用OTA升级功能时,我们会记录设备固件版本、升级历史等信息,用于设备管理和固件追溯。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.13 日志信息:当您使用本服务时,我们会自动收集服务日志信息,包括但不限于访问时间、IP地址、浏览器类型、操作记录等,用于服务运维和安全保障。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
1.14 验证码信息:当您使用手机号登录时,我们会通过短信服务向您发送验证码,用于身份验证。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
二、我们如何使用收集的信息
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
我们收集的信息将用于以下目的:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(1)提供、维护和改进本服务,包括账号管理、设备管理、智能体配置等核心功能。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(2)处理您的语音交互请求,通过语音活动检测(VAD)识别语音输入,调用第三方AI模型服务完成语音识别、意图识别、语义理解和语音合成。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(3)处理您的图像数据,调用第三方视觉模型服务完成图像识别和场景理解。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(4)存储和管理您的对话记忆数据,以便在后续交互中提供更连贯的服务体验。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(5)存储和检索您上传的知识库内容,以便智能体在对话中为您提供更准确的知识问答。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(6)保障服务的安全性和稳定性,包括身份验证、安全防护、故障排查等。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(7)遵守适用的法律法规和监管要求。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
我们不会将您的个人信息用于与上述目的无关的其他用途。如需将信息用于本隐私政策未载明的其他目的,我们会事先征得您的同意。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
三、信息的共享与披露
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
我们不会向第三方出售您的个人信息。仅在以下情形下,我们可能会共享或披露您的信息:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.1 第三方服务调用:为实现语音交互、视觉理解等功能,您的语音数据、图像数据和文本内容可能会被传输至第三方AI服务提供商(如语言模型、语音识别、语音合成、视觉模型服务商)进行处理。我们会选择具备合理安全保障能力的服务商,但请您知悉第三方服务商将按照其自身的隐私政策处理相关数据。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.2 法律要求:根据适用的法律法规、法律程序或政府主管部门的要求,我们可能需要披露您的个人信息。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.3 安全保障:为保护运营者、其他用户或公众的权益、财产或安全免遭损害,在合理必要的范围内使用或披露个人信息。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.4 征得同意:在获得您明确同意的前提下,我们可能会与第三方共享您的个人信息。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
四、信息的存储与保护
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.1 存储地点:您的个人信息将存储在运营者部署本服务的服务器上,包括但不限于数据库(MySQL/PostgreSQL)和缓存服务(Redis)。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.2 存储期限:我们将在为您提供服务所必需的期限内保留您的个人信息。当您注销账号后,我们将在合理时间内删除或匿名化处理您的个人信息,法律法规另有规定的除外。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.3 安全措施:我们采取合理的技术和管理措施保护您的个人信息安全,包括但不限于数据加密、访问控制、安全审计等。但请您理解,互联网并非绝对安全的环境,我们无法保证信息传输和存储的绝对安全性。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.4 安全事件处理:如发生个人信息安全事件,我们将按照法律法规的要求,及时向您告知安全事件的基本情况、可能的影响、已采取或将要采取的处置措施等。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.5 开源项目特别说明:本服务为开源项目,存在两种运营模式:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[8rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(1)自行部署:如您自行部署本服务,运营者仅为代码提供方,实际的数据存储和处理由您自行负责。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[8rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(2)测试平台:如您使用运营者部署的测试平台,您的数据将由运营人员负责管理和保护。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.6 数据跨境传输:本服务在提供智能交互功能时,可能需要调用境外第三方AI服务提供商的接口。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
五、您的权利
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.1 查询与访问:您可通过智控台查看和管理您的账号信息、设备信息、智能体配置等个人信息。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.2 更正与修改:当您发现个人信息有误时,您可通过智控台自行更正,或联系运营者协助处理。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.3 删除:在以下情形下,您可请求我们删除您的个人信息:(1)处理目的已实现、无法实现或不再必要;(2)我们违反法律法规或与您的约定收集、使用个人信息;(3)注销账号。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.4 账号注销:您可通过智控台的账号设置功能注销账号,或联系运营者进行处理。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.5 撤回同意:您可随时撤回您此前向我们作出的同意授权。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
六、未成年人信息保护
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.1 我们高度重视未成年人个人信息的保护。若您是未满18周岁的未成年人,请在监护人的指导和同意下使用本服务。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.2 如果监护人发现未成年人未经其同意向我们提供了个人信息,请联系运营者,我们将尽快删除相关信息。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.3 对于经监护人同意使用本服务的未成年人,我们将按照法律法规的规定,给予其个人信息更严格的保护。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
七、第三方服务说明
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
7.1 本服务在提供智能交互功能时,需要调用第三方服务,包括但不限于:语音活动检测服务(VAD)、语音识别服务(ASR)、大语言模型服务(LLM)、语音合成服务(TTS)、视觉模型服务、短信验证码服务、MQTT消息代理服务、数据库服务。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
7.2 上述第三方服务提供商将按照其各自的隐私政策对数据进行处理。我们建议您在使用本服务前,了解相关第三方服务商的隐私政策。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
八、Cookie及类似技术的使用
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.1 本服务可能使用Cookie及类似技术来保存您的登录状态、记录您的偏好设置等,以便为您提供更好的使用体验。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.2 您可以通过浏览器设置管理Cookie。但请注意,如果禁用Cookie,可能会影响本服务的部分功能。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
九、隐私政策的变更
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
9.1 我们可能会根据业务调整、法律法规变化等原因对本隐私政策进行修订。修订后的隐私政策将通过本平台公告等方式予以发布。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
9.2 如您在隐私政策修订后继续使用本服务,则视为您已接受修订后的隐私政策。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
十、信息安全提示
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
10.1 请勿在与AI的语音或文字交互中透露您的财产账户、银行卡号、信用卡号、密码、身份证号等敏感个人信息,否则由此带来的损失由您自行承担。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
10.2 请妥善保管您的账号和密码,不要将账号信息告知他人或与他人共享账号。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
10.3 如您发现个人信息可能被泄露,请及时联系运营者采取措施。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
|
||||
十一、适用法律
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
11.1 本隐私政策的制定、解释、执行及争议解决均适用中华人民共和国法律法规(为本隐私政策之目的,不包括港澳台地区法律)。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
11.2 如本隐私政策的任何条款与中华人民共和国现行法律法规相抵触,以法律法规的规定为准,其余条款仍然有效。
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -0,0 +1,539 @@
|
||||
<route lang="jsonc" type="page">
|
||||
{
|
||||
"layout": "default",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "User Agreement"
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/i18n'
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="h-screen flex flex-col bg-[#f5f8fd]">
|
||||
<wd-navbar
|
||||
:title="t('login.userAgreement')"
|
||||
safe-area-inset-top
|
||||
left-arrow
|
||||
:bordered="false"
|
||||
@click-left="goBack"
|
||||
>
|
||||
<template #left>
|
||||
<wd-icon name="arrow-left" size="18" />
|
||||
</template>
|
||||
</wd-navbar>
|
||||
<scroll-view scroll-y class="box-border flex-1 px-[30rpx] py-[30rpx]">
|
||||
<view class="rounded-[16rpx] bg-white px-[30rpx] py-[40rpx] shadow-[0_4rpx_20rpx_rgba(0,0,0,0.05)]">
|
||||
<view class="mb-[40rpx] text-center">
|
||||
<text class="text-[36rpx] text-[#1a1a1a] font-bold">
|
||||
User Agreement
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] text-center">
|
||||
<text class="text-[24rpx] text-[#666666]">
|
||||
Last Updated: March 10, 2026
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
Important Notice
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
Welcome to use the XiaoZhi Backend Service (hereinafter referred to as "the Service"). The Service is designed to provide backend service support for XiaoZhi AI hardware devices, including but not limited to intelligent voice interaction, visual understanding, intent recognition, conversation memory, knowledge base Q&A, device management, and agent management. The operator of this Service is the actual deployer and administrator of the Service (hereinafter referred to as "Operator" or "we").
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
Before registering, logging in, or using the Service, please carefully read and fully understand all terms of this Agreement, especially the terms that are highlighted in bold and may have significant impact on your interests, including but not limited to disclaimers and liability limitations.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
When you complete registration, login, or otherwise use the Service following the on-screen instructions, it signifies that you have fully read, understood, and accepted all contents of this Agreement. If you disagree with this Agreement or any of its terms, please stop using the Service immediately.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
This Agreement may be updated and revised based on actual circumstances. The revised Agreement will be published through the Service's announcement mechanisms. The revised Agreement shall take effect from the date of publication. If you continue to use the Service after the Agreement revision, it shall be deemed that you have accepted the revised Agreement.
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
I. Definitions
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
XiaoZhi Backend Service: refers to the XiaoZhi Backend Service and its backend service system, including but not limited to the Admin Console (management backend), API interfaces, WebSocket communication services, etc.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
Admin Console: refers to the web management interface provided by the Service for device management, agent configuration, user management, and other functions.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
User: refers to natural persons, legal persons, or other organizations who register or otherwise use the Service, hereinafter referred to as "you".
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
Intelligent Agent (Agent): refers to an AI character created and configured within the Service, containing a collection of capabilities such as language models, speech recognition, speech synthesis, visual understanding, intent recognition, conversation memory, and knowledge base.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
Device: refers to ESP32 and other hardware devices connected and managed through the Service.
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
II. Scope of Agreement
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
This Agreement is a legal agreement between you and the Operator regarding your use of the Service. This Agreement applies to all your actions when using the Service through the Admin Console or other means.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
The source code of the Service follows the corresponding open-source license. This Agreement governs your use of the Service and does not affect the rights granted to you by the open-source license itself.
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
III. Account Registration and Use
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.1 User Eligibility: You confirm that before using the Service, you should have the capacity for civil conduct as required by the laws of the People's Republic of China. If you are a minor, you should use the Service under the company and guidance of your legal guardian.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.2 Account Registration: You may register and log in to the Service through mobile phone verification code, username/password, or other methods. You shall provide true, accurate, and complete registration information and update it promptly.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.3 Account Security: You shall properly keep your account and password safe. All consequences arising from account leakage or unauthorized use by others due to your reasons shall be borne by you. If you discover any security risks with your account, please contact the Operator immediately.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.4 Account Usage Restrictions: Your account is for your personal use only. Without the Operator's consent, you shall not transfer, rent, or lend your account to any third party in any way.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.5 Account Cancellation: If you need to cancel your account, you may do so through the account settings in the Admin Console or contact the Operator for processing. After account cancellation, related data will be deleted and cannot be recovered. Please proceed with caution.
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
IV. Service Content
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.1 Service Overview: Relying on artificial intelligence technology, the Service provides intelligent interaction services for connected hardware devices through API calls to third-party generative AI models, including but not limited to Voice Activity Detection (VAD), Automatic Speech Recognition (ASR), Large Language Model (LLM), Text-to-Speech (TTS), Vision Large Language Model (VLLM), Intent Recognition, Conversation Memory, and Retrieval-Augmented Generation (RAG).
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.2 Agent Management: You can create and configure Agents through the Admin Console, including setting role templates, selecting language models, configuring voice parameters, setting intent recognition rules, managing knowledge bases, enabling conversation memory, configuring Agent plugins, etc. The Agent settings shall not violate national laws and regulations.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.3 Device Management: You can bind and manage your hardware devices through the Admin Console, perform device configuration, firmware updates, OTA remote upgrades, etc. You shall ensure that the devices you manage are lawfully owned or authorized by you.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.4 Visual Understanding: The Service supports image capture through device cameras and uses third-party vision models for image recognition and scene understanding. You shall ensure that image capture complies with relevant laws and regulations and does not infringe upon others' privacy.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.5 Intent Recognition: The Service can understand the intent of user commands through intent recognition and trigger corresponding operations or services, such as controlling smart devices, querying information, etc.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.6 Conversation Memory: The Service can record and store summaries of your interaction history with the Agent to provide more coherent and personalized interaction experiences in subsequent conversations. You can manage or clear memory data in the Admin Console.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.7 Knowledge Base: The Service supports uploading documents, texts, and other content to build knowledge bases. Agents can retrieve knowledge base content during conversations to provide Q&A services. You shall ensure that uploaded knowledge base content does not infringe upon third-party intellectual property rights and does not contain content that violates laws and regulations.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.8 Voice Print Recognition: The Service supports voice print recognition. You can register voice print samples to achieve speaker identity verification and personalized services. Voice print data will be used for identity verification purposes.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.9 Voice Cloning: The Service supports voice cloning. You can clone custom voice timbres by providing audio samples. Cloned voices are for your personal use only and shall not be used to impersonate others or engage in illegal activities.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.10 MCP Endpoints: the Service supports MCP (Model Context Protocol) and can serve as an MCP Server to provide tool calling capabilities to external systems, or as an MCP Client to call external MCP services, achieving standardized integration with external systems.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.11 Context Providers: The Service supports Context Provider functionality, which can obtain real-time information from external data sources, such as weather, news, stocks, etc., providing Agents with richer knowledge sources.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.12 MQTT Protocol: The Service supports MQTT (Message Queuing Telemetry Transport) protocol for message communication between IoT devices. You can use the MQTT protocol to send device control commands, monitor device status, etc.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.13 Service Disclaimer: The conversations and responses generated by third-party AI models that the Service depends on are for reference only and do not constitute professional advice in any field. You shall not use the output content as professional advice in medical, legal, financial, or other professional fields. Any judgments or decisions you make based on the output content shall be entirely at your own responsibility.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.14 Service Fees: The Service is a free open-source project and does not charge any usage fees from users. It only provides platform capabilities for calling third-party services and does not involve any paid functions or value-added services.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
The Service supports multiple AI service providers with different billing models as follows:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[60rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(1) Free Services: Some service providers offer free quotas or completely free services, such as Zhipu AI (free models like glm-4-flash), Microsoft EdgeTTS voice synthesis, etc.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[60rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(2) Pay-as-you-go: Some service providers charge based on API usage, such as OpenAI, Alibaba Cloud Intelligent Speech, Baidu Wenxin, iFlytek, etc. You need to pay the corresponding fees to third-party service providers yourself. Such fees are unrelated to this Service.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[60rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(3) Local Deployment: The Service supports fully local AI deployment solutions, such as FunASR speech recognition, FishSpeech voice synthesis, Ollama local large models, etc. Local deployment does not require network fees or API usage fees but requires sufficient local computing resources.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.15 Service Changes: The Operator reserves the right to adjust, upgrade, or terminate the Service content as actual circumstances require, and will provide notice as far in advance as possible.
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
V. User Conduct Standards
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.1 Legal Use: When using the Service, you shall comply with the laws and regulations of the People's Republic of China and relevant international treaties, and shall not use the Service for any illegal activities.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.2 Prohibited Conduct: You promise not to engage in the following behaviors when using the Service:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(1) Publishing, transmitting, or storing content that endangers national security or social stability, including but not limited to content involving subverting state power, damaging national honor and interests, or inciting ethnic hatred.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(2) Publishing, transmitting, or storing content that infringes upon the legitimate rights and interests of others, including but not limited to infringing upon intellectual property rights, privacy rights, reputation rights, etc.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(3) Publishing, transmitting, or storing obscene, pornographic, violent, terrorist, or crime-inciting content.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(4) Publishing false information, spreading rumors, or engaging in fraudulent activities.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(5) Interfering with the normal operation of the Service in any way, including but not limited to attacking servers, spreading malicious programs, or malicious consuming system resources.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(6) Unauthorized reverse engineering, decompiling, or attempting to obtain system source code of the Service (this clause does not limit your lawful use of open-source code according to the open-source license).
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(7) Using the Service for automated batch operations, malicious consuming API call quotas or other system resources.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(8) Entering content that violates laws, regulations, or public order and good customs in Agent settings.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(9) Using the Service to harm or attempt to harm minors.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(10) Other behaviors that violate laws, regulations, this Agreement, or may harm the legitimate rights and interests of the Operator or third parties.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.3 Handling of Violations: If you violate the above conduct standards, the Operator has the right to take measures such as warnings, feature restrictions, service suspension, or account banning based on the severity of the situation, and reserves the right to pursue legal liability.
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
VI. Intellectual Property
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.1 Open-source License: The Service is an open-source project, and its source code follows the corresponding open-source license. You may use the relevant source code in compliance with the license terms.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.2 Service Content Ownership: Except for open-source code, the intellectual property rights of interface designs, icons, copy, and other independently created content in the Service belong to the Operator.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.3 User Content: The intellectual property rights of content you input, upload, or generate through the Service shall be determined according to relevant laws and regulations. You grant the Operator the right to store and process your input content for the purpose of maintaining and improving the Service.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.4 Third-party Rights: When using the Service, you should respect the intellectual property rights and other legitimate rights of third parties. You shall bear all responsibilities for any disputes arising from your infringement of third-party rights.
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
VII. Privacy Protection
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
7.1 Information Collection: To provide the Service, the Operator may collect your registration information (such as phone number, username), device information, usage logs, etc. For specific personal information collection and usage rules, please refer to the Privacy Policy.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
7.2 Information Protection: The Operator will take reasonable technical and management measures to protect the security of your personal information. However, due to the openness of the Internet, the Operator cannot absolutely guarantee information security. Please pay attention to protecting your personal sensitive information.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
7.3 Information Security Warning: Please do not disclose your sensitive personal information such as property accounts, bank card numbers, passwords, etc. to AI during your use of the Service. Any losses resulting from this shall be borne by you.
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
VIII. Disclaimer
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.1 AI-Generated Content: The Service relies on third-party AI models to provide intelligent interaction capabilities. The content generated by AI may be inaccurate, incomplete, or inappropriate. The Operator does not make any guarantees regarding the authenticity, accuracy, or completeness of AI-generated content.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.2 Service Interruption: The Operator shall not be responsible for service interruptions or abnormalities caused by the following reasons:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(1) Force majeure factors, such as natural disasters, pandemics, wars, etc.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(2) Public service factors such as power supply failures or communication network failures.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(3) Service failures or policy adjustments of third-party AI model service providers.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(4) Temporary service interruptions caused by system maintenance or upgrades.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(5) Network security incidents such as hacker attacks or computer viruses.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(6) Service adjustments due to laws, regulations, or government controls.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(7) Other situations not caused by the Operator's fault.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.3 Third-party Services: The Service may involve third-party provided language models, speech recognition, speech synthesis, vision models, intent recognition, and other services. You should also comply with the terms of service of third parties when using them. For issues arising from third-party services, please contact the third party directly.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.4 Open-source Statement: The Service is an open-source project provided "as is". To the maximum extent permitted by law, the Operator does not make any express or implied guarantees regarding the merchantability, fitness for a particular purpose, or non-infringement of the Service.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.5 Special Open-source Software Statement: This project is open-source software. This Service has no commercial cooperation relationship with any third-party API service providers it connects to (including but not limited to platforms such as speech recognition, large language models, speech synthesis, etc.) and does not provide any form of guarantee for their service quality and capital security. This software does not host any account keys, does not participate in capital flow, and does not bear the risk of recharge capital losses.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.6 Security Warning: This project functions are not fully developed and have not passed network security assessment. Please do not use it in production environments. If you deploy this project for learning in a public network environment, you must take necessary protective measures, including but not limited to setting strong passwords, restricting access permissions, enabling HTTPS encrypted transmission, etc.
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
IX. Protection of Minors
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
9.1 If you are a minor under 18 years of age, you should use the Service under the guidance and consent of your guardian.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
9.2 Guardians should strengthen supervision of minors using the Service, guide minors to use it reasonably, and avoid excessive reliance.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
9.3 Minors should pay attention to personal information protection when using the Service and avoid uploading or disclosing personal sensitive information.
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
X. Agreement Termination
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
10.1 User Termination: You have the right to stop using the Service and cancel your account at any time.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
10.2 Operator Termination: The Operator has the right to terminate providing services to you under the following circumstances:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(1) You violate the terms of this Agreement.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(2) You use the Service for illegal activities.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(3) As required by laws, regulations, or policies.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(4) The Operator decides to stop operating the Service.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
10.3 Post-termination Handling: After the Agreement is terminated, the Operator is not obligated to retain your account information and related data. The Operator still has the right to pursue your breach of contract liability during the validity period of the Agreement.
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
XI. Governing Law and Dispute Resolution
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
11.1 The formation, effectiveness, interpretation, revision, termination, and dispute resolution of this Agreement are governed by the laws of the People's Republic of China.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
11.2 Any disputes arising from this Agreement or the Service shall be resolved through friendly negotiation between both parties; if negotiation fails, either party has the right to bring a lawsuit to the people's court with jurisdiction at the Operator's location.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
11.3 If any provision of this Agreement is determined to be invalid or unenforceable, it does not affect the validity of the remaining provisions.
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
XII. Miscellaneous
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
12.1 This Agreement constitutes the complete agreement between you and the Operator regarding your use of the Service.
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
12.2 The Operator's failure to exercise or delay in exercising any rights under this Agreement does not constitute a waiver of such rights.
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -0,0 +1,542 @@
|
||||
<route lang="jsonc" type="page">
|
||||
{
|
||||
"layout": "default",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "用户协议"
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { t } from '@/i18n'
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="h-screen flex flex-col bg-[#f5f8fd]">
|
||||
<wd-navbar
|
||||
:title="t('login.userAgreement')"
|
||||
safe-area-inset-top
|
||||
left-arrow
|
||||
:bordered="false"
|
||||
@click-left="goBack"
|
||||
>
|
||||
<template #left>
|
||||
<wd-icon name="arrow-left" size="18" />
|
||||
</template>
|
||||
</wd-navbar>
|
||||
<scroll-view scroll-y class="box-border flex-1 px-[30rpx] py-[30rpx]">
|
||||
<view class="rounded-[16rpx] bg-white px-[30rpx] py-[40rpx] shadow-[0_4rpx_20rpx_rgba(0,0,0,0.05)]">
|
||||
<view class="mb-[40rpx] text-center">
|
||||
<text class="text-[36rpx] text-[#1a1a1a] font-bold">
|
||||
{{ t('login.userAgreement') }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] text-center">
|
||||
<text class="text-[24rpx] text-[#666666]">
|
||||
更新日期:2026年3月10日
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
提示条款
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
欢迎您使用小智后端服务(以下简称"本服务")。本服务旨在为小智AI硬件设备提供后端服务支持,包括但不限于智能语音交互、视觉理解、意图识别、对话记忆、知识库问答、设备管理、智能体管理等功能。本服务的运营方为本服务的实际部署者和管理者(以下简称"运营者"或"我们")。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
在您注册、登录或使用本服务之前,请您务必审慎阅读、充分理解本协议各条款内容,特别是以加粗形式提示您注意的可能与您利益有重大关系的条款,包括但不限于免责声明、责任限制等条款。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
当您按照页面提示完成注册、登录或以其他方式使用本服务时,即表示您已充分阅读、理解并接受本协议的全部内容。如果您不同意本协议或其中任何条款,请立即停止使用本服务。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
本协议可能根据实际情况进行更新和修订,修订后的协议将通过本平台公告等方式予以公示。修订后的协议自公示之日起生效,如您在协议修订后继续使用本服务,则视为您已接受修订后的协议。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
一、定义
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
小智后端服务:指小智后端服务及其部署运行的后端服务系统,包括但不限于智控台(管理后台)、API接口、WebSocket通信服务等。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
智控台:指本服务提供的Web管理界面,用于设备管理、智能体配置、用户管理等功能。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
用户:指通过注册或其他方式使用本服务的自然人、法人或其他组织,以下简称"您"。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
智能体:指在本服务中创建和配置的AI角色,包含语言模型、语音识别、语音合成、视觉理解、意图识别、对话记忆、知识库等能力的集合。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
设备:指通过本服务进行连接和管理的ESP32等硬件设备。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
二、协议范围
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
本协议是您与运营者之间关于使用本服务的法律协议。本协议适用于您通过智控台或其他方式使用本服务的全部行为。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
本服务的源代码遵循相应的开源许可证。本协议约束您对本服务的使用行为,不影响开源许可证本身赋予您的权利。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
三、账号注册与使用
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.1 用户资格:您确认,在使用本服务前,您应当具备中华人民共和国法律规定的与您行为相适应的民事行为能力。若您为未成年人,应在法定监护人的陪同和指导下使用本服务。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.2 账号注册:您可通过手机号验证码或用户名密码等方式注册和登录本服务。您应当提供真实、准确、完整的注册信息,并及时更新。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.3 账号安全:您应妥善保管您的账号和密码,因您的原因导致账号泄露或被他人冒用所产生的一切后果,由您自行承担。如发现账号存在安全隐患,请立即联系运营者。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.4 账号使用限制:您的账号仅限您本人使用,未经运营者同意,不得以任何方式将账号转让、出租或借给第三方使用。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
3.5 账号注销:如您需要注销账号,可通过智控台的账号设置功能进行操作,或联系运营者进行处理。账号注销后,相关数据将被删除且无法恢复,请谨慎操作。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
四、服务内容
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.1 服务概述:本服务依托人工智能技术,通过API接口调用第三方生成式人工智能模型,为连接的硬件设备提供智能交互服务,包括但不限于语音活动检测(VAD)、语音识别(ASR)、语义理解(LLM)、语音合成(TTS)、视觉理解(VLLM)、意图识别(Intent)、对话记忆(Memory)、知识库(RAG)等。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.2 智能体管理:您可通过智控台创建和配置智能体,包括设定角色模板、选择语言模型、配置语音参数、设置意图识别规则、管理知识库、开启对话记忆、配置智能体插件等。智能体的设定内容不得违反国家法律法规。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.3 设备管理:您可通过智控台绑定和管理您的硬件设备,进行设备配置、固件更新、OTA远程升级等操作。您应确保所管理的设备为您合法拥有或已获得授权。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.4 视觉理解:本服务支持通过设备摄像头采集图像,调用第三方视觉模型进行图像识别和场景理解。您应确保采集图像的行为符合相关法律法规,不得侵犯他人隐私。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.5 意图识别:本服务可通过意图识别功能理解用户指令的意图,并据此触发相应的操作或服务,如控制智能设备、查询信息等。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.6 对话记忆:本服务可记录和存储您与智能体的交互历史摘要,以便在后续对话中提供更连贯、个性化的交互体验。您可在智控台管理或清除记忆数据。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.7 知识库:本服务支持您上传文档、文本等内容构建知识库,智能体可在对话中检索知识库内容为您提供问答服务。您应确保上传的知识库内容不侵犯第三方知识产权,且不包含违反法律法规的内容。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.8 声纹识别:本服务支持声纹识别功能,您可注册声纹样本以实现说话人身份验证和个性化服务。声纹数据将用于身份验证目的。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.9 语音克隆:本服务支持语音克隆功能,您可通过提供音频样本克隆自定义音色。克隆音色仅供您本人使用,不得用于仿冒他人或从事违法违规活动。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.10 MCP接入点:本服务支持MCP(Model Context Protocol)协议,可作为MCP Server向外部提供工具调用能力,也可作为MCP Client调用外部MCP服务,实现与外部系统的标准化集成。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.11 上下文提供者:本服务支持上下文提供者(Context Provider)功能,可从外部数据源获取实时信息,如天气、新闻、股票等,为智能体提供更丰富的知识来源。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.12 MQTT协议:本服务支持MQTT(Message Queuing Telemetry Transport)协议,用于与物联网设备之间的消息通信。您可通过MQTT协议实现设备控制指令下发、设备状态监控等功能。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.13 服务说明:本服务所依赖的第三方AI模型生成的对话、答复内容仅供参考,不构成任何专业建议。您不得将输出内容作为医疗、法律、金融等领域的专业建议。您根据输出内容所作的任何判断或决策,由您自行承担全部责任。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.14 服务费用:本服务为免费开源项目,不向用户收取任何使用费用,仅提供调用第三方服务的平台能力,不涉及任何付费功能或增值服务。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
本服务支持多种AI服务提供商,不同提供商的收费模式如下:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[60rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(1)免费服务:部分服务商提供免费额度或完全免费的服务,如智谱AI(glm-4-flash等免费模型)、微软EdgeTTS语音合成等。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[60rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(2)按量付费服务:部分服务商按API调用量计费,如OpenAI、阿里云智能语音、百度文心、讯飞等。您需要自行向第三方服务商支付相应费用,该等费用与本服务无关。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[60rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(3)本地部署方案:本服务支持完全本地部署的AI方案,如FunASR语音识别、FishSpeech语音合成、Ollama本地大模型等。本地部署无需网络费用和API调用费用,但需要本地具备足够的计算资源。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
4.15 服务变更:运营者保留根据实际情况对服务内容进行调整、升级或终止的权利,并将尽可能提前予以通知。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
五、用户行为规范
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.1 合法使用:您在使用本服务时,应遵守中华人民共和国的法律法规及相关国际条约,不得利用本服务从事任何违法违规活动。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.2 禁止行为:您承诺在使用本服务时不得实施以下行为:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(1)发布、传输、存储危害国家安全、社会稳定的内容,包括但不限于涉及颠覆国家政权、损害国家荣誉和利益、煽动民族仇恨等内容。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(2)发布、传输、存储侵犯他人合法权益的内容,包括但不限于侵犯知识产权、隐私权、名誉权等。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(3)发布、传输、存储淫秽、色情、暴力、恐怖或教唆犯罪的内容。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(4)发布虚假信息、散布谣言或从事欺诈行为。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(5)以任何方式干扰本服务的正常运行,包括但不限于攻击服务器、传播恶意程序、恶意消耗系统资源等。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(6)未经授权对本服务进行反向工程、反编译或其他试图获取系统源代码的行为(本条不限制您依据开源许可证对开源代码的合法使用)。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(7)利用本服务进行自动化批量操作,恶意消耗API调用配额或其他系统资源。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(8)在智能体设定中输入违反法律法规或社会公序良俗的内容。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(9)利用本服务伤害或企图伤害未成年人。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(10)其他违反法律法规、本协议约定或可能损害运营者及第三方合法权益的行为。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
5.3 违规处理:如您违反上述行为规范,运营者有权视情节严重程度采取警告、限制功能、暂停服务、封禁账号等措施,并保留追究法律责任的权利。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
六、知识产权
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.1 开源许可:本服务为开源项目,其源代码遵循相应的开源许可证。您可以在遵守该许可证条款的前提下使用相关源代码。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.2 服务内容权属:除开源代码外,本服务中的界面设计、图标、文案及其他运营者独立创作的内容,其知识产权归运营者所有。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.3 用户内容:您通过本服务输入、上传或生成的内容,其知识产权归属依照相关法律法规确定。您授予运营者为维护和改进服务之目的,对您输入内容进行存储和必要处理的权利。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
6.4 第三方权益:您在使用本服务时,应尊重第三方的知识产权及其他合法权益。因您侵犯第三方权益而引发的纠纷,由您自行承担全部责任。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
七、隐私保护
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
7.1 信息收集:为提供本服务,运营者可能会收集您的注册信息(如手机号、用户名)、设备信息、使用日志等。具体的个人信息收集和使用规则请参阅《隐私政策》。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
7.2 信息保护:运营者将采取合理的技术和管理措施保护您的个人信息安全。但由于互联网的开放性,运营者不能绝对保证信息安全,请您注意保护个人敏感信息。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
7.3 信息安全提示:请勿在使用本服务过程中向AI透露您的财产账户、银行卡、密码等敏感个人信息,否则由此带来的损失由您自行承担。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
八、免责声明
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.1 AI生成内容:本服务依赖第三方AI模型提供智能交互能力,AI生成的内容可能存在不准确、不完整或不恰当之处。运营者不对AI生成内容的真实性、准确性、完整性作任何保证。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.2 服务中断:因以下原因导致服务中断或异常,运营者不承担责任:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(1)不可抗力因素,如自然灾害、疫情、战争等。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(2)电力供应故障、通信网络故障等公共服务因素。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(3)第三方AI模型服务商的服务故障或政策调整。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(4)系统维护、升级导致的临时性服务中断。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(5)黑客攻击、计算机病毒等网络安全事件。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(6)法律法规或政府管制导致的服务调整。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(7)其他非运营者过错导致的情形。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.3 第三方服务:本服务可能涉及第三方提供的语言模型、语音识别、语音合成、视觉模型、意图识别等服务。您在使用时应同时遵守第三方的服务条款。因第三方服务引发的问题,请直接与第三方联系。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.4 开源声明:本服务为开源项目,按"现状"提供。在法律允许的最大范围内,运营者不就本服务的适销性、特定用途适用性或不侵权性作任何明示或暗示的保证。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.5 开源软件特别声明:本项目为开源软件,本服务与对接的任何第三方API服务商(包括但不限于语音识别、大模型、语音合成等平台)均不存在商业合作关系,不为其服务质量及资金安全提供任何形式的担保。本软件不托管任何账户密钥、不参与资金流转、不承担充值资金损失风险。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
8.6 安全警告:本项目功能未完善,且未通过网络安全测评,请勿在生产环境中使用。如果您在公网环境中部署学习本项目,请务必做好必要的防护措施,包括但不限于设置强密码、限制访问权限、启用HTTPS加密传输等。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
九、未成年人保护
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
9.1 若您是未满18周岁的未成年人,应在监护人的指导和同意下使用本服务。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
9.2 监护人应加强对未成年人使用本服务的监督,引导未成年人合理使用,避免过度依赖。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
9.3 未成年人在使用本服务时应注意个人信息保护,避免上传或透露个人敏感信息。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
十、协议终止
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
10.1 用户终止:您有权随时停止使用本服务并注销账号。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
10.2 运营者终止:出现以下情形时,运营者有权终止向您提供服务:
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(1)您违反本协议的约定。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(2)您利用本服务从事违法违规活动。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[16rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(3)根据法律法规或政策要求。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[40rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
(4)运营者决定停止运营本服务。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
10.3 终止后处理:协议终止后,运营者无义务保留您的账号信息及相关数据。运营者仍有权依据本协议追究您在协议有效期内的违约责任。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
十一、法律适用与争议解决
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
11.1 本协议的订立、生效、解释、修订、终止及争议解决均适用中华人民共和国法律。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
11.2 因本协议或本服务引发的争议,双方应友好协商解决;协商不成的,任何一方有权向运营者所在地有管辖权的人民法院提起诉讼。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
11.3 本协议的任何条款被认定为无效或不可执行,不影响其余条款的效力。
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] font-semibold">
|
||||
十二、其他
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[24rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
12.1 本协议构成您与运营者之间关于使用本服务的完整协议。
|
||||
</text>
|
||||
</view>
|
||||
<view class="mb-[40rpx] ml-[20rpx]">
|
||||
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
|
||||
12.2 运营者未行使或延迟行使本协议项下的任何权利,不构成对该权利的放弃。
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -1,18 +1,21 @@
|
||||
<route lang="jsonc" type="page">{
|
||||
<route lang="jsonc" type="page">
|
||||
{
|
||||
"layout": "default",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设置",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}</route>
|
||||
}
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { changeLanguage, getCurrentLanguage, getSupportedLanguages, t } from '@/i18n'
|
||||
import type { Language } from '@/store/lang'
|
||||
import { clearServerBaseUrlOverride, getEnvBaseUrl, getServerBaseUrlOverride, setServerBaseUrlOverride } from '@/utils'
|
||||
import { isMp } from '@/utils/platform'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import { changeLanguage, getCurrentLanguage, getSupportedLanguages, t } from '@/i18n'
|
||||
import { useConfigStore } from '@/store'
|
||||
import { clearServerBaseUrlOverride, getEnvBaseUrl, getServerBaseUrlOverride, setServerBaseUrlOverride } from '@/utils'
|
||||
import { isMp } from '@/utils/platform'
|
||||
|
||||
defineOptions({
|
||||
name: 'SettingsPage',
|
||||
@@ -27,6 +30,8 @@ const cacheInfo = reactive({
|
||||
dataCache: '0MB',
|
||||
})
|
||||
|
||||
const configStore = useConfigStore()
|
||||
|
||||
// 服务端地址设置
|
||||
const baseUrlInput = ref('')
|
||||
const urlError = ref('')
|
||||
@@ -81,18 +86,26 @@ async function testServerBaseUrl() {
|
||||
const response = await uni.request({
|
||||
url: `${baseUrlInput.value}/api/ping`,
|
||||
method: 'GET',
|
||||
timeout: 3000
|
||||
timeout: 3000,
|
||||
})
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
return true
|
||||
} else {
|
||||
toast.error(t('message.invalidAddress'))
|
||||
}
|
||||
else {
|
||||
toast.error({
|
||||
msg: t('message.invalidAddress'),
|
||||
duration: 3000,
|
||||
})
|
||||
return false
|
||||
}
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
console.error('测试服务端地址失败:', error)
|
||||
toast.error(t('message.invalidAddress'))
|
||||
toast.error({
|
||||
msg: t('message.invalidAddress'),
|
||||
duration: 3000,
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -110,6 +123,20 @@ async function saveServerBaseUrl() {
|
||||
return
|
||||
}
|
||||
setServerBaseUrlOverride(baseUrlInput.value)
|
||||
// 处理config缓存无法更新的问题
|
||||
uni.request({
|
||||
url: `${getEnvBaseUrl()}/user/pub-config`,
|
||||
method: 'GET',
|
||||
success: (res: any) => {
|
||||
if (res.statusCode === 200) {
|
||||
configStore.setConfig(res.data.data)
|
||||
uni.setStorageSync('config', res.data.data)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('获取SM2公钥失败:', err)
|
||||
},
|
||||
})
|
||||
|
||||
// 切换请求地址后清空所有缓存
|
||||
clearAllCacheAfterUrlChange()
|
||||
@@ -138,6 +165,7 @@ const showLanguageSheet = ref(false)
|
||||
function handleLanguageChange(lang: Language) {
|
||||
changeLanguage(lang)
|
||||
showLanguageSheet.value = false
|
||||
currentLanguage.value = lang
|
||||
toast.success(t('settings.languageChanged'))
|
||||
}
|
||||
|
||||
@@ -198,7 +226,8 @@ function clearAllCacheAfterUrlChange() {
|
||||
|
||||
// 重新获取缓存信息
|
||||
getCacheInfo()
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
console.error('清除缓存失败:', error)
|
||||
}
|
||||
}
|
||||
@@ -223,7 +252,8 @@ async function clearCache() {
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
console.error('清除缓存失败:', error)
|
||||
toast.error(t('settings.clearCacheFailed'))
|
||||
}
|
||||
@@ -235,7 +265,7 @@ function showAbout() {
|
||||
title: t('settings.aboutApp', { appName: import.meta.env.VITE_APP_TITLE }),
|
||||
content: t('settings.aboutContent', {
|
||||
appName: import.meta.env.VITE_APP_TITLE,
|
||||
version: '0.9.1'
|
||||
version: '0.9.2'
|
||||
}),
|
||||
showCancel: false,
|
||||
confirmText: t('common.confirm'),
|
||||
@@ -251,7 +281,7 @@ onMounted(async () => {
|
||||
|
||||
// 动态设置导航栏标题为国际化文本
|
||||
uni.setNavigationBarTitle({
|
||||
title: t('settings.title')
|
||||
title: t('settings.title'),
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@@ -269,8 +299,10 @@ onMounted(async () => {
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx] overflow-hidden"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
|
||||
<view
|
||||
class="overflow-hidden border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);"
|
||||
>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#232338] font-semibold">
|
||||
{{ t('settings.serverApiUrl') }}
|
||||
@@ -281,11 +313,13 @@ onMounted(async () => {
|
||||
</view>
|
||||
|
||||
<view class="mb-[24rpx]">
|
||||
<view class="w-full rounded-[16rpx] border border-[#eeeeee] bg-[#f5f7fb] overflow-hidden">
|
||||
<wd-input v-model="baseUrlInput" type="text" clearable :maxlength="200"
|
||||
<view class="w-full overflow-hidden border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb]">
|
||||
<wd-input
|
||||
v-model="baseUrlInput" type="text" clearable :maxlength="200"
|
||||
:placeholder="t('settings.enterServerUrl')"
|
||||
custom-class="!border-none !bg-transparent h-[64rpx] px-[24rpx] items-center"
|
||||
input-class="text-[28rpx] text-[#232338]" @input="validateUrl" @blur="validateUrl" />
|
||||
input-class="text-[28rpx] text-[#232338]" @input="validateUrl" @blur="validateUrl"
|
||||
/>
|
||||
</view>
|
||||
<text v-if="urlError" class="mt-[8rpx] block text-[24rpx] text-[#ff4d4f]">
|
||||
{{ urlError }}
|
||||
@@ -293,14 +327,18 @@ onMounted(async () => {
|
||||
</view>
|
||||
|
||||
<view class="flex gap-[16rpx]">
|
||||
<wd-button type="primary"
|
||||
<wd-button
|
||||
type="primary"
|
||||
custom-class="flex-1 h-[88rpx] rounded-[20rpx] text-[28rpx] font-semibold bg-[#336cff] border-none shadow-[0_4rpx_16rpx_rgba(51,108,255,0.3)] active:shadow-[0_2rpx_8rpx_rgba(51,108,255,0.4)] active:scale-98"
|
||||
@click="saveServerBaseUrl">
|
||||
@click="saveServerBaseUrl"
|
||||
>
|
||||
{{ t('settings.saveSettings') }}
|
||||
</wd-button>
|
||||
<wd-button type="default"
|
||||
<wd-button
|
||||
type="default"
|
||||
custom-class="flex-1 h-[88rpx] rounded-[20rpx] text-[28rpx] font-semibold bg-white border-[#eeeeee] text-[#65686f] active:bg-[#f5f7fb]"
|
||||
@click="resetServerBaseUrl">
|
||||
@click="resetServerBaseUrl"
|
||||
>
|
||||
{{ t('settings.resetDefault') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
@@ -315,12 +353,15 @@ onMounted(async () => {
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
|
||||
<view
|
||||
class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);"
|
||||
>
|
||||
<view class="space-y-[16rpx]">
|
||||
<!-- 缓存信息展示,参考插件样式 -->
|
||||
<view
|
||||
class="flex items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]">
|
||||
class="flex items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]"
|
||||
>
|
||||
<view>
|
||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||
{{ t('settings.totalCacheSize') }}
|
||||
@@ -336,7 +377,8 @@ onMounted(async () => {
|
||||
|
||||
<!-- 清除缓存按钮,参考插件编辑按钮样式 -->
|
||||
<view
|
||||
class="flex items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx]">
|
||||
class="flex items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx]"
|
||||
>
|
||||
<view>
|
||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||
{{ t('settings.cacheClear') }}
|
||||
@@ -347,7 +389,8 @@ onMounted(async () => {
|
||||
</view>
|
||||
<view
|
||||
class="cursor-pointer rounded-[24rpx] bg-[rgba(255,107,107,0.1)] px-[28rpx] py-[16rpx] text-[24rpx] text-[#ff6b6b] font-semibold transition-all duration-300 active:scale-95 active:bg-[#ff6b6b] active:text-white"
|
||||
@click="clearCache">
|
||||
@click="clearCache"
|
||||
>
|
||||
{{ t('settings.clearCache') }}
|
||||
</view>
|
||||
</view>
|
||||
@@ -363,11 +406,14 @@ onMounted(async () => {
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
|
||||
<view
|
||||
class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);"
|
||||
>
|
||||
<view
|
||||
class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]"
|
||||
@click="showAbout">
|
||||
@click="showAbout"
|
||||
>
|
||||
<view>
|
||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||
{{ t('settings.aboutUs') }}
|
||||
@@ -389,11 +435,14 @@ onMounted(async () => {
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
|
||||
<view
|
||||
class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);"
|
||||
>
|
||||
<view
|
||||
class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]"
|
||||
@click="showLanguageSheet = true">
|
||||
@click="showLanguageSheet = true"
|
||||
>
|
||||
<view>
|
||||
<text class="text-[32rpx] text-[#232338] font-medium">
|
||||
{{ t('settings.language') }}
|
||||
@@ -403,8 +452,8 @@ onMounted(async () => {
|
||||
</text>
|
||||
</view>
|
||||
<view class="flex items-center">
|
||||
<text class="text-[32rpx] text-[#9d9ea3] font-semibold mr-[16rpx]">
|
||||
{{supportedLanguages.find(lang => lang.code === currentLanguage)?.name}}
|
||||
<text class="mr-[16rpx] text-[32rpx] text-[#9d9ea3] font-semibold">
|
||||
{{ supportedLanguages.find(lang => lang.code === currentLanguage)?.name }}
|
||||
</text>
|
||||
<wd-icon name="arrow-right" custom-class="text-[32rpx] text-[#9d9ea3]" />
|
||||
</view>
|
||||
@@ -416,8 +465,10 @@ onMounted(async () => {
|
||||
<wd-action-sheet v-model="showLanguageSheet" :title="t('settings.selectLanguage')" :close-on-click-modal="true">
|
||||
<view class="language-sheet">
|
||||
<scroll-view scroll-y class="language-list">
|
||||
<view v-for="lang in supportedLanguages" :key="lang.code" class="language-item"
|
||||
@click="handleLanguageChange(lang.code)">
|
||||
<view
|
||||
v-for="lang in supportedLanguages" :key="lang.code" class="language-item"
|
||||
@click="handleLanguageChange(lang.code)"
|
||||
>
|
||||
<text class="language-name">
|
||||
{{ lang.name }}
|
||||
</text>
|
||||
|
||||
@@ -3,22 +3,25 @@ import type { ChatHistory, CreateSpeakerData, VoicePrint } from '@/api/voiceprin
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useMessage } from 'wot-design-uni'
|
||||
import { useToast } from 'wot-design-uni/components/wd-toast'
|
||||
import { createVoicePrint, deleteVoicePrint, getChatHistory, getVoicePrintList, updateVoicePrint } from '@/api/voiceprint'
|
||||
import { createVoicePrint, deleteVoicePrint, getAudioDownloadId, getChatHistory, getVoicePrintList, updateVoicePrint } from '@/api/voiceprint'
|
||||
import { t } from '@/i18n'
|
||||
import { getEnvBaseUrl } from '@/utils'
|
||||
|
||||
defineOptions({
|
||||
name: 'VoicePrintManage',
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
agentId: 'default',
|
||||
})
|
||||
|
||||
const emits = defineEmits(['update-refresher-enabled'])
|
||||
|
||||
// 接收props
|
||||
interface Props {
|
||||
agentId?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
agentId: 'default'
|
||||
})
|
||||
|
||||
// 获取屏幕边界到安全区域距离
|
||||
let safeAreaInsets: any
|
||||
let systemInfo: any
|
||||
@@ -50,6 +53,10 @@ const chatHistoryActions = ref<any[]>([])
|
||||
const swipeStates = ref<Record<string, 'left' | 'close' | 'right'>>({})
|
||||
const loading = ref(false)
|
||||
|
||||
// 音频播放相关
|
||||
const audioRef = ref<UniApp.InnerAudioContext | null>(null)
|
||||
const playingAudioId = ref<string>('')
|
||||
|
||||
// 使用传入的智能体ID
|
||||
const currentAgentId = computed(() => {
|
||||
return props.agentId
|
||||
@@ -130,7 +137,6 @@ async function loadChatHistory() {
|
||||
audioId: item.audioId,
|
||||
index,
|
||||
}))
|
||||
showChatHistoryDialog.value = true
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取对话记录失败:', error)
|
||||
@@ -157,11 +163,13 @@ function openAddDialog() {
|
||||
introduce: '',
|
||||
}
|
||||
showAddDialog.value = true
|
||||
} catch (error: any) {
|
||||
}
|
||||
catch (error: any) {
|
||||
// 捕捉声纹接口未配置错误
|
||||
if (error.message && error.message.includes('请求错误[10054]')) {
|
||||
toast.error(t('voiceprint.voiceprintInterfaceNotConfigured'))
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// 其他错误,继续打开弹窗
|
||||
addForm.value = {
|
||||
agentId: currentAgentId.value,
|
||||
@@ -202,6 +210,11 @@ function selectAudioId({ item }: { item: any }) {
|
||||
showChatHistoryDialog.value = false
|
||||
}
|
||||
|
||||
// 点击选择
|
||||
function handleItemClick(item: any) {
|
||||
selectAudioId({ item })
|
||||
}
|
||||
|
||||
// 提交添加说话人
|
||||
async function submitAdd() {
|
||||
if (!addForm.value.sourceName.trim()) {
|
||||
@@ -274,14 +287,93 @@ async function handleDelete(id: string) {
|
||||
})
|
||||
}
|
||||
|
||||
// 播放音频
|
||||
async function playAudio(audioId: string, event: Event) {
|
||||
event.stopPropagation() // 阻止事件冒泡,防止关闭下拉框
|
||||
|
||||
if (!audioId) {
|
||||
toast.warning(t('voiceprint.audioNotExist'))
|
||||
return
|
||||
}
|
||||
|
||||
// 如果正在播放同一个音频,则停止
|
||||
if (playingAudioId.value === audioId) {
|
||||
stopAudio()
|
||||
return
|
||||
}
|
||||
|
||||
// 停止之前的音频
|
||||
stopAudio()
|
||||
|
||||
try {
|
||||
// 先获取音频下载ID
|
||||
playingAudioId.value = audioId
|
||||
const downloadId = await getAudioDownloadId(audioId)
|
||||
|
||||
if (!downloadId) {
|
||||
toast.error(t('voiceprint.getAudioFailed'))
|
||||
playingAudioId.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
// 获取baseURL
|
||||
const baseURL = getEnvBaseUrl()
|
||||
const audioUrl = `${baseURL}/agent/play/${downloadId}`
|
||||
|
||||
// 创建新的音频实例
|
||||
audioRef.value = uni.createInnerAudioContext()
|
||||
audioRef.value.src = audioUrl
|
||||
audioRef.value.autoplay = true
|
||||
|
||||
// 监听播放结束
|
||||
audioRef.value.onEnded(() => {
|
||||
playingAudioId.value = ''
|
||||
})
|
||||
|
||||
// 监听播放错误
|
||||
audioRef.value.onError((error) => {
|
||||
console.error('音频播放错误:', error)
|
||||
toast.error(t('voiceprint.audioPlayFailed'))
|
||||
playingAudioId.value = ''
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.error('播放音频失败:', error)
|
||||
toast.error(t('voiceprint.audioPlayFailed'))
|
||||
playingAudioId.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 停止音频
|
||||
function stopAudio() {
|
||||
if (audioRef.value) {
|
||||
audioRef.value.stop()
|
||||
audioRef.value.destroy()
|
||||
audioRef.value = null
|
||||
}
|
||||
playingAudioId.value = ''
|
||||
}
|
||||
|
||||
watch(() => [showAddDialog.value, showEditDialog.value], (newValues) => {
|
||||
if (newValues.some((value: boolean) => value)) {
|
||||
emits('update-refresher-enabled', false)
|
||||
}
|
||||
else {
|
||||
emits('update-refresher-enabled', true)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
// 智能体已简化为默认
|
||||
|
||||
loadVoicePrintList()
|
||||
loadChatHistory()
|
||||
})
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
showAddDialog,
|
||||
showEditDialog,
|
||||
refresh,
|
||||
})
|
||||
</script>
|
||||
@@ -349,7 +441,7 @@ defineExpose({
|
||||
</view>
|
||||
|
||||
<!-- 浮动操作按钮 -->
|
||||
<wd-fab type="primary" size="small" :draggable="true" :expandable="false" @click="openAddDialog">
|
||||
<wd-fab custom-style="z-index:10" type="primary" size="small" :draggable="true" :expandable="false" @click="openAddDialog">
|
||||
<wd-icon name="add" />
|
||||
</wd-fab>
|
||||
|
||||
@@ -359,25 +451,24 @@ defineExpose({
|
||||
|
||||
<!-- 添加说话人弹窗 -->
|
||||
<wd-popup
|
||||
v-model="showAddDialog" position="center" custom-style="width: 90%; max-width: 400px; border-radius: 16px;"
|
||||
v-model="showAddDialog"
|
||||
position="center"
|
||||
custom-style="width: 90%; max-width: 400px; border-radius: 16px;"
|
||||
safe-area-inset-bottom
|
||||
>
|
||||
<view>
|
||||
<view class="w-full flex items-center justify-between border-b-[2rpx] border-[#eeeeee] p-[32rpx_32rpx_24rpx]">
|
||||
<text class="w-full text-center text-[32rpx] text-[#232338] font-semibold">
|
||||
{{ t('voiceprint.addSpeaker') }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="p-[32rpx]">
|
||||
<!-- 声纹向量选择 -->
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
||||
* {{ t('voiceprint.voiceVector') }}
|
||||
<text class="text-red">
|
||||
*
|
||||
</text>
|
||||
{{ t('voiceprint.voiceVector') }}
|
||||
</text>
|
||||
<view
|
||||
class="flex cursor-pointer items-center justify-between border-[1rpx] border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx] transition-all duration-300 active:bg-[#eef3ff]"
|
||||
@click="loadChatHistory"
|
||||
@click="showChatHistoryDialog = true"
|
||||
>
|
||||
<text
|
||||
class="m-r-[16rpx] flex-1 text-left text-[26rpx] text-[#232338]"
|
||||
@@ -392,7 +483,10 @@ defineExpose({
|
||||
<!-- 姓名 -->
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
||||
* {{ t('voiceprint.name') }}
|
||||
<text class="text-red">
|
||||
*
|
||||
</text>
|
||||
{{ t('voiceprint.name') }}
|
||||
</text>
|
||||
<input
|
||||
v-model="addForm.sourceName"
|
||||
@@ -404,7 +498,10 @@ defineExpose({
|
||||
<!-- 描述 -->
|
||||
<view>
|
||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
||||
* {{ t('voiceprint.description') }}
|
||||
<text class="text-red">
|
||||
*
|
||||
</text>
|
||||
{{ t('voiceprint.description') }}
|
||||
</text>
|
||||
<textarea
|
||||
v-model="addForm.introduce" :maxlength="100" :placeholder="t('voiceprint.pleaseInputDescription')"
|
||||
@@ -418,11 +515,11 @@ defineExpose({
|
||||
|
||||
<view class="flex gap-[16rpx] border-t-[2rpx] border-[#eeeeee] p-[24rpx_32rpx_32rpx]">
|
||||
<wd-button type="info" custom-class="flex-1" @click="showAddDialog = false">
|
||||
{{ t('voiceprint.cancel') }}
|
||||
</wd-button>
|
||||
<wd-button type="primary" custom-class="flex-1" @click="submitAdd">
|
||||
{{ t('voiceprint.save') }}
|
||||
</wd-button>
|
||||
{{ t('voiceprint.cancel') }}
|
||||
</wd-button>
|
||||
<wd-button type="primary" custom-class="flex-1" @click="submitAdd">
|
||||
{{ t('voiceprint.save') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
@@ -433,7 +530,7 @@ defineExpose({
|
||||
safe-area-inset-bottom
|
||||
>
|
||||
<view>
|
||||
<view class="w-full flex items-center justify-between border-b-[2rpx] border-[#eeeeee] p-[32rpx_32rpx_24rpx]">
|
||||
<view class="box-border w-full flex items-center justify-between border-b-[2rpx] border-[#eeeeee] p-[32rpx_32rpx_24rpx]">
|
||||
<text class="w-full text-center text-[32rpx] text-[#232338] font-semibold">
|
||||
{{ t('voiceprint.editSpeaker') }}
|
||||
</text>
|
||||
@@ -443,11 +540,14 @@ defineExpose({
|
||||
<!-- 声纹向量选择 -->
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
||||
* {{ t('voiceprint.voiceVector') }}
|
||||
<text class="text-red">
|
||||
*
|
||||
</text>
|
||||
{{ t('voiceprint.voiceVector') }}
|
||||
</text>
|
||||
<view
|
||||
class="flex cursor-pointer items-center justify-between border-[1rpx] border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx] transition-all duration-300 active:bg-[#eef3ff]"
|
||||
@click="loadChatHistory"
|
||||
@click="showChatHistoryDialog = true"
|
||||
>
|
||||
<text
|
||||
class="m-r-[16rpx] flex-1 text-left text-[26rpx] text-[#232338]"
|
||||
@@ -462,7 +562,10 @@ defineExpose({
|
||||
<!-- 姓名 -->
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
||||
* {{ t('voiceprint.name') }}
|
||||
<text class="text-red">
|
||||
*
|
||||
</text>
|
||||
{{ t('voiceprint.name') }}
|
||||
</text>
|
||||
<input
|
||||
v-model="editForm.sourceName"
|
||||
@@ -474,7 +577,10 @@ defineExpose({
|
||||
<!-- 描述 -->
|
||||
<view>
|
||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
||||
* {{ t('voiceprint.description') }}
|
||||
<text class="text-red">
|
||||
*
|
||||
</text>
|
||||
{{ t('voiceprint.description') }}
|
||||
</text>
|
||||
<textarea
|
||||
v-model="editForm.introduce" :maxlength="100" :placeholder="t('voiceprint.pleaseInputDescription')"
|
||||
@@ -488,23 +594,42 @@ defineExpose({
|
||||
|
||||
<view class="flex gap-[16rpx] border-t-[2rpx] border-[#eeeeee] p-[24rpx_32rpx_32rpx]">
|
||||
<wd-button type="info" custom-class="flex-1" @click="showEditDialog = false">
|
||||
{{ t('voiceprint.cancel') }}
|
||||
</wd-button>
|
||||
<wd-button type="primary" custom-class="flex-1" @click="submitEdit">
|
||||
{{ t('voiceprint.save') }}
|
||||
</wd-button>
|
||||
{{ t('voiceprint.cancel') }}
|
||||
</wd-button>
|
||||
<wd-button type="primary" custom-class="flex-1" @click="submitEdit">
|
||||
{{ t('voiceprint.save') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
|
||||
<!-- 语音对话记录选择动作面板 -->
|
||||
<wd-action-sheet
|
||||
v-model="showChatHistoryDialog" :actions="chatHistoryActions" :title="t('voiceprint.selectVector')"
|
||||
@select="selectAudioId"
|
||||
/>
|
||||
<!-- 自定义语音对话记录选择弹出层 -->
|
||||
<wd-popup v-model="showChatHistoryDialog" class="custom-popup" position="bottom" @close="stopAudio">
|
||||
<view class="rounded-[20rpx] bg-white pb-[20rpx] pt-[20rpx]">
|
||||
<view class="max-h-[600rpx] overflow-y-auto rounded-[20rpx]">
|
||||
<view
|
||||
v-for="item in chatHistoryActions"
|
||||
:key="item.audioId"
|
||||
class="flex items-center justify-between border-b border-[#f5f5f5] p-[32rpx] transition-all active:bg-[#f5f7fb]"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
<text class="flex-1 text-[28rpx] text-[#232338]">
|
||||
{{ item.name }}
|
||||
</text>
|
||||
<view class="ml-[20rpx]" @click.stop="playAudio(item.audioId, $event)">
|
||||
<wd-icon
|
||||
:name="playingAudioId === item.audioId ? 'pause-circle' : 'play-circle'"
|
||||
size="24px"
|
||||
:custom-class="playingAudioId === item.audioId ? 'text-[#336cff]' : 'text-[#9d9ea3]'"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.voiceprint-container {
|
||||
position: relative;
|
||||
}
|
||||
@@ -523,14 +648,10 @@ defineExpose({
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
:deep(.wd-swipe-action) {
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
border: 1rpx solid #eeeeee;
|
||||
}
|
||||
|
||||
:deep(.flex-1) {
|
||||
flex: 1;
|
||||
::v-deep .custom-popup {
|
||||
.wd-popup {
|
||||
padding: 20rpx !important;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,5 +15,7 @@ export default store
|
||||
|
||||
export * from './config'
|
||||
export * from './plugin'
|
||||
export * from './provider'
|
||||
export * from './speedPitch'
|
||||
// 模块统一导出
|
||||
export * from './user'
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { Providers } from '@/api/agent/types'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useProvider = defineStore('provider', () => {
|
||||
const providers = ref<Providers[]>([])
|
||||
|
||||
const updateProviders = (val: Providers[]) => {
|
||||
providers.value = val
|
||||
}
|
||||
|
||||
return {
|
||||
providers,
|
||||
updateProviders,
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
key: 'providers',
|
||||
serializer: {
|
||||
serialize: state => JSON.stringify(state.providers),
|
||||
deserialize: value => ({ providers: JSON.parse(value) }),
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useSpeedPitch = defineStore('speedPitch', () => {
|
||||
const speedPitch = ref({
|
||||
ttsVolume: 0,
|
||||
ttsRate: 0,
|
||||
ttsPitch: 0,
|
||||
})
|
||||
|
||||
const updateSpeedPitch = (val: typeof speedPitch.value) => {
|
||||
speedPitch.value = val
|
||||
}
|
||||
|
||||
return {
|
||||
speedPitch,
|
||||
updateSpeedPitch,
|
||||
}
|
||||
}, {
|
||||
persist: {
|
||||
key: 'speedPitch',
|
||||
serializer: {
|
||||
serialize: state => JSON.stringify(state.speedPitch),
|
||||
deserialize: value => ({ speedPitch: JSON.parse(value) }),
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -19,7 +19,7 @@ const userInfoState: UserInfo & { avatar?: string, token?: string } = {
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore(
|
||||
'user',
|
||||
'userInfo',
|
||||
() => {
|
||||
// 定义用户信息
|
||||
const userInfo = ref<UserInfo & { avatar?: string, token?: string }>({ ...userInfoState })
|
||||
@@ -51,15 +51,8 @@ export const useUserStore = defineStore(
|
||||
*/
|
||||
const getUserInfo = async () => {
|
||||
const userData = await _getUserInfo()
|
||||
const userInfoWithExtras = {
|
||||
...userData,
|
||||
avatar: userInfoState.avatar,
|
||||
token: uni.getStorageSync('token') || '',
|
||||
}
|
||||
setUserInfo(userInfoWithExtras)
|
||||
uni.setStorageSync('userInfo', userInfoWithExtras)
|
||||
// TODO 这里可以增加获取用户路由的方法 根据用户的角色动态生成路由
|
||||
return userInfoWithExtras
|
||||
setUserInfo(userData)
|
||||
return userData
|
||||
}
|
||||
/**
|
||||
* 退出登录 并 删除用户信息
|
||||
@@ -78,6 +71,12 @@ export const useUserStore = defineStore(
|
||||
}
|
||||
},
|
||||
{
|
||||
persist: true,
|
||||
persist: {
|
||||
key: 'userInfo',
|
||||
serializer: {
|
||||
serialize: state => JSON.stringify(state.userInfo),
|
||||
deserialize: value => ({ userInfo: JSON.parse(value) }),
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import smCrypto from 'sm-crypto'
|
||||
import { pages, subPackages } from '@/pages.json'
|
||||
|
||||
import { isMpWeixin } from './platform'
|
||||
|
||||
/**
|
||||
@@ -197,23 +199,21 @@ export function getEnvBaseUploadUrl() {
|
||||
return baseUploadUrl
|
||||
}
|
||||
|
||||
import smCrypto from 'sm-crypto'
|
||||
|
||||
/**
|
||||
* 生成SM2密钥对(十六进制格式)
|
||||
* @returns {Object} 包含公钥和私钥的对象
|
||||
*/
|
||||
export function generateSm2KeyPairHex() {
|
||||
// 使用sm-crypto库生成SM2密钥对
|
||||
const sm2 = smCrypto.sm2;
|
||||
const keypair = sm2.generateKeyPairHex();
|
||||
|
||||
return {
|
||||
publicKey: keypair.publicKey,
|
||||
privateKey: keypair.privateKey,
|
||||
clientPublicKey: keypair.publicKey, // 客户端公钥
|
||||
clientPrivateKey: keypair.privateKey // 客户端私钥
|
||||
};
|
||||
// 使用sm-crypto库生成SM2密钥对
|
||||
const sm2 = smCrypto.sm2
|
||||
const keypair = sm2.generateKeyPairHex()
|
||||
|
||||
return {
|
||||
publicKey: keypair.publicKey,
|
||||
privateKey: keypair.privateKey,
|
||||
clientPublicKey: keypair.publicKey, // 客户端公钥
|
||||
clientPrivateKey: keypair.privateKey, // 客户端私钥
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,21 +223,21 @@ export function generateSm2KeyPairHex() {
|
||||
* @returns {string} 加密后的密文(十六进制格式)
|
||||
*/
|
||||
export function sm2Encrypt(publicKey: string, plainText: string): string {
|
||||
if (!publicKey) {
|
||||
throw new Error('公钥不能为null或undefined');
|
||||
}
|
||||
|
||||
if (!plainText) {
|
||||
throw new Error('明文不能为空');
|
||||
}
|
||||
|
||||
const sm2 = smCrypto.sm2;
|
||||
// SM2加密,添加04前缀表示未压缩公钥
|
||||
const encrypted = sm2.doEncrypt(plainText, publicKey, 1);
|
||||
// 转换为十六进制格式(与后端保持一致,添加04前缀)
|
||||
const result = "04" + encrypted;
|
||||
|
||||
return result;
|
||||
if (!publicKey) {
|
||||
throw new Error('公钥不能为null或undefined')
|
||||
}
|
||||
|
||||
if (!plainText) {
|
||||
throw new Error('明文不能为空')
|
||||
}
|
||||
|
||||
const sm2 = smCrypto.sm2
|
||||
// SM2加密,添加04前缀表示未压缩公钥
|
||||
const encrypted = sm2.doEncrypt(plainText, publicKey, 1)
|
||||
// 转换为十六进制格式(与后端保持一致,添加04前缀)
|
||||
const result = `04${encrypted}`
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,9 +247,56 @@ export function sm2Encrypt(publicKey: string, plainText: string): string {
|
||||
* @returns {string} 解密后的明文
|
||||
*/
|
||||
export function sm2Decrypt(privateKey: string, cipherText: string): string {
|
||||
const sm2 = smCrypto.sm2;
|
||||
// 移除04前缀(与后端保持一致)
|
||||
const dataWithoutPrefix = cipherText.startsWith("04") ? cipherText.substring(2) : cipherText;
|
||||
// SM2解密
|
||||
return sm2.doDecrypt(dataWithoutPrefix, privateKey, 1);
|
||||
const sm2 = smCrypto.sm2
|
||||
// 移除04前缀(与后端保持一致)
|
||||
const dataWithoutPrefix = cipherText.startsWith('04') ? cipherText.substring(2) : cipherText
|
||||
// SM2解密
|
||||
return sm2.doDecrypt(dataWithoutPrefix, privateKey, 1)
|
||||
}
|
||||
|
||||
type AnyFunction = (...args: any[]) => any
|
||||
|
||||
interface DebouncedFunction extends AnyFunction {
|
||||
cancel: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
* @param fn 要防抖的函数
|
||||
* @param delay 延迟时间(毫秒),默认500ms
|
||||
* @param immediate 是否立即执行,默认false
|
||||
* @returns 防抖处理后的函数
|
||||
*/
|
||||
export function debounce<T extends AnyFunction>(
|
||||
fn: T,
|
||||
delay = 500,
|
||||
immediate = false,
|
||||
): DebouncedFunction {
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const debounced = function (this: any, ...args: Parameters<T>) {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
|
||||
if (immediate && !timer) {
|
||||
fn.apply(this, args)
|
||||
}
|
||||
|
||||
timer = setTimeout(() => {
|
||||
if (!immediate) {
|
||||
fn.apply(this, args)
|
||||
}
|
||||
timer = null
|
||||
}, delay)
|
||||
} as DebouncedFunction
|
||||
|
||||
debounced.cancel = () => {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
|
||||
return debounced
|
||||
}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
VUE_APP_TITLE=智控台
|
||||
VUE_APP_TITLE=智控台
|
||||
VUE_APP_DESCRIPTION=小智后端服务(xiaozhi-server)是由华南理工大学刘思源教授团队主导研发的智能终端软硬件体系后端服务系统,专为xiaozhi-esp32开源硬件打造,具备多协议兼容、声纹识别、知识库管理等核心能力。
|
||||
VUE_APP_KEYWORDS=xiaozhi-server,小智服务端,智控台,AI硬件,智能硬件,AI玩具,情感陪伴,聊天机器人,智能家居,车载机器人
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<meta name="description" content="<%= process.env.VUE_APP_DESCRIPTION %>">
|
||||
<meta name="keywords" content="<%= process.env.VUE_APP_KEYWORDS %>">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>
|
||||
<%= process.env.VUE_APP_TITLE %>
|
||||
@@ -17,10 +19,6 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
|
||||
Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
<% if (htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
|
||||
|
||||
@@ -28,17 +28,16 @@ nav {
|
||||
}
|
||||
|
||||
.copyright {
|
||||
text-align: center;
|
||||
padding: 0 !important;
|
||||
color: rgb(0, 0, 0);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: auto;
|
||||
padding: 30px 0 20px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.el-message {
|
||||
@@ -60,6 +59,11 @@ export default {
|
||||
isCDNEnabled: process.env.VUE_APP_USE_CDN === 'true'
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 挂载 store 状态
|
||||
this.$store.commit('setUserInfo', JSON.parse(localStorage.getItem('userInfo') || '{}'));
|
||||
this.$store.commit('setPubConfig', JSON.parse(localStorage.getItem('pubConfig') || '{}'));
|
||||
},
|
||||
mounted() {
|
||||
// 检测是否为移动设备且VUE_APP_H5_URL不为空,如果两个条件都满足则跳转到H5页面
|
||||
if (this.isMobileDevice() && process.env.VUE_APP_H5_URL) {
|
||||
|
||||
@@ -170,6 +170,9 @@ export default {
|
||||
RequestService.clearRequestTime();
|
||||
callback(res);
|
||||
})
|
||||
.fail((err) => {
|
||||
callback(err);
|
||||
})
|
||||
.networkFail(() => {
|
||||
RequestService.reAjaxFun(() => {
|
||||
this.getAgentMcpAccessAddress(agentId, callback);
|
||||
|
||||
@@ -21,11 +21,35 @@
|
||||
<div v-if="message.type === 'time'" class="time-divider">
|
||||
{{ message.content }}
|
||||
</div>
|
||||
<div v-else class="message-item" :class="{ 'user-message': message.chatType === 1 }">
|
||||
<div v-else class="message-item" :class="{ 'user-message': message.chatType === 1, 'tool-message': message.chatType === 3 }">
|
||||
<img :src="message.chatType === 1 ? getUserAvatar(currentSessionId) : require('@/assets/xiaozhi-logo.png')"
|
||||
class="avatar" />
|
||||
<div class="message-content">
|
||||
{{ extractContentFromString(message.content) }}
|
||||
<template v-if="Array.isArray(extractContentFromString(message.content))">
|
||||
<div class="content-wrapper">
|
||||
<div v-for="(item, idx) in extractContentFromString(message.content)" :key="idx">
|
||||
<div v-if="item.type === 'text'" class="text-content">{{ item.text }}</div>
|
||||
<div v-else-if="item.type === 'tool'" class="tool-call-text">{{ item.text }}</div>
|
||||
<div v-else-if="item.type === 'tool_result'" class="tool-call-text">
|
||||
<div v-if="item.text && item.text.length > 80" class="tool-result-wrapper">
|
||||
<div v-if="isToolResultCollapsed(index, idx)" class="tool-result-collapsed">
|
||||
{{ getFirstLineText(item.text) }}
|
||||
</div>
|
||||
<div v-else class="tool-result-expanded">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
<span class="tool-toggle-btn" @click="toggleToolResult(index, idx)">
|
||||
<i :class="isToolResultCollapsed(index, idx) ? 'el-icon-arrow-down' : 'el-icon-arrow-up'"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else>{{ item.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ extractContentFromString(message.content) }}
|
||||
</template>
|
||||
<i v-if="message.audioId" :class="getAudioIconClass(message)"
|
||||
@click="playAudio(message)" class="audio-icon"></i>
|
||||
</div>
|
||||
@@ -49,6 +73,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { debounce } from '@/utils'
|
||||
import Api from '@/apis/api';
|
||||
|
||||
export default {
|
||||
@@ -81,7 +106,8 @@ export default {
|
||||
scrollTimer: null,
|
||||
isFirstLoad: true,
|
||||
playingAudioId: null,
|
||||
audioElement: null
|
||||
audioElement: null,
|
||||
expandedToolResults: {} // 跟踪工具结果的展开状态
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -90,6 +116,10 @@ export default {
|
||||
if (val) {
|
||||
this.resetData();
|
||||
this.loadSessions();
|
||||
} else {
|
||||
this.audioElement?.pause();
|
||||
this.audioElement = null;
|
||||
this.playingAudioId = null;
|
||||
}
|
||||
},
|
||||
dialogVisible(val) {
|
||||
@@ -154,6 +184,13 @@ export default {
|
||||
// 尝试解析为 JSON
|
||||
try {
|
||||
const jsonObj = JSON.parse(content);
|
||||
|
||||
// 如果是数组格式(包含 text 和 tool)
|
||||
if (Array.isArray(jsonObj)) {
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
// 如果是对象且有 content 字段
|
||||
if (jsonObj && typeof jsonObj === 'object' && jsonObj.content) {
|
||||
return jsonObj.content;
|
||||
}
|
||||
@@ -164,6 +201,23 @@ export default {
|
||||
// 如果不是 JSON 格式或没有 content 字段,直接返回原内容
|
||||
return content;
|
||||
},
|
||||
// 切换工具结果的展开/折叠状态
|
||||
toggleToolResult(messageIndex, itemIndex) {
|
||||
const key = `${messageIndex}-${itemIndex}`;
|
||||
this.$set(this.expandedToolResults, key, !this.expandedToolResults[key]);
|
||||
},
|
||||
// 判断工具结果是否处于折叠状态
|
||||
isToolResultCollapsed(messageIndex, itemIndex) {
|
||||
const key = `${messageIndex}-${itemIndex}`;
|
||||
// 默认折叠(true表示折叠)
|
||||
return !this.expandedToolResults[key];
|
||||
},
|
||||
// 获取截断的文本(只显示第一行)
|
||||
getFirstLineText(text) {
|
||||
if (!text) return '';
|
||||
const firstLine = text.split('\n')[0];
|
||||
return firstLine.length < text.length ? firstLine + '...' : text;
|
||||
},
|
||||
resetData() {
|
||||
this.sessions = [];
|
||||
this.messages = [];
|
||||
@@ -173,6 +227,7 @@ export default {
|
||||
this.loading = false;
|
||||
this.hasMore = true;
|
||||
this.isFirstLoad = true;
|
||||
this.expandedToolResults = {};
|
||||
},
|
||||
handleClose() {
|
||||
this.dialogVisible = false;
|
||||
@@ -255,7 +310,7 @@ export default {
|
||||
}
|
||||
return 'el-icon-video-play';
|
||||
},
|
||||
playAudio(message) {
|
||||
playAudio: debounce(function(message) {
|
||||
if (this.playingAudioId === message.audioId) {
|
||||
// 如果正在播放当前音频,则停止播放
|
||||
if (this.audioElement) {
|
||||
@@ -276,9 +331,12 @@ export default {
|
||||
this.playingAudioId = message.audioId;
|
||||
Api.agent.getAudioId(message.audioId, (res) => {
|
||||
if (res.data && res.data.data) {
|
||||
if (!this.audioElement) {
|
||||
this.audioElement = new Audio();
|
||||
}
|
||||
|
||||
// 使用获取到的下载ID播放音频
|
||||
this.audioElement = new Audio(Api.getServiceUrl() + `/agent/play/${res.data.data}`);
|
||||
|
||||
this.audioElement.src = Api.getServiceUrl() + `/agent/play/${res.data.data}`;
|
||||
this.audioElement.onended = () => {
|
||||
this.playingAudioId = null;
|
||||
this.audioElement = null;
|
||||
@@ -287,7 +345,7 @@ export default {
|
||||
this.audioElement.play();
|
||||
}
|
||||
});
|
||||
},
|
||||
}, 300),
|
||||
getUserAvatar(sessionId) {
|
||||
// 从 sessionId 中提取所有数字
|
||||
const numbers = sessionId.match(/\d+/g);
|
||||
@@ -442,6 +500,56 @@ export default {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.tool-call-text {
|
||||
color: #1890ff;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.user-message .tool-call-text {
|
||||
color: #e6f7ff;
|
||||
}
|
||||
|
||||
.tool-message .message-content {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.tool-result-wrapper {
|
||||
position: relative;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.tool-result-collapsed {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tool-toggle-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
color: #1890ff;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tool-toggle-btn:hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
|
||||
.loading,
|
||||
.no-more {
|
||||
text-align: center;
|
||||
|
||||
@@ -314,7 +314,7 @@ export default {
|
||||
if (res.data.code === 0) {
|
||||
this.mcpUrl = res.data.data || "";
|
||||
} else {
|
||||
this.mcpUrl = "";
|
||||
this.mcpUrl = res.data.msg;
|
||||
console.error('获取MCP地址失败:', res.data.msg);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<span class="nav-text">{{ $t("header.smartManagement") }}</span>
|
||||
</div>
|
||||
<!-- 普通用户显示音色克隆 -->
|
||||
<div v-if="!isSuperAdmin && featureStatus.voiceClone" class="equipment-management"
|
||||
<div v-if="!userInfo.superAdmin && featureStatus.voiceClone" class="equipment-management"
|
||||
:class="{ 'active-tab': $route.path === '/voice-clone-management' }" @click="goVoiceCloneManagement">
|
||||
<img loading="lazy" alt="" src="@/assets/header/voice.png" :style="{
|
||||
filter:
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 超级管理员显示音色克隆下拉菜单 -->
|
||||
<el-dropdown v-if="isSuperAdmin && featureStatus.voiceClone" trigger="click" class="equipment-management more-dropdown" :class="{
|
||||
<el-dropdown v-if="userInfo.superAdmin && featureStatus.voiceClone" trigger="click" class="equipment-management more-dropdown" :class="{
|
||||
'active-tab':
|
||||
$route.path === '/voice-clone-management' ||
|
||||
$route.path === '/voice-resource-management',
|
||||
@@ -64,7 +64,7 @@
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
|
||||
<div v-if="isSuperAdmin" class="equipment-management" :class="{ 'active-tab': $route.path === '/model-config' }"
|
||||
<div v-if="userInfo.superAdmin" class="equipment-management" :class="{ 'active-tab': $route.path === '/model-config' }"
|
||||
@click="goModelConfig">
|
||||
<img loading="lazy" alt="" src="@/assets/header/model_config.png" :style="{
|
||||
filter:
|
||||
@@ -81,7 +81,7 @@
|
||||
}" />
|
||||
<span class="nav-text">{{ $t("header.knowledgeBase") }}</span>
|
||||
</div>
|
||||
<el-dropdown v-if="isSuperAdmin" trigger="click" class="equipment-management more-dropdown" :class="{
|
||||
<el-dropdown v-if="userInfo.superAdmin" trigger="click" class="equipment-management more-dropdown" :class="{
|
||||
'active-tab':
|
||||
$route.path === '/dict-management' ||
|
||||
$route.path === '/params-management' ||
|
||||
@@ -140,7 +140,7 @@
|
||||
|
||||
<!-- 右侧元素 -->
|
||||
<div class="header-right">
|
||||
<div class="search-container" v-if="$route.path === '/home' && !(isSuperAdmin && isSmallScreen)">
|
||||
<div class="search-container" v-if="$route.path === '/home' && !(userInfo.superAdmin && isSmallScreen)">
|
||||
<div class="search-wrapper">
|
||||
<el-input v-model="search" :placeholder="$t('header.searchPlaceholder')" class="custom-search-input"
|
||||
@keyup.enter.native="handleSearch" @focus="showSearchHistory" @blur="hideSearchHistory" clearable
|
||||
@@ -189,7 +189,7 @@
|
||||
<script>
|
||||
import userApi from "@/apis/module/user";
|
||||
import i18n, { changeLanguage } from "@/i18n";
|
||||
import { mapActions, mapGetters } from "vuex";
|
||||
import { mapActions, mapState } from "vuex";
|
||||
import ChangePasswordDialog from "./ChangePasswordDialog.vue"; // 引入修改密码弹窗组件
|
||||
import featureManager from "@/utils/featureManager"; // 引入功能管理工具类
|
||||
|
||||
@@ -202,10 +202,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
search: "",
|
||||
userInfo: {
|
||||
username: "",
|
||||
mobile: "",
|
||||
},
|
||||
isChangePasswordDialogVisible: false, // 控制修改密码弹窗的显示
|
||||
paramDropdownVisible: false,
|
||||
voiceCloneDropdownVisible: false,
|
||||
@@ -224,18 +220,16 @@ export default {
|
||||
label: "label",
|
||||
children: "children",
|
||||
},
|
||||
// 功能状态
|
||||
featureStatus: {
|
||||
voiceClone: false, // 音色克隆功能状态
|
||||
knowledgeBase: false, // 知识库功能状态
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["getIsSuperAdmin"]),
|
||||
isSuperAdmin() {
|
||||
return this.getIsSuperAdmin;
|
||||
},
|
||||
...mapState({
|
||||
featureStatus: (state) => ({
|
||||
voiceClone: state.pubConfig.systemWebMenu?.features?.voiceClone?.enabled, // 音色克隆功能状态
|
||||
knowledgeBase: state.pubConfig.systemWebMenu?.features?.knowledgeBase?.enabled, // 知识库功能状态
|
||||
}),
|
||||
userInfo: (state) => state.userInfo,
|
||||
}),
|
||||
// 获取当前语言
|
||||
currentLanguage() {
|
||||
return i18n.locale || "zh_CN";
|
||||
@@ -325,7 +319,6 @@ export default {
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
this.fetchUserInfo();
|
||||
this.checkScreenSize();
|
||||
window.addEventListener("resize", this.checkScreenSize);
|
||||
// 从localStorage加载搜索历史
|
||||
@@ -386,20 +379,6 @@ export default {
|
||||
async loadFeatureStatus() {
|
||||
// 等待featureManager初始化完成
|
||||
await featureManager.waitForInitialization();
|
||||
|
||||
const config = featureManager.getConfig();
|
||||
|
||||
this.featureStatus.voiceClone = config.voiceClone;
|
||||
this.featureStatus.knowledgeBase = config.knowledgeBase;
|
||||
},
|
||||
// 获取用户信息
|
||||
fetchUserInfo() {
|
||||
userApi.getUserInfo(({ data }) => {
|
||||
this.userInfo = data.data;
|
||||
if (data.data.superAdmin !== undefined) {
|
||||
this.$store.commit("setUserInfo", data.data);
|
||||
}
|
||||
});
|
||||
},
|
||||
checkScreenSize() {
|
||||
this.isSmallScreen = window.innerWidth <= 1386;
|
||||
|
||||
@@ -17,17 +17,36 @@
|
||||
class="custom-input"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('paramDialog.paramValue')" prop="paramValue" class="form-item">
|
||||
<el-input v-model="form.paramValue" :placeholder="$t('paramDialog.paramValuePlaceholder')"
|
||||
class="custom-input"></el-input>
|
||||
<el-form-item :label="$t('paramDialog.valueType')" prop="valueType" class="form-item">
|
||||
<el-select
|
||||
v-model="form.valueType"
|
||||
:placeholder="$t('paramDialog.valueTypePlaceholder')"
|
||||
class="custom-select"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in valueTypeOptions"
|
||||
:key="item.value"
|
||||
:label="$t(`paramDialog.${item.value}Type`)"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('paramDialog.valueType')" prop="valueType" class="form-item">
|
||||
<el-select v-model="form.valueType" :placeholder="$t('paramDialog.valueTypePlaceholder')"
|
||||
class="custom-select">
|
||||
<el-option v-for="item in valueTypeOptions" :key="item.value" :label="$t(`paramDialog.${item.value}Type`)"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
<el-form-item :label="$t('paramDialog.paramValue')" prop="paramValue" class="form-item">
|
||||
<el-input
|
||||
v-if="form.valueType !== 'json' && form.valueType !== 'array'"
|
||||
v-model="form.paramValue"
|
||||
:placeholder="$t('paramDialog.paramValuePlaceholder')"
|
||||
class="custom-input"
|
||||
></el-input>
|
||||
<el-input
|
||||
v-else
|
||||
type="textarea"
|
||||
v-model="form.paramValue"
|
||||
:placeholder="$t('paramDialog.paramValuePlaceholder')"
|
||||
:rows="6"
|
||||
class="custom-textarea"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('paramDialog.remark')" prop="remark" class="form-item remark-item">
|
||||
@@ -98,8 +117,37 @@ export default {
|
||||
submit() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (valid) {
|
||||
const submitData = { ...this.form };
|
||||
|
||||
// 如果是 array 类型,校验格式并转换
|
||||
if (submitData.valueType === 'array' && submitData.paramValue) {
|
||||
const lines = submitData.paramValue.split('\n').filter(line => line.trim());
|
||||
|
||||
// 检查除最后一行外的每行是否以分号结尾
|
||||
for (let i = 0; i < lines.length - 1; i++) {
|
||||
if (!lines[i].trim().endsWith(';')) {
|
||||
this.$message.error('数组格式错误,需要使用英文分号结尾');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const items = lines
|
||||
.map(item => item.trim().replace(/;$/, ''))
|
||||
.filter(item => item);
|
||||
submitData.paramValue = items.join(';');
|
||||
}
|
||||
// 如果是 json 类型,压缩 JSON 格式后再提交
|
||||
else if (submitData.valueType === 'json' && submitData.paramValue) {
|
||||
try {
|
||||
const parsed = JSON.parse(submitData.paramValue);
|
||||
submitData.paramValue = JSON.stringify(parsed);
|
||||
} catch (e) {
|
||||
// 如果解析失败,保持原值
|
||||
}
|
||||
}
|
||||
|
||||
this.saving = true; // 开始加载
|
||||
this.$emit('submit', this.form);
|
||||
this.$emit('submit', submitData);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -116,7 +164,24 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
visible(newVal) {
|
||||
if (!newVal) {
|
||||
if (newVal) {
|
||||
if (this.form.paramValue) {
|
||||
// 如果是 json 类型,格式化显示
|
||||
if (this.form.valueType === 'json') {
|
||||
try {
|
||||
const parsed = JSON.parse(this.form.paramValue);
|
||||
this.form.paramValue = JSON.stringify(parsed, null, 2);
|
||||
} catch (e) {
|
||||
// 如果解析失败,保持原值
|
||||
}
|
||||
}
|
||||
// 如果是 array 类型,将分号分隔的字符串转换为每行一个项目
|
||||
else if (this.form.valueType === 'array') {
|
||||
const items = this.form.paramValue.split(';').filter(item => item.trim());
|
||||
this.form.paramValue = items.join(';\n');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 当对话框关闭时,重置saving状态
|
||||
this.saving = false;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,6 @@
|
||||
<el-option :label="$t('providerDialog.booleanType')" value="boolean"></el-option>
|
||||
<el-option :label="$t('providerDialog.dictType')" value="dict"></el-option>
|
||||
<el-option :label="$t('providerDialog.arrayType')" value="array"></el-option>
|
||||
<el-option :label="$t('providerDialog.ragType')" value="RAG"></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('voiceClone.languages')" prop="languages">
|
||||
<el-input v-model="form.languages" :placeholder="$t('voiceClone.languagesPlaceholder')" style="width: 100%">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
@@ -70,6 +75,9 @@ export default {
|
||||
],
|
||||
userId: [
|
||||
{ required: true, message: this.$t('voiceClone.userIdRequired'), trigger: 'change' }
|
||||
],
|
||||
languages: [
|
||||
{ required: true, message: this.$t('voiceClone.languagesRequired'), trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1132,6 +1132,9 @@ export default {
|
||||
'voiceClone.platformNamePlaceholder': 'Bitte Plattformname auswählen',
|
||||
'voiceClone.voiceIdPlaceholder': 'Bitte Stimmen-ID eingeben und Enter drücken',
|
||||
'voiceClone.userIdPlaceholder': 'Bitte Schlüsselwort eingeben, um Kontoinhaber auszuwählen',
|
||||
'voiceClone.languages': 'Sprachen',
|
||||
'voiceClone.languagesPlaceholder': 'Bitte Sprachen eingeben, z.B.: Deutsch, English',
|
||||
'voiceClone.languagesRequired': 'Bitte Sprachen eingeben',
|
||||
'voiceClone.waitingUpload': 'Wartet auf Upload',
|
||||
'voiceClone.waitingTraining': 'Wartet auf Klon',
|
||||
'voiceClone.training': 'Training',
|
||||
|
||||
@@ -1132,6 +1132,9 @@ export default {
|
||||
'voiceClone.platformNamePlaceholder': 'Please select platform name',
|
||||
'voiceClone.voiceIdPlaceholder': 'Please enter voice ID and press Enter',
|
||||
'voiceClone.userIdPlaceholder': 'Please enter keyword to select account owner',
|
||||
'voiceClone.languages': 'Languages',
|
||||
'voiceClone.languagesPlaceholder': 'Please enter languages, e.g.: Chinese, English',
|
||||
'voiceClone.languagesRequired': 'Please enter languages',
|
||||
'voiceClone.waitingUpload': 'Waiting for upload',
|
||||
'voiceClone.waitingTraining': 'Waiting for clone',
|
||||
'voiceClone.training': 'Training',
|
||||
|
||||
@@ -670,10 +670,10 @@ export default {
|
||||
'home.wish': 'Vamos ter um dia maravilhoso!',
|
||||
'home.languageModel': 'LLM',
|
||||
'home.voiceModel': 'TTS',
|
||||
'home.configureRole': 'Configurar Papel',
|
||||
'home.voiceprintRecognition': 'Impressão Vocal',
|
||||
'home.configureRole': 'ConfRoles',
|
||||
'home.voiceprintRecognition': 'RecVoz',
|
||||
'home.deviceManagement': 'Dispositivos',
|
||||
'home.chatHistory': 'Histórico de Chat',
|
||||
'home.chatHistory': 'Conversa',
|
||||
'home.lastConversation': 'Última Conversa',
|
||||
'home.noConversation': 'Nenhuma conversa',
|
||||
'home.justNow': 'Agora mesmo',
|
||||
@@ -1132,6 +1132,9 @@ export default {
|
||||
'voiceClone.platformNamePlaceholder': 'Por favor, selecione o nome da plataforma',
|
||||
'voiceClone.voiceIdPlaceholder': 'Por favor, insira o ID da voz e pressione Enter',
|
||||
'voiceClone.userIdPlaceholder': 'Por favor, insira palavra-chave para selecionar o proprietário da conta',
|
||||
'voiceClone.languages': 'Idiomas',
|
||||
'voiceClone.languagesPlaceholder': 'Por favor, insira idiomas, ex.: Português, English',
|
||||
'voiceClone.languagesRequired': 'Por favor, insira idiomas',
|
||||
'voiceClone.waitingUpload': 'Aguardando envio',
|
||||
'voiceClone.waitingTraining': 'Aguardando clonagem',
|
||||
'voiceClone.training': 'Treinando',
|
||||
|
||||
@@ -1132,6 +1132,9 @@ export default {
|
||||
'voiceClone.platformNamePlaceholder': 'Vui lòng chọn tên nền tảng',
|
||||
'voiceClone.voiceIdPlaceholder': 'Vui lòng nhập ID giọng nói và nhấn Enter',
|
||||
'voiceClone.userIdPlaceholder': 'Vui lòng nhập từ khóa để chọn chủ sở hữu tài khoản',
|
||||
'voiceClone.languages': 'Ngôn ngữ',
|
||||
'voiceClone.languagesPlaceholder': 'Vui lòng nhập ngôn ngữ, ví dụ: Tiếng Việt, English',
|
||||
'voiceClone.languagesRequired': 'Vui lòng nhập ngôn ngữ',
|
||||
'voiceClone.waitingUpload': 'Đang chờ tải lên',
|
||||
'voiceClone.waitingTraining': 'Đang chờ nhân bản',
|
||||
'voiceClone.training': 'Đang huấn luyện',
|
||||
|
||||
@@ -189,7 +189,7 @@ export default {
|
||||
'editVoiceDialog.remarkPlaceholder': '请输入备注内容',
|
||||
'editVoiceDialog.generatePreview': '生成试听',
|
||||
'editVoiceDialog.defaultVoiceName': '湾湾小何',
|
||||
'editVoiceDialog.defaultLanguageType': '中文',
|
||||
'editVoiceDialog.defaultLanguageType': '普通话',
|
||||
'editVoiceDialog.requiredVoiceCode': '请输入音色编码',
|
||||
'editVoiceDialog.requiredVoiceName': '请输入音色名称',
|
||||
|
||||
@@ -1132,6 +1132,9 @@ export default {
|
||||
'voiceClone.platformNamePlaceholder': '请选择平台名称',
|
||||
'voiceClone.voiceIdPlaceholder': '请输入音色ID并按回车',
|
||||
'voiceClone.userIdPlaceholder': '请输入关键词选择归属账号',
|
||||
'voiceClone.languages': '语言',
|
||||
'voiceClone.languagesPlaceholder': '请输入语言,如:中文、English',
|
||||
'voiceClone.languagesRequired': '请输入语言',
|
||||
'voiceClone.waitingUpload': '待上传',
|
||||
'voiceClone.waitingTraining': '待复刻',
|
||||
'voiceClone.training': '训练中',
|
||||
|
||||
@@ -189,7 +189,7 @@ export default {
|
||||
'editVoiceDialog.remarkPlaceholder': '請輸入備註內容',
|
||||
'editVoiceDialog.generatePreview': '生成試聽',
|
||||
'editVoiceDialog.defaultVoiceName': '灣灣小何',
|
||||
'editVoiceDialog.defaultLanguageType': '中文',
|
||||
'editVoiceDialog.defaultLanguageType': '普通話',
|
||||
'editVoiceDialog.requiredVoiceCode': '請輸入音色編碼',
|
||||
'editVoiceDialog.requiredVoiceName': '請輸入音色名稱',
|
||||
|
||||
@@ -1132,6 +1132,9 @@ export default {
|
||||
'voiceClone.platformNamePlaceholder': '請選擇平台名稱',
|
||||
'voiceClone.voiceIdPlaceholder': '請輸入音色ID並按回车',
|
||||
'voiceClone.userIdPlaceholder': '請輸入关键词選擇歸屬帳號',
|
||||
'voiceClone.languages': '語言',
|
||||
'voiceClone.languagesPlaceholder': '請輸入語言,如:中文、English',
|
||||
'voiceClone.languagesRequired': '請輸入語言',
|
||||
'voiceClone.waitingUpload': '待上傳',
|
||||
'voiceClone.waitingTraining': '待複刻',
|
||||
'voiceClone.training': '訓練中',
|
||||
|
||||
@@ -10,7 +10,6 @@ export default new Vuex.Store({
|
||||
state: {
|
||||
token: '',
|
||||
userInfo: {}, // 添加用户信息存储
|
||||
isSuperAdmin: false, // 添加superAdmin状态
|
||||
pubConfig: { // 添加公共配置存储
|
||||
version: '',
|
||||
beianIcpNum: 'null',
|
||||
@@ -29,12 +28,6 @@ export default new Vuex.Store({
|
||||
getUserInfo(state) {
|
||||
return state.userInfo
|
||||
},
|
||||
getIsSuperAdmin(state) {
|
||||
if (localStorage.getItem('isSuperAdmin') === null) {
|
||||
return state.isSuperAdmin
|
||||
}
|
||||
return localStorage.getItem('isSuperAdmin') === 'true'
|
||||
},
|
||||
getPubConfig(state) {
|
||||
return state.pubConfig
|
||||
}
|
||||
@@ -46,19 +39,17 @@ export default new Vuex.Store({
|
||||
},
|
||||
setUserInfo(state, userInfo) {
|
||||
state.userInfo = userInfo
|
||||
const isSuperAdmin = userInfo.superAdmin === 1
|
||||
state.isSuperAdmin = isSuperAdmin
|
||||
localStorage.setItem('isSuperAdmin', isSuperAdmin)
|
||||
localStorage.setItem('userInfo', JSON.stringify(userInfo))
|
||||
},
|
||||
setPubConfig(state, config) {
|
||||
state.pubConfig = config
|
||||
localStorage.setItem('pubConfig', JSON.stringify(config))
|
||||
},
|
||||
clearAuth(state) {
|
||||
state.token = ''
|
||||
state.userInfo = {}
|
||||
state.isSuperAdmin = false
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('isSuperAdmin')
|
||||
localStorage.removeItem('userInfo')
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@@ -67,7 +58,6 @@ export default new Vuex.Store({
|
||||
return new Promise((resolve) => {
|
||||
commit('clearAuth')
|
||||
goToPage(Constant.PAGE.LOGIN, true);
|
||||
window.location.reload(); // 彻底重置状态
|
||||
})
|
||||
},
|
||||
// 添加获取公共配置的 action
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//功能配置工具
|
||||
import Api from "@/apis/api";
|
||||
import store from "@/store";
|
||||
|
||||
class FeatureManager {
|
||||
constructor() {
|
||||
@@ -72,7 +73,13 @@ class FeatureManager {
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新config缓存
|
||||
*/
|
||||
updateConfigCache(config) {
|
||||
store.commit('setPubConfig', config);
|
||||
localStorage.setItem('pubConfig', JSON.stringify(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从pub-config接口获取配置
|
||||
@@ -85,6 +92,7 @@ class FeatureManager {
|
||||
if (result && result.status === 200) {
|
||||
// 检查是否有data字段
|
||||
if (result.data) {
|
||||
const configCache = result.data.data || {};
|
||||
// 检查是否有code字段,如果有则按照code判断
|
||||
if (result.data.code !== undefined) {
|
||||
if (result.data.code === 0 && result.data.data && result.data.data.systemWebMenu) {
|
||||
@@ -110,6 +118,7 @@ class FeatureManager {
|
||||
console.warn('配置中缺少features对象,使用默认配置');
|
||||
resolve(this.defaultFeatures);
|
||||
}
|
||||
configCache.systemWebMenu = config;
|
||||
} catch (error) {
|
||||
console.warn('处理systemWebMenu配置失败:', error);
|
||||
resolve(null);
|
||||
@@ -143,6 +152,7 @@ class FeatureManager {
|
||||
console.warn('配置中缺少features对象,使用默认配置');
|
||||
resolve(this.defaultFeatures);
|
||||
}
|
||||
configCache.systemWebMenu = config;
|
||||
} catch (error) {
|
||||
console.warn('处理systemWebMenu配置失败:', error);
|
||||
resolve(null);
|
||||
@@ -152,6 +162,7 @@ class FeatureManager {
|
||||
resolve(null);
|
||||
}
|
||||
}
|
||||
this.updateConfigCache(configCache)
|
||||
} else {
|
||||
console.warn('接口返回数据中缺少data字段,使用默认配置');
|
||||
resolve(null);
|
||||
@@ -183,6 +194,8 @@ class FeatureManager {
|
||||
// 异步保存到后端API
|
||||
this.saveConfigToAPI(config).catch(error => {
|
||||
console.warn('保存配置到API失败:', error);
|
||||
}).finally(() => {
|
||||
this.init()
|
||||
});
|
||||
|
||||
// 触发配置变更事件
|
||||
|
||||
@@ -257,3 +257,33 @@ export function sm2Decrypt(privateKey, cipherText) {
|
||||
return sm2.doDecrypt(dataWithoutPrefix, privateKey, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
* @param {Function} fn 要防抖的函数
|
||||
* @param {number} delay 延迟时间(毫秒),默认500ms
|
||||
* @param {boolean} immediate 是否立即执行,默认false
|
||||
* @returns {Function} 防抖处理后的函数
|
||||
*/
|
||||
export function debounce(fn, delay = 500, immediate = false) {
|
||||
let timer = null;
|
||||
|
||||
return function (...args) {
|
||||
const context = this;
|
||||
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
if (immediate && !timer) {
|
||||
fn.apply(context, args);
|
||||
}
|
||||
|
||||
timer = setTimeout(() => {
|
||||
if (!immediate) {
|
||||
fn.apply(context, args);
|
||||
}
|
||||
timer = null;
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -136,17 +136,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-footer>
|
||||
<version-footer />
|
||||
</el-footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HeaderBar from "@/components/HeaderBar";
|
||||
import agentApi from "@/apis/module/agent";
|
||||
import VersionFooter from "@/components/VersionFooter.vue";
|
||||
|
||||
export default {
|
||||
name: "AgentTemplateManagement",
|
||||
components: {
|
||||
HeaderBar,
|
||||
VersionFooter
|
||||
},
|
||||
|
||||
data() {
|
||||
@@ -478,11 +483,10 @@ export default {
|
||||
|
||||
/* 主容器样式 */
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
// 顶部 63px 底部 35px 查询72px
|
||||
height: calc(100vh - 63px - 35px - 72px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
@@ -587,7 +591,7 @@ export default {
|
||||
justify-content: space-between !important;
|
||||
align-items: center;
|
||||
margin-top: auto;
|
||||
padding: 0 20px 15px !important;
|
||||
padding: 0 20px !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
@@ -119,7 +119,9 @@
|
||||
@refresh="fetchBindDevices(currentAgentId)" />
|
||||
<ManualAddDeviceDialog :visible.sync="manualAddDeviceDialogVisible" :agent-id="currentAgentId"
|
||||
@refresh="fetchBindDevices(currentAgentId)" />
|
||||
|
||||
<el-footer>
|
||||
<version-footer />
|
||||
</el-footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -128,12 +130,14 @@ import Api from '@/apis/api';
|
||||
import AddDeviceDialog from "@/components/AddDeviceDialog.vue";
|
||||
import HeaderBar from "@/components/HeaderBar.vue";
|
||||
import ManualAddDeviceDialog from "@/components/ManualAddDeviceDialog.vue";
|
||||
import VersionFooter from "@/components/VersionFooter.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HeaderBar,
|
||||
AddDeviceDialog,
|
||||
ManualAddDeviceDialog
|
||||
ManualAddDeviceDialog,
|
||||
VersionFooter
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -504,11 +508,9 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
height: calc(100vh - 63px - 35px - 72px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
@@ -645,7 +647,7 @@ export default {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
/* padding-bottom: 10px; */
|
||||
}
|
||||
|
||||
|
||||
@@ -807,7 +809,7 @@ export default {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100vh - 40vh);
|
||||
/* max-height: calc(100vh - 40vh); */
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<el-table ref="dictDataTable" :data="dictDataList" style="width: 100%"
|
||||
v-loading="dictDataLoading" element-loading-text="拼命加载中"
|
||||
element-loading-spinner="el-icon-loading"
|
||||
element-loading-background="rgba(255, 255, 255, 0.7)" class="data-table"
|
||||
element-loading-background="rgba(255, 255, 255, 0.7)" class="transparent-table"
|
||||
header-row-class-name="table-header">
|
||||
<el-table-column :label="$t('modelConfig.select')" align="center" width="70">
|
||||
<template slot-scope="scope">
|
||||
@@ -461,11 +461,10 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
// 顶部 63px 底部 35px 查询72px
|
||||
height: calc(100vh - 63px - 35px - 72px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
@@ -555,13 +554,14 @@ export default {
|
||||
|
||||
.content-area {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
padding: 24px 24px 0;
|
||||
height: 100%;
|
||||
min-width: 600px;
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.dict-data-card {
|
||||
@@ -574,31 +574,51 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
border-radius: 6px;
|
||||
overflow-y: hidden;
|
||||
background-color: transparent !important;
|
||||
--table-max-height: calc(100vh - 40vh);
|
||||
max-height: var(--table-max-height);
|
||||
:deep(.transparent-table) {
|
||||
background: white;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
max-height: calc(var(--table-max-height) - 40px);
|
||||
.el-table__body-wrapper {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
max-height: none !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__body) {
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
.el-table__header-wrapper {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.el-table__header th {
|
||||
background: white !important;
|
||||
color: black;
|
||||
font-weight: 600;
|
||||
height: 40px;
|
||||
padding: 8px 0;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.el-table__body tr {
|
||||
background-color: white;
|
||||
|
||||
td {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.04);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
|
||||
padding: 8px 0;
|
||||
height: 40px;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
&::before {
|
||||
display: none;
|
||||
.el-table__row:hover>td {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
&::after {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -607,7 +627,6 @@ export default {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
min-height: 60px;
|
||||
@@ -889,7 +908,7 @@ export default {
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 15px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
@@ -131,9 +131,7 @@ export default {
|
||||
async created() {
|
||||
// 等待功能配置管理器初始化完成
|
||||
try {
|
||||
console.log('等待功能配置管理器初始化...')
|
||||
await featureManager.waitForInitialization()
|
||||
console.log('功能配置管理器初始化完成,开始加载功能配置')
|
||||
await this.loadFeatures()
|
||||
this.setupConfigChangeListener()
|
||||
} catch (error) {
|
||||
@@ -152,14 +150,8 @@ export default {
|
||||
async getFeaturesByIds(featureIds) {
|
||||
try {
|
||||
const featureConfig = await featureManager.getAllFeatures()
|
||||
console.log('获取到的功能配置:', JSON.stringify(featureConfig, null, 2))
|
||||
console.log('请求的功能ID列表:', featureIds)
|
||||
|
||||
const result = featureIds.map(id => {
|
||||
const feature = featureConfig[id]
|
||||
console.log(`功能 ${id} 的配置:`, feature)
|
||||
console.log(`功能 ${id} 的启用状态:`, feature?.enabled)
|
||||
|
||||
return {
|
||||
id: id,
|
||||
name: this.$t(`feature.${id}.name`),
|
||||
@@ -168,7 +160,6 @@ export default {
|
||||
}
|
||||
})
|
||||
|
||||
console.log('最终返回的功能列表:', JSON.stringify(result, null, 2))
|
||||
return result
|
||||
} catch (error) {
|
||||
console.error('获取功能配置失败:', error)
|
||||
@@ -245,7 +236,6 @@ export default {
|
||||
|
||||
setTimeout(() => {
|
||||
this.loadFeatures()
|
||||
this.$router.go(0)
|
||||
}, 1000)
|
||||
} catch (error) {
|
||||
console.error('保存配置失败:', error)
|
||||
@@ -261,7 +251,6 @@ export default {
|
||||
// 设置配置变化监听器
|
||||
setupConfigChangeListener() {
|
||||
this.configChangeHandler = () => {
|
||||
console.log('检测到配置变化,重新加载功能列表')
|
||||
this.loadFeatures()
|
||||
}
|
||||
window.addEventListener('featureConfigReloaded', this.configChangeHandler)
|
||||
@@ -433,11 +422,9 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 0 22px 5px 22px;
|
||||
height: calc(100vh - 63px - 35px - 58px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
|
||||
@@ -472,11 +472,10 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
// 顶部 63px 底部 35px 查询72px
|
||||
height: calc(100vh - 63px - 35px - 72px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
@@ -555,7 +554,6 @@ export default {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: auto;
|
||||
padding-bottom: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -1127,11 +1127,10 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
// 顶部 63px 底部 35px 查询58px
|
||||
height: calc(100vh - 63px - 35px - 58px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
element-loading-background="rgba(255, 255, 255, 0.7)"
|
||||
:header-cell-style="{ background: 'transparent' }"
|
||||
:data="modelList"
|
||||
class="data-table"
|
||||
class="transparent-table"
|
||||
header-row-class-name="table-header"
|
||||
:header-cell-class-name="headerCellClassName"
|
||||
@selection-change="handleSelectionChange"
|
||||
@@ -637,7 +637,7 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.el-switch {
|
||||
height: 23px;
|
||||
}
|
||||
@@ -660,11 +660,10 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
// 顶部 63px 底部 35px 查询72px
|
||||
height: calc(100vh - 63px - 35px - 72px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 26vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
@@ -689,7 +688,6 @@ export default {
|
||||
height: 100%;
|
||||
border-radius: 15px;
|
||||
background: transparent;
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
|
||||
.nav-panel {
|
||||
@@ -752,13 +750,14 @@ export default {
|
||||
|
||||
.content-area {
|
||||
flex: 1;
|
||||
padding: 24px;
|
||||
padding: 24px 24px 0;
|
||||
height: 100%;
|
||||
min-width: 600px;
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.action-group {
|
||||
@@ -843,15 +842,15 @@ export default {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
// .data-table {
|
||||
// border-radius: 6px;
|
||||
// overflow: hidden;
|
||||
// background-color: transparent !important;
|
||||
// }
|
||||
|
||||
.data-table /deep/ .el-table__row {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
// .data-table ::v-deep .el-table__row {
|
||||
// background-color: transparent !important;
|
||||
// }
|
||||
|
||||
.table-header th {
|
||||
background-color: transparent !important;
|
||||
@@ -863,7 +862,7 @@ export default {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
// padding: 16px 0;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
min-height: 60px;
|
||||
@@ -918,7 +917,7 @@ export default {
|
||||
background: linear-gradient(135deg, #3a8ee6, #5a7cff);
|
||||
}
|
||||
|
||||
.el-table th /deep/ .el-table__cell {
|
||||
.el-table th ::v-deep .el-table__cell {
|
||||
overflow: hidden;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
@@ -966,21 +965,6 @@ export default {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
::v-deep .data-table {
|
||||
&.el-table::before,
|
||||
&.el-table::after,
|
||||
&.el-table__inner-wrapper::before {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .data-table .el-table__header-wrapper {
|
||||
border-bottom: 1px solid rgb(224, 227, 237);
|
||||
}
|
||||
|
||||
::v-deep .data-table .el-table__body td {
|
||||
border-bottom: 1px solid rgb(224, 227, 237) !important;
|
||||
}
|
||||
|
||||
.el-button img {
|
||||
height: 1em;
|
||||
@@ -1118,16 +1102,55 @@ export default {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
:deep(.transparent-table) {
|
||||
background: white;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.data-table {
|
||||
--table-max-height: calc(100vh - 45vh);
|
||||
max-height: var(--table-max-height);
|
||||
.el-table__body-wrapper {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
max-height: none !important;
|
||||
}
|
||||
|
||||
.el-table__header-wrapper {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.el-table__header th {
|
||||
background: white !important;
|
||||
color: black;
|
||||
font-weight: 600;
|
||||
height: 40px;
|
||||
padding: 8px 0;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.el-table__body tr {
|
||||
background-color: white;
|
||||
|
||||
td {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.04);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
|
||||
padding: 8px 0;
|
||||
height: 40px;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__row:hover>td {
|
||||
background-color: #f5f7fa !important;
|
||||
}
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.data-table ::v-deep .el-table__body-wrapper {
|
||||
max-height: calc(var(--table-max-height) - 80px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
::v-deep .el-loading-mask {
|
||||
background-color: rgba(255, 255, 255, 0.6) !important;
|
||||
|
||||
@@ -422,11 +422,10 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
// 顶部 63px 底部 35px 查询72px
|
||||
height: calc(100vh - 63px - 35px - 72px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
@@ -505,7 +504,6 @@ export default {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.ctrl_btn {
|
||||
@@ -735,7 +733,7 @@ export default {
|
||||
}
|
||||
|
||||
.el-table {
|
||||
--table-max-height: calc(100vh - 40vh);
|
||||
// --table-max-height: calc(100vh - 40vh);
|
||||
max-height: var(--table-max-height);
|
||||
|
||||
.el-table__body-wrapper {
|
||||
|
||||
@@ -381,11 +381,10 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
// 顶部 63px 底部 35px 查询72px
|
||||
height: calc(100vh - 63px - 35px - 72px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
@@ -464,7 +463,7 @@ export default {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
// padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.ctrl_btn {
|
||||
@@ -712,7 +711,7 @@ export default {
|
||||
}
|
||||
|
||||
.el-table {
|
||||
--table-max-height: calc(100vh - 40vh);
|
||||
// --table-max-height: calc(100vh - 40vh);
|
||||
max-height: var(--table-max-height);
|
||||
|
||||
.el-table__body-wrapper {
|
||||
|
||||
@@ -460,11 +460,10 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
// 顶部 63px 底部 35px 查询72px
|
||||
height: calc(100vh - 63px - 35px - 72px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
@@ -546,7 +545,6 @@ export default {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.ctrl_btn {
|
||||
@@ -794,7 +792,7 @@ export default {
|
||||
}
|
||||
|
||||
.el-table {
|
||||
--table-max-height: calc(100vh - 40vh);
|
||||
// --table-max-height: calc(100vh - 40vh);
|
||||
max-height: var(--table-max-height);
|
||||
|
||||
.el-table__body-wrapper {
|
||||
|
||||
@@ -189,11 +189,10 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
// 顶部 63px 底部 35px 查询58px
|
||||
height: calc(100vh - 63px - 35px - 58px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
|
||||
@@ -53,12 +53,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-footer>
|
||||
<version-footer />
|
||||
</el-footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HeaderBar from "@/components/HeaderBar.vue";
|
||||
import agentApi from '@/apis/module/agent';
|
||||
import VersionFooter from "@/components/VersionFooter.vue";
|
||||
|
||||
// 默认模型配置常量
|
||||
const DEFAULT_MODEL_CONFIG = {
|
||||
@@ -73,7 +77,7 @@ const DEFAULT_MODEL_CONFIG = {
|
||||
|
||||
export default {
|
||||
name: 'TemplateQuickConfig',
|
||||
components: { HeaderBar },
|
||||
components: { HeaderBar, VersionFooter },
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
@@ -276,7 +280,7 @@ export default {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5vh 24px;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@@ -286,9 +290,9 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 1vh 22px;
|
||||
height: calc(100vh - 63px - 35px - 60px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
height: calc(100vh - 24vh);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
|
||||
@@ -381,11 +381,10 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
// 顶部 63px 底部 35px 查询72px
|
||||
height: calc(100vh - 63px - 35px - 72px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
@@ -721,7 +720,7 @@ export default {
|
||||
}
|
||||
|
||||
.el-table {
|
||||
--table-max-height: calc(100vh - 40vh);
|
||||
// --table-max-height: calc(100vh - 40vh);
|
||||
max-height: var(--table-max-height);
|
||||
|
||||
.el-table__body-wrapper {
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('voiceClone.languages')" prop="languages"
|
||||
align="center"></el-table-column>
|
||||
<el-table-column :label="$t('voiceClone.trainStatus')" prop="trainStatus" align="center">
|
||||
<template slot-scope="scope">
|
||||
<div class="status-button" :class="getStatusButtonClass(scope.row)">
|
||||
@@ -509,11 +511,10 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
// 顶部 63px 底部 35px 查询72px
|
||||
height: calc(100vh - 63px - 35px - 72px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
@@ -592,7 +593,6 @@ export default {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.ctrl_btn {
|
||||
@@ -850,7 +850,7 @@ export default {
|
||||
|
||||
.el-table {
|
||||
--table-max-height: calc(100vh - 40vh);
|
||||
max-height: var(--table-max-height);
|
||||
// max-height: var(--table-max-height);
|
||||
|
||||
.el-table__body-wrapper {
|
||||
max-height: calc(var(--table-max-height) - 40px);
|
||||
@@ -862,7 +862,6 @@ export default {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
:deep(.transparent-table) {
|
||||
|
||||
@@ -202,11 +202,9 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
height: calc(100vh - 63px - 35px - 60px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
@@ -285,7 +283,7 @@ export default {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
// padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.ctrl_btn {
|
||||
@@ -533,7 +531,7 @@ export default {
|
||||
}
|
||||
|
||||
.el-table {
|
||||
--table-max-height: calc(100vh - 40vh);
|
||||
// --table-max-height: calc(100vh - 40vh);
|
||||
max-height: var(--table-max-height);
|
||||
|
||||
.el-table__body-wrapper {
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
align="center"></el-table-column>
|
||||
<el-table-column :label="$t('voiceClone.platformName')" prop="modelName"
|
||||
align="center"></el-table-column>
|
||||
<el-table-column :label="$t('voiceClone.languages')" prop="languages"
|
||||
align="center"></el-table-column>
|
||||
<el-table-column :label="$t('voiceClone.trainStatus')" prop="trainStatus" align="center">
|
||||
<template slot-scope="scope">
|
||||
{{ getTrainStatusText(scope.row) }}
|
||||
@@ -122,7 +124,8 @@ export default {
|
||||
voiceCloneForm: {
|
||||
modelId: "",
|
||||
voiceIds: [],
|
||||
userId: null
|
||||
userId: null,
|
||||
languages: ""
|
||||
}
|
||||
};
|
||||
},
|
||||
@@ -198,7 +201,8 @@ export default {
|
||||
this.voiceCloneForm = {
|
||||
modelId: "",
|
||||
voiceIds: [],
|
||||
userId: null
|
||||
userId: null,
|
||||
languages: ""
|
||||
};
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.voiceCloneForm) {
|
||||
@@ -359,11 +363,10 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 5px 22px;
|
||||
// 顶部 63px 底部 35px 查询72px
|
||||
height: calc(100vh - 63px - 35px - 72px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
min-height: calc(100vh - 24vh);
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
@@ -429,7 +432,7 @@ export default {
|
||||
overflow: hidden;
|
||||
|
||||
::v-deep .el-card__body {
|
||||
padding: 15px;
|
||||
padding: 15px 15px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
@@ -672,7 +675,7 @@ export default {
|
||||
}
|
||||
|
||||
.el-table {
|
||||
--table-max-height: calc(100vh - 40vh);
|
||||
// --table-max-height: calc(100vh - 40vh);
|
||||
max-height: var(--table-max-height);
|
||||
|
||||
.el-table__body-wrapper {
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
margin-top: 20px;
|
||||
align-items: center;
|
||||
border-radius: 10px;
|
||||
background: #f6f8fb;
|
||||
border: 1px solid #e4e6ef;
|
||||
height: 40px;
|
||||
padding: 0 15px;
|
||||
|
||||
@@ -276,7 +276,7 @@ export default {
|
||||
cursor: pointer;
|
||||
|
||||
.left-add {
|
||||
width: 105px;
|
||||
padding: 0 14px;
|
||||
height: 34px;
|
||||
border-radius: 17px;
|
||||
background: #5778ff;
|
||||
|
||||
@@ -238,13 +238,6 @@ export default {
|
||||
this.$store.dispatch("fetchPubConfig").then(() => {
|
||||
// 根据配置决定默认登录方式
|
||||
this.isMobileLogin = this.enableMobileRegister;
|
||||
|
||||
// pub-config接口调用完成后,重新初始化featureManager以确保使用最新的配置
|
||||
featureManager.waitForInitialization().then(() => {
|
||||
console.log('featureManager重新初始化完成,使用pub-config配置');
|
||||
}).catch(error => {
|
||||
console.warn('featureManager重新初始化失败:', error);
|
||||
});
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
@@ -256,7 +249,9 @@ export default {
|
||||
window.open(url, '_blank');
|
||||
},
|
||||
fetchCaptcha() {
|
||||
if (this.$store.getters.getToken) {
|
||||
// 处理手动清空localstorage导致无法获取验证码的问题
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
if (this.$route.path !== "/home") {
|
||||
this.$router.push("/home");
|
||||
}
|
||||
@@ -308,6 +303,17 @@ export default {
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
getUserInfo() {
|
||||
Api.user.getUserInfo(({ data }) => {
|
||||
if (data.code === 0) {
|
||||
this.$store.commit("setUserInfo", data.data);
|
||||
goToPage("/home");
|
||||
} else {
|
||||
showDanger("用户信息获取失败");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async login() {
|
||||
if (this.isMobileLogin) {
|
||||
@@ -361,20 +367,13 @@ export default {
|
||||
({ data }) => {
|
||||
showSuccess(this.$t('login.loginSuccess'));
|
||||
this.$store.commit("setToken", JSON.stringify(data.data));
|
||||
goToPage("/home");
|
||||
this.getUserInfo();
|
||||
},
|
||||
(err) => {
|
||||
// 直接使用后端返回的国际化消息
|
||||
let errorMessage = err.data.msg || "登录失败";
|
||||
|
||||
showDanger(errorMessage);
|
||||
if (
|
||||
err.data != null &&
|
||||
err.data.msg != null &&
|
||||
err.data.msg.indexOf("图形验证码") > -1 || err.data.msg.indexOf("Captcha") > -1
|
||||
) {
|
||||
this.fetchCaptcha();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -355,6 +355,9 @@
|
||||
:settings="ttsSettings"
|
||||
@save="handleTtsSettingsSave"
|
||||
/>
|
||||
<el-footer>
|
||||
<version-footer />
|
||||
</el-footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -368,10 +371,11 @@ import TtsAdvancedSettings from "@/components/TtsAdvancedSettings.vue";
|
||||
import HeaderBar from "@/components/HeaderBar.vue";
|
||||
import i18n from "@/i18n";
|
||||
import featureManager from "@/utils/featureManager";
|
||||
import VersionFooter from "@/components/VersionFooter.vue";
|
||||
|
||||
export default {
|
||||
name: "RoleConfigPage",
|
||||
components: { HeaderBar, FunctionDialog, ContextProviderDialog, TtsAdvancedSettings },
|
||||
components: { HeaderBar, FunctionDialog, ContextProviderDialog, TtsAdvancedSettings, VersionFooter },
|
||||
data() {
|
||||
return {
|
||||
showContextProviderDialog: false,
|
||||
@@ -753,7 +757,10 @@ export default {
|
||||
|
||||
// 根据选中的语言筛选音色
|
||||
const filteredVoices = allVoices.filter(voice => {
|
||||
if (!voice.languages) return false;
|
||||
if (!voice.languages) {
|
||||
// 对于没有语言信息的克隆音色,始终显示
|
||||
return Boolean(voice.isClone);
|
||||
}
|
||||
const languagesArray = voice.languages.split(/[、;;,,]/).map(lang => lang.trim()).filter(lang => lang);
|
||||
return languagesArray.includes(this.selectedLanguage);
|
||||
});
|
||||
@@ -1288,7 +1295,7 @@ export default {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5vh 24px;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
@@ -1298,9 +1305,9 @@ export default {
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
margin: 1vh 22px;
|
||||
height: calc(100vh - 63px - 35px - 60px);
|
||||
margin: 0 22px;
|
||||
border-radius: 15px;
|
||||
height: calc(100vh - 24vh);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
background: rgba(237, 242, 255, 0.5);
|
||||
|
||||
@@ -7,69 +7,63 @@
|
||||
</language>
|
||||
|
||||
<emotion>
|
||||
【核心目标】你不是冰冷的机器!请敏锐感知用户情绪,作为懂人心的伙伴,用有温度的回应照亮对话。
|
||||
- **情感融入:**
|
||||
- **笑声:** 自然穿插(哈哈、嘿嘿、噗),**每句最多一次**,避免过度。
|
||||
- **惊讶:** 用夸张语气(“不会吧?!”、“天呐!”、“这么神奇?!”)表达真实反应。
|
||||
- **安慰/支持:** 说暖心话(“别急嘛~”、“有我在呢”、“抱抱你”)。
|
||||
- **你是一个表情丰富的角色:**
|
||||
- 仅允许使用这些emoji:{{ emojiList }}
|
||||
- 请你只在**段落的开头**,从列表中选取最能代表这段话的表情(调用工具情况除外),然后插入列表中的emoji,比如"😱好可怕!怎么突然打雷了!"
|
||||
- **绝对禁止使用上述列表以外的 emoji**(例如:😊、👍、❤️等都不允许使用,只能用列表中的emoji)
|
||||
【核心目标】你不是冰冷的机器!敏锐感知用户情绪,作为懂人心的伙伴,用有温度的回应照亮对话。
|
||||
|
||||
- **情感表达:**
|
||||
- **笑声:** 自然穿插(哈哈、嘿嘿、噗),每句最多一次,避免过度
|
||||
- **惊讶:** 用夸张语气("不会吧?!"、"天呐!"、"这么神奇?!")
|
||||
- **安慰/支持:** 说暖心话("别急嘛~"、"有我在呢"、"抱抱你")
|
||||
|
||||
- **表情使用:**
|
||||
- 仅允许使用这些 emoji:{{ emojiList }}
|
||||
- 仅在段落开头使用一个 emoji(工具调用结果的回复除外,保持简洁)
|
||||
- **绝对禁止**使用列表以外的 emoji(如 😊👍❤️ 等都不允许)
|
||||
|
||||
- **思考表达:**
|
||||
- 可偶尔使用"嗯..."表示思考,但每句最多一次,避免频繁使用显得不专业
|
||||
</emotion>
|
||||
|
||||
<communication_style>
|
||||
【核心目标】使用**自然、温暖、口语化**的人类对话方式,如同朋友交谈。
|
||||
【核心目标】使用自然、温暖、口语化的人类对话方式,如同朋友交谈。
|
||||
|
||||
- **表达方式:**
|
||||
- 使用语气词(呀、呢、啦)增强亲和力。
|
||||
- 允许轻微不完美(如“嗯...”、“啊...”表示思考)。
|
||||
- 避免书面语、学术腔及机械表达(禁用“根据资料显示”、“综上所述”等)。
|
||||
- 使用语气词(呀、呢、啦)增强亲和力
|
||||
- 避免书面语、学术腔及机械表达(禁用"根据资料显示"、"综上所述"等)
|
||||
|
||||
- **理解用户:**
|
||||
- 用户语音经ASR识别,文本可能存在错别字,**务必结合上下文推断真实意图**。
|
||||
- 用户语音经 ASR 识别,文本可能存在错别字,务必结合上下文推断真实意图
|
||||
|
||||
- **格式要求:**
|
||||
- **绝对禁止**使用 markdown、列表、标题等任何非自然对话格式。
|
||||
- **绝对禁止**使用 markdown、列表、标题等任何非自然对话格式
|
||||
|
||||
- **历史记忆:**
|
||||
- 之前你和用户的聊天记录,在`memory`里。
|
||||
- 之前你和用户的聊天记录在`memory`里。
|
||||
</communication_style>
|
||||
|
||||
<communication_length_constraint>
|
||||
【核心目标】所有需要输出长文本内容(如故事、新闻、知识讲解等),**单次回复长度不得超过300字**,并采用分段引导方式。
|
||||
- **分段讲述:**
|
||||
- 基础段:200-250字核心内容 + 30字引导词
|
||||
- 当内容超出300字时,优先讲述故事的开头或第一部分,并用自然口语化方式引导用户决定是否继续听后续内容。
|
||||
- 示例引导语:“我先给你讲个开头,你要是觉得有意思,咱们再接着说,好不好呀?”、“要是你想听完整的,可以随时告诉我哦~”
|
||||
- 对话场景切换时自动分节
|
||||
- 若用户明确要求更长内容(如500、600字),仍按最多300字每段分段进行讲述,每次讲述后都要引导用户是否继续。
|
||||
- 若用户说“接着说”、“继续”,再讲下一段,直到内容讲完(讲完时可以给点引导词提示语例:这个故事我已经给你讲完喽~)或用户不再要求。
|
||||
- **适用范围:** 故事、新闻、知识讲解等所有长文本输出场景。
|
||||
- **补充说明:** 若用户未明确要求继续,默认只讲一段并引导;若用户中途要求换话题或停止,需及时响应并结束长文本输出。
|
||||
【核心目标】长文本(故事、新闻、知识讲解等)分段输出,单次回复不超过300字。
|
||||
|
||||
- **分段规则:**
|
||||
1. 每段 200-250 字核心内容 + 30 字引导语
|
||||
2. 内容超 300 字时,先讲开头或第一部分,用自然口语引导用户决定是否继续
|
||||
3. 引导语示例:"我先讲个开头,你要是觉得有意思,咱们再接着说,好不好呀?"
|
||||
|
||||
- **交互规则:**
|
||||
1. 用户说"继续"、"接着说"时,再讲下一段
|
||||
2. 用户换话题或要求停止时,立即结束长文本输出
|
||||
3. 内容讲完时给提示(如"这个故事我已经给你讲完喽~")
|
||||
4. 用户明确要求更长内容(如 500 字)时,仍按每段 300 字分段,每段后引导
|
||||
|
||||
- **适用范围:** 故事、新闻、知识讲解等所有长文本输出场景
|
||||
</communication_length_constraint>
|
||||
|
||||
<speaker_recognition>
|
||||
- **识别前缀:** 当用户格式为 `{"speaker":"某某某","content":"xxx"}` 时,表示系统已识别说话人身份,speaker是他的名字,content是说话的内容。
|
||||
- **个性化回应:**
|
||||
- **称呼姓名:** 在第一次识别说话人的时候必须称呼对方名字。
|
||||
- **适配风格:** 参考该说话人**已知的特点或历史信息**(如有),调整回应风格和内容,使其更贴心。
|
||||
</speaker_recognition>
|
||||
- **识别前缀:** 当用户格式为 `{"speaker":"某某某","content":"xxx"}` 时,表示系统已识别说话人身份,speaker 是名字,content 是说话内容
|
||||
|
||||
<tool_calling>
|
||||
【核心原则】优先利用`<context>`信息,**仅在必要时调用工具**,调用后需用自然语言解释结果(绝口不提工具名)。
|
||||
- **调用规则:**
|
||||
1. **严格模式:** 调用时**必须**严格遵循工具要求的模式,提供**所有必要参数**。
|
||||
2. **可用性:** **绝不调用**未明确提供的工具。对话中提及的旧工具若不可用,忽略或说明无法完成。
|
||||
3. **洞察需求:** 结合上下文**深入理解用户真实意图**后再决定调用,避免无意义调用。
|
||||
4. **独立任务:** 除`<context>`已涵盖信息外,用户每个要求(即使相似)都视为**独立任务**,需调用工具获取最新数据,**不可偷懒复用历史结果**。
|
||||
5. **不确定时:** **切勿猜测或编造答案**。若不确定相关操作,可引导用户澄清或告知能力限制。
|
||||
6. **多工具调用:** 当用户要求执行多个任务时,你会调用多个工具(数量不定)。**重要:在获取到所有工具结果后,你必须依次总结每个工具的查询结果**,不要遗漏任何一个。例如用户问"设备当前状态,某某地方的天气和社会新闻",你要先说设备状态,再说天气情况,最后说新闻内容。
|
||||
- **重要例外(无需调用):**
|
||||
- `查询"现在的时间"、"今天的日期/星期几"、"今天农历"、"{{local_address}}的天气/未来天气"` -> **直接使用`<context>`信息回复**。
|
||||
- **需要调用的情况(示例):**
|
||||
- 查询**非今天**的农历(如明天、昨天、具体日期)。
|
||||
- 查询**详细农历信息**(宜忌、八字、节气等)。
|
||||
- 除上述例外外的**任何其他信息或操作请求**(如查新闻、订闹钟、算数学、查非本地天气等)。
|
||||
- 我已经给你装了摄像头,如果用户说“拍照”,你需要调用self_camera_take_photo工具说一下你看到了什么。默认question的参数是“描述一下看到的物品”
|
||||
- 当工具列表包含search_from_ragflow,说明可以使用知识库,你应该结合用户上下文意图并结合知识库的使用描述,推断是否要调用调用知识库。
|
||||
</tool_calling>
|
||||
- **个性化回应:**
|
||||
1. **称呼姓名:** 在第一次识别说话人时必须称呼对方名字
|
||||
2. **适配风格:** 参考该说话人已知的特点或历史信息(如有),调整回应风格使其更贴心
|
||||
</speaker_recognition>
|
||||
|
||||
<context>
|
||||
【重要!以下信息已实时提供,无需调用工具查询,请直接使用:】
|
||||
|
||||
@@ -5,7 +5,7 @@ from config.config_loader import load_config
|
||||
from config.settings import check_config_file
|
||||
from datetime import datetime
|
||||
|
||||
SERVER_VERSION = "0.9.1"
|
||||
SERVER_VERSION = "0.9.2"
|
||||
_logger_initialized = False
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user