mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-29 05:03:57 +08:00
Merge branch 'main' of https://github.com/xinnan-tech/xiaozhi-esp32-server
This commit is contained in:
@@ -294,7 +294,7 @@ public interface Constant {
|
|||||||
/**
|
/**
|
||||||
* 版本号
|
* 版本号
|
||||||
*/
|
*/
|
||||||
public static final String VERSION = "0.8.5";
|
public static final String VERSION = "0.8.6";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 无效固件URL
|
* 无效固件URL
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package xiaozhi.common.utils;
|
package xiaozhi.common.utils;
|
||||||
|
|
||||||
import cn.hutool.json.JSONObject;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 敏感数据处理工具类
|
* 敏感数据处理工具类
|
||||||
*/
|
*/
|
||||||
@@ -30,7 +31,7 @@ public class SensitiveDataUtils {
|
|||||||
* 隐藏字符串中间部分
|
* 隐藏字符串中间部分
|
||||||
*/
|
*/
|
||||||
public static String maskMiddle(String value) {
|
public static String maskMiddle(String value) {
|
||||||
if (StringUtils.isBlank(value)) {
|
if (StringUtils.isBlank(value) || value.length() == 1) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ public class ModelConfigBodyDTO {
|
|||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
// @Schema(description = "模型类型(Memory/ASR/VAD/LLM/TTS)")
|
@Schema(description = "模型ID,未填写将自动生成")
|
||||||
// private String modelType;
|
private String id;
|
||||||
//
|
|
||||||
@Schema(description = "模型编码(如AliLLM、DoubaoTTS)")
|
@Schema(description = "模型编码(如AliLLM、DoubaoTTS)")
|
||||||
private String modelCode;
|
private String modelCode;
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ package xiaozhi.modules.model.entity;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||||
|
|
||||||
@@ -18,7 +16,6 @@ import lombok.Data;
|
|||||||
@Schema(description = "模型配置表")
|
@Schema(description = "模型配置表")
|
||||||
public class ModelConfigEntity {
|
public class ModelConfigEntity {
|
||||||
|
|
||||||
@TableId(type = IdType.ASSIGN_UUID)
|
|
||||||
@Schema(description = "主键")
|
@Schema(description = "主键")
|
||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
|
|||||||
+7
@@ -11,6 +11,7 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
@@ -252,6 +253,12 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
if (modelConfigBodyDTO == null) {
|
if (modelConfigBodyDTO == null) {
|
||||||
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
|
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
|
||||||
}
|
}
|
||||||
|
if (StringUtils.isBlank(modelConfigBodyDTO.getId())) {
|
||||||
|
// 参照 MP @TableId AutoUUID 策略使用
|
||||||
|
// com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator(UUID.replace("-",""))
|
||||||
|
// 进行分配默认模型ID
|
||||||
|
modelConfigBodyDTO.setId(DefaultIdentifierGenerator.getInstance().nextUUID(ModelConfigEntity.class));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
}</route>
|
}</route>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<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 { clearServerBaseUrlOverride, getEnvBaseUrl, getServerBaseUrlOverride, setServerBaseUrlOverride } from '@/utils'
|
||||||
import { isMp } from '@/utils/platform'
|
import { isMp } from '@/utils/platform'
|
||||||
import { computed, onMounted, reactive, ref } from 'vue'
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
import { useToast } from 'wot-design-uni'
|
import { useToast } from 'wot-design-uni'
|
||||||
import { t, changeLanguage, getSupportedLanguages, getCurrentLanguage } from '@/i18n'
|
|
||||||
import type { Language } from '@/store/lang'
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'SettingsPage',
|
name: 'SettingsPage',
|
||||||
@@ -235,7 +235,7 @@ function showAbout() {
|
|||||||
title: t('settings.aboutApp', { appName: import.meta.env.VITE_APP_TITLE }),
|
title: t('settings.aboutApp', { appName: import.meta.env.VITE_APP_TITLE }),
|
||||||
content: t('settings.aboutContent', {
|
content: t('settings.aboutContent', {
|
||||||
appName: import.meta.env.VITE_APP_TITLE,
|
appName: import.meta.env.VITE_APP_TITLE,
|
||||||
version: '0.8.5'
|
version: '0.8.6'
|
||||||
}),
|
}),
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
confirmText: t('common.confirm'),
|
confirmText: t('common.confirm'),
|
||||||
@@ -283,9 +283,9 @@ onMounted(async () => {
|
|||||||
<view class="mb-[24rpx]">
|
<view class="mb-[24rpx]">
|
||||||
<view class="w-full rounded-[16rpx] border border-[#eeeeee] bg-[#f5f7fb] overflow-hidden">
|
<view class="w-full rounded-[16rpx] border border-[#eeeeee] bg-[#f5f7fb] overflow-hidden">
|
||||||
<wd-input v-model="baseUrlInput" type="text" clearable :maxlength="200"
|
<wd-input v-model="baseUrlInput" type="text" clearable :maxlength="200"
|
||||||
:placeholder="t('settings.enterServerUrl')"
|
:placeholder="t('settings.enterServerUrl')"
|
||||||
custom-class="!border-none !bg-transparent h-[64rpx] px-[24rpx] items-center"
|
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>
|
</view>
|
||||||
<text v-if="urlError" class="mt-[8rpx] block text-[24rpx] text-[#ff4d4f]">
|
<text v-if="urlError" class="mt-[8rpx] block text-[24rpx] text-[#ff4d4f]">
|
||||||
{{ urlError }}
|
{{ urlError }}
|
||||||
@@ -323,11 +323,11 @@ onMounted(async () => {
|
|||||||
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>
|
<view>
|
||||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||||
{{ t('settings.totalCacheSize') }}
|
{{ t('settings.totalCacheSize') }}
|
||||||
</text>
|
</text>
|
||||||
<text class="mt-[4rpx] block text-[24rpx] text-[#9d9ea3]">
|
<text class="mt-[4rpx] block text-[24rpx] text-[#9d9ea3]">
|
||||||
{{ t('settings.appDataSize') }}
|
{{ t('settings.appDataSize') }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="text-[28rpx] text-[#65686f] font-semibold">
|
<text class="text-[28rpx] text-[#65686f] font-semibold">
|
||||||
{{ cacheInfo.storageSize }}
|
{{ cacheInfo.storageSize }}
|
||||||
@@ -339,11 +339,11 @@ onMounted(async () => {
|
|||||||
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>
|
<view>
|
||||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||||
{{ t('settings.cacheClear') }}
|
{{ t('settings.cacheClear') }}
|
||||||
</text>
|
</text>
|
||||||
<text class="mt-[4rpx] block text-[24rpx] text-[#9d9ea3]">
|
<text class="mt-[4rpx] block text-[24rpx] text-[#9d9ea3]">
|
||||||
{{ t('settings.clearAllCache') }}
|
{{ t('settings.clearAllCache') }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<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"
|
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"
|
||||||
@@ -359,8 +359,8 @@ onMounted(async () => {
|
|||||||
<view class="mb-[32rpx]">
|
<view class="mb-[32rpx]">
|
||||||
<view class="mb-[24rpx] flex items-center">
|
<view class="mb-[24rpx] flex items-center">
|
||||||
<text class="text-[32rpx] text-[#232338] font-bold">
|
<text class="text-[32rpx] text-[#232338] font-bold">
|
||||||
{{ t('settings.appInfo') }}
|
{{ t('settings.appInfo') }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||||
@@ -370,11 +370,11 @@ onMounted(async () => {
|
|||||||
@click="showAbout">
|
@click="showAbout">
|
||||||
<view>
|
<view>
|
||||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||||
{{ t('settings.aboutUs') }}
|
{{ t('settings.aboutUs') }}
|
||||||
</text>
|
</text>
|
||||||
<text class="mt-[4rpx] block text-[24rpx] text-[#9d9ea3]">
|
<text class="mt-[4rpx] block text-[24rpx] text-[#9d9ea3]">
|
||||||
{{ t('settings.appVersion') }}
|
{{ t('settings.appVersion') }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<wd-icon name="arrow-right" custom-class="text-[32rpx] text-[#9d9ea3]" />
|
<wd-icon name="arrow-right" custom-class="text-[32rpx] text-[#9d9ea3]" />
|
||||||
</view>
|
</view>
|
||||||
@@ -385,24 +385,26 @@ onMounted(async () => {
|
|||||||
<view class="mb-[32rpx]">
|
<view class="mb-[32rpx]">
|
||||||
<view class="mb-[24rpx] flex items-center">
|
<view class="mb-[24rpx] flex items-center">
|
||||||
<text class="text-[32rpx] text-[#232338] font-bold">
|
<text class="text-[32rpx] text-[#232338] font-bold">
|
||||||
{{ t('settings.languageSettings') }}
|
{{ t('settings.languageSettings') }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
|
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">
|
<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">
|
||||||
<view>
|
<view>
|
||||||
<text class="text-[32rpx] text-[#232338] font-medium">
|
<text class="text-[32rpx] text-[#232338] font-medium">
|
||||||
{{ t('settings.language') }}
|
{{ t('settings.language') }}
|
||||||
</text>
|
</text>
|
||||||
<text class="mt-[4rpx] block text-[24rpx] text-[#9d9ea3]">
|
<text class="mt-[4rpx] block text-[24rpx] text-[#9d9ea3]">
|
||||||
{{ t('settings.selectLanguage') }}
|
{{ t('settings.selectLanguage') }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex items-center">
|
<view class="flex items-center">
|
||||||
<text class="text-[32rpx] text-[#9d9ea3] font-semibold mr-[16rpx]">
|
<text class="text-[32rpx] text-[#9d9ea3] font-semibold mr-[16rpx]">
|
||||||
{{ supportedLanguages.find(lang => lang.code === currentLanguage)?.name }}
|
{{supportedLanguages.find(lang => lang.code === currentLanguage)?.name}}
|
||||||
</text>
|
</text>
|
||||||
<wd-icon name="arrow-right" custom-class="text-[32rpx] text-[#9d9ea3]" />
|
<wd-icon name="arrow-right" custom-class="text-[32rpx] text-[#9d9ea3]" />
|
||||||
</view>
|
</view>
|
||||||
@@ -411,19 +413,11 @@ onMounted(async () => {
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 语言选择弹窗 -->
|
<!-- 语言选择弹窗 -->
|
||||||
<wd-action-sheet
|
<wd-action-sheet v-model="showLanguageSheet" :title="t('settings.selectLanguage')" :close-on-click-modal="true">
|
||||||
v-model="showLanguageSheet"
|
|
||||||
:title="t('settings.selectLanguage')"
|
|
||||||
:close-on-click-modal="true"
|
|
||||||
>
|
|
||||||
<view class="language-sheet">
|
<view class="language-sheet">
|
||||||
<scroll-view scroll-y class="language-list">
|
<scroll-view scroll-y class="language-list">
|
||||||
<view
|
<view v-for="lang in supportedLanguages" :key="lang.code" class="language-item"
|
||||||
v-for="lang in supportedLanguages"
|
@click="handleLanguageChange(lang.code)">
|
||||||
:key="lang.code"
|
|
||||||
class="language-item"
|
|
||||||
@click="handleLanguageChange(lang.code)"
|
|
||||||
>
|
|
||||||
<text class="language-name">
|
<text class="language-name">
|
||||||
{{ lang.name }}
|
{{ lang.name }}
|
||||||
</text>
|
</text>
|
||||||
@@ -446,17 +440,21 @@ onMounted(async () => {
|
|||||||
.language-sheet {
|
.language-sheet {
|
||||||
.language-list {
|
.language-list {
|
||||||
max-height: 50vh;
|
max-height: 50vh;
|
||||||
|
|
||||||
.language-item {
|
.language-item {
|
||||||
padding: 30rpx 0;
|
padding: 30rpx 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
|
||||||
.language-name {
|
.language-name {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: #f5f7fb;
|
background-color: #f5f7fb;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export default {
|
|||||||
addModel(params, callback) {
|
addModel(params, callback) {
|
||||||
const { modelType, provideCode, formData } = params;
|
const { modelType, provideCode, formData } = params;
|
||||||
const postData = {
|
const postData = {
|
||||||
|
id: formData.id,
|
||||||
modelCode: formData.modelCode,
|
modelCode: formData.modelCode,
|
||||||
modelName: formData.modelName,
|
modelName: formData.modelName,
|
||||||
isDefault: formData.isDefault ? 1 : 0,
|
isDefault: formData.isDefault ? 1 : 0,
|
||||||
|
|||||||
@@ -27,6 +27,12 @@
|
|||||||
|
|
||||||
<div style="height: 2px; background: #e9e9e9; margin-bottom: 22px;"></div>
|
<div style="height: 2px; background: #e9e9e9; margin-bottom: 22px;"></div>
|
||||||
<el-form :model="formData" label-width="100px" label-position="left" class="custom-form">
|
<el-form :model="formData" label-width="100px" label-position="left" class="custom-form">
|
||||||
|
<div style="display: flex; gap: 20px; margin-bottom: 0;">
|
||||||
|
<el-form-item :label="$t('modelConfigDialog.modelId')" prop="id" style="flex: 1;">
|
||||||
|
<el-input v-model="formData.id" :placeholder="$t('modelConfigDialog.enterModelId')" class="custom-input-bg"
|
||||||
|
maxlength="32"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
<div style="display: flex; gap: 20px; margin-bottom: 0;">
|
<div style="display: flex; gap: 20px; margin-bottom: 0;">
|
||||||
<el-form-item :label="$t('modelConfigDialog.modelName')" prop="modelName" style="flex: 1;">
|
<el-form-item :label="$t('modelConfigDialog.modelName')" prop="modelName" style="flex: 1;">
|
||||||
<el-input v-model="formData.modelName" :placeholder="$t('modelConfigDialog.enterModelName')"
|
<el-input v-model="formData.modelName" :placeholder="$t('modelConfigDialog.enterModelName')"
|
||||||
@@ -69,16 +75,14 @@
|
|||||||
<div style="height: 2px; background: #e9e9e9; margin-bottom: 22px;"></div>
|
<div style="height: 2px; background: #e9e9e9; margin-bottom: 22px;"></div>
|
||||||
|
|
||||||
<el-form :model="formData.configJson" label-width="auto" label-position="left" class="custom-form">
|
<el-form :model="formData.configJson" label-width="auto" label-position="left" class="custom-form">
|
||||||
<template v-for="(row, rowIndex) in chunkedCallInfoFields">
|
<div v-for="(row, rowIndex) in chunkedCallInfoFields" :key="rowIndex"
|
||||||
<div :key="rowIndex" style="display: flex; gap: 20px; margin-bottom: 0;">
|
style="display: flex; gap: 20px; margin-bottom: 0;">
|
||||||
<el-form-item v-for="field in row" :key="field.prop" :label="field.label" :prop="field.prop"
|
<el-form-item v-for="field in row" :key="field.prop" :label="field.label" :prop="field.prop" style="flex: 1;">
|
||||||
style="flex: 1;">
|
<el-input v-model="formData.configJson[field.prop]" :placeholder="field.placeholder"
|
||||||
<el-input v-model="formData.configJson[field.prop]" :placeholder="field.placeholder"
|
:type="field.type || 'text'" class="custom-input-bg" :show-password="field.type === 'password'">
|
||||||
:type="field.type || 'text'" class="custom-input-bg" :show-password="field.type === 'password'">
|
</el-input>
|
||||||
</el-input>
|
</el-form-item>
|
||||||
</el-form-item>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -107,6 +111,7 @@ export default {
|
|||||||
providerFields: [],
|
providerFields: [],
|
||||||
currentProvider: null,
|
currentProvider: null,
|
||||||
formData: {
|
formData: {
|
||||||
|
id: '',
|
||||||
modelName: '',
|
modelName: '',
|
||||||
modelCode: '',
|
modelCode: '',
|
||||||
supplier: '',
|
supplier: '',
|
||||||
@@ -202,6 +207,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const submitData = {
|
const submitData = {
|
||||||
|
id: this.formData.id || '',
|
||||||
modelName: this.formData.modelName || '',
|
modelName: this.formData.modelName || '',
|
||||||
modelCode: this.formData.modelCode || '',
|
modelCode: this.formData.modelCode || '',
|
||||||
supplier: this.formData.supplier,
|
supplier: this.formData.supplier,
|
||||||
@@ -230,6 +236,7 @@ export default {
|
|||||||
resetForm() {
|
resetForm() {
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
this.formData = {
|
this.formData = {
|
||||||
|
id: '',
|
||||||
modelName: '',
|
modelName: '',
|
||||||
modelCode: '',
|
modelCode: '',
|
||||||
supplier: '',
|
supplier: '',
|
||||||
|
|||||||
@@ -169,12 +169,12 @@
|
|||||||
></i>
|
></i>
|
||||||
</span>
|
</span>
|
||||||
<el-dropdown-menu slot="dropdown">
|
<el-dropdown-menu slot="dropdown">
|
||||||
<el-dropdown-item @click.native="goOtaManagement">
|
|
||||||
{{ $t("header.otaManagement") }}
|
|
||||||
</el-dropdown-item>
|
|
||||||
<el-dropdown-item @click.native="goParamManagement">
|
<el-dropdown-item @click.native="goParamManagement">
|
||||||
{{ $t("header.paramManagement") }}
|
{{ $t("header.paramManagement") }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click.native="goOtaManagement">
|
||||||
|
{{ $t("header.otaManagement") }}
|
||||||
|
</el-dropdown-item>
|
||||||
<el-dropdown-item @click.native="goDictManagement">
|
<el-dropdown-item @click.native="goDictManagement">
|
||||||
{{ $t("header.dictManagement") }}
|
{{ $t("header.dictManagement") }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
|
|||||||
@@ -841,6 +841,8 @@ export default {
|
|||||||
'modelConfigDialog.modelInfo': 'Model Information',
|
'modelConfigDialog.modelInfo': 'Model Information',
|
||||||
'modelConfigDialog.enable': 'Enable',
|
'modelConfigDialog.enable': 'Enable',
|
||||||
'modelConfigDialog.setDefault': 'Set as Default',
|
'modelConfigDialog.setDefault': 'Set as Default',
|
||||||
|
'modelConfigDialog.modelId': 'Model ID',
|
||||||
|
'modelConfigDialog.enterModelId': 'If not filled in, it will be generated automatically',
|
||||||
'modelConfigDialog.modelName': 'Model Name',
|
'modelConfigDialog.modelName': 'Model Name',
|
||||||
'modelConfigDialog.enterModelName': 'Please enter model name',
|
'modelConfigDialog.enterModelName': 'Please enter model name',
|
||||||
'modelConfigDialog.modelCode': 'Model Code',
|
'modelConfigDialog.modelCode': 'Model Code',
|
||||||
|
|||||||
@@ -841,6 +841,8 @@ export default {
|
|||||||
'modelConfigDialog.modelInfo': '模型信息',
|
'modelConfigDialog.modelInfo': '模型信息',
|
||||||
'modelConfigDialog.enable': '是否启用',
|
'modelConfigDialog.enable': '是否启用',
|
||||||
'modelConfigDialog.setDefault': '设为默认',
|
'modelConfigDialog.setDefault': '设为默认',
|
||||||
|
'modelConfigDialog.modelId': '模型ID',
|
||||||
|
'modelConfigDialog.enterModelId': '未填写将自动生成模型ID',
|
||||||
'modelConfigDialog.modelName': '模型名称',
|
'modelConfigDialog.modelName': '模型名称',
|
||||||
'modelConfigDialog.enterModelName': '请输入模型名称',
|
'modelConfigDialog.enterModelName': '请输入模型名称',
|
||||||
'modelConfigDialog.modelCode': '模型编码',
|
'modelConfigDialog.modelCode': '模型编码',
|
||||||
|
|||||||
@@ -841,6 +841,8 @@ export default {
|
|||||||
'modelConfigDialog.modelInfo': '模型信息',
|
'modelConfigDialog.modelInfo': '模型信息',
|
||||||
'modelConfigDialog.enable': '是否啟用',
|
'modelConfigDialog.enable': '是否啟用',
|
||||||
'modelConfigDialog.setDefault': '設為默認',
|
'modelConfigDialog.setDefault': '設為默認',
|
||||||
|
'modelConfigDialog.modelId': '模型ID',
|
||||||
|
'modelConfigDialog.enterModelId': '未填冩將自動生成模型ID',
|
||||||
'modelConfigDialog.modelName': '模型名稱',
|
'modelConfigDialog.modelName': '模型名稱',
|
||||||
'modelConfigDialog.enterModelName': '請輸入模型名稱',
|
'modelConfigDialog.enterModelName': '請輸入模型名稱',
|
||||||
'modelConfigDialog.modelCode': '模型編碼',
|
'modelConfigDialog.modelCode': '模型編碼',
|
||||||
|
|||||||
@@ -162,13 +162,6 @@ const routes = [
|
|||||||
return import('../views/TemplateQuickConfig.vue')
|
return import('../views/TemplateQuickConfig.vue')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/provider-management',
|
|
||||||
name: 'ProviderManagement',
|
|
||||||
component: function () {
|
|
||||||
return import('../views/ProviderManagement.vue')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
base: process.env.VUE_APP_PUBLIC_PATH || '/',
|
base: process.env.VUE_APP_PUBLIC_PATH || '/',
|
||||||
|
|||||||
@@ -471,6 +471,7 @@ export default {
|
|||||||
const id = formData.id;
|
const id = formData.id;
|
||||||
|
|
||||||
if (this.editModelData.duplicateMode) {
|
if (this.editModelData.duplicateMode) {
|
||||||
|
formData.id = "";
|
||||||
Api.model.addModel({ modelType, provideCode, formData }, ({ data }) => {
|
Api.model.addModel({ modelType, provideCode, formData }, ({ data }) => {
|
||||||
if (data.code === 0) {
|
if (data.code === 0) {
|
||||||
this.$message.success(this.$t("modelConfig.duplicateSuccess"));
|
this.$message.success(this.$t("modelConfig.duplicateSuccess"));
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ def get_config_from_api(config):
|
|||||||
"vision_explain": config["server"].get("vision_explain", ""),
|
"vision_explain": config["server"].get("vision_explain", ""),
|
||||||
"auth_key": config["server"].get("auth_key", ""),
|
"auth_key": config["server"].get("auth_key", ""),
|
||||||
}
|
}
|
||||||
|
# 如果服务器没有prompt_template,则从本地配置读取
|
||||||
|
if not config_data.get("prompt_template"):
|
||||||
|
config_data["prompt_template"] = config.get("prompt_template")
|
||||||
return config_data
|
return config_data
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from config.config_loader import load_config
|
|||||||
from config.settings import check_config_file
|
from config.settings import check_config_file
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
SERVER_VERSION = "0.8.5"
|
SERVER_VERSION = "0.8.6"
|
||||||
_logger_initialized = False
|
_logger_initialized = False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,3 +23,5 @@ manager-api:
|
|||||||
url: http://127.0.0.1:8002/xiaozhi
|
url: http://127.0.0.1:8002/xiaozhi
|
||||||
# 你的manager-api的token,就是刚才复制出来的server.secret
|
# 你的manager-api的token,就是刚才复制出来的server.secret
|
||||||
secret: 你的server.secret值
|
secret: 你的server.secret值
|
||||||
|
# 默认系统提示词模板文件
|
||||||
|
prompt_template: agent-base-prompt.txt
|
||||||
@@ -94,37 +94,40 @@ async def sendAudio(conn, audios, frame_duration=60):
|
|||||||
send_delay = conn.config.get("tts_audio_send_delay", -1) / 1000.0
|
send_delay = conn.config.get("tts_audio_send_delay", -1) / 1000.0
|
||||||
|
|
||||||
if isinstance(audios, bytes):
|
if isinstance(audios, bytes):
|
||||||
if conn.client_abort:
|
# 重置流控状态,第一次读取和会话发生转变时
|
||||||
return
|
if not hasattr(conn, "audio_flow_control") or conn.audio_flow_control.get("sentence_id") != conn.sentence_id:
|
||||||
|
|
||||||
conn.last_activity_time = time.time() * 1000
|
|
||||||
|
|
||||||
# 获取或初始化流控状态
|
|
||||||
if not hasattr(conn, "audio_flow_control"):
|
|
||||||
conn.audio_flow_control = {
|
conn.audio_flow_control = {
|
||||||
"last_send_time": 0,
|
"last_send_time": 0,
|
||||||
"packet_count": 0,
|
"packet_count": 0,
|
||||||
"start_time": time.perf_counter(),
|
"start_time": time.perf_counter(),
|
||||||
"sequence": 0, # 添加序列号
|
"sequence": 0, # 添加序列号
|
||||||
|
"sentence_id": conn.sentence_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conn.client_abort:
|
||||||
|
return
|
||||||
|
|
||||||
|
conn.last_activity_time = time.time() * 1000
|
||||||
|
|
||||||
|
# 预缓冲:前5个包直接发送,不做延迟
|
||||||
|
pre_buffer_count = 5
|
||||||
flow_control = conn.audio_flow_control
|
flow_control = conn.audio_flow_control
|
||||||
current_time = time.perf_counter()
|
current_time = time.perf_counter()
|
||||||
|
|
||||||
if send_delay > 0:
|
if flow_control["packet_count"] < pre_buffer_count:
|
||||||
|
# 预缓冲阶段,直接发送不延迟
|
||||||
|
pass
|
||||||
|
elif send_delay > 0:
|
||||||
# 使用固定延迟
|
# 使用固定延迟
|
||||||
await asyncio.sleep(send_delay)
|
await asyncio.sleep(send_delay)
|
||||||
else:
|
else:
|
||||||
# 计算预期发送时间
|
effective_packet = flow_control["packet_count"] - pre_buffer_count
|
||||||
expected_time = flow_control["start_time"] + (
|
expected_time = flow_control["start_time"] + (
|
||||||
flow_control["packet_count"] * frame_duration / 1000
|
effective_packet * frame_duration / 1000
|
||||||
)
|
)
|
||||||
delay = expected_time - current_time
|
delay = expected_time - current_time
|
||||||
if delay > 0:
|
if delay > 0:
|
||||||
await asyncio.sleep(delay)
|
await asyncio.sleep(delay)
|
||||||
else:
|
|
||||||
# 纠正误差
|
|
||||||
flow_control["start_time"] += abs(delay)
|
|
||||||
|
|
||||||
if conn.conn_from_mqtt_gateway:
|
if conn.conn_from_mqtt_gateway:
|
||||||
# 计算时间戳和序列号
|
# 计算时间戳和序列号
|
||||||
@@ -150,7 +153,7 @@ async def sendAudio(conn, audios, frame_duration=60):
|
|||||||
play_position = 0
|
play_position = 0
|
||||||
|
|
||||||
# 执行预缓冲
|
# 执行预缓冲
|
||||||
pre_buffer_frames = min(3, len(audios))
|
pre_buffer_frames = min(5, len(audios))
|
||||||
for i in range(pre_buffer_frames):
|
for i in range(pre_buffer_frames):
|
||||||
if conn.conn_from_mqtt_gateway:
|
if conn.conn_from_mqtt_gateway:
|
||||||
# 计算时间戳和序列号
|
# 计算时间戳和序列号
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ class LLMProvider(LLMProviderBase):
|
|||||||
self.base_url = config.get("base_url")
|
self.base_url = config.get("base_url")
|
||||||
else:
|
else:
|
||||||
self.base_url = config.get("url")
|
self.base_url = config.get("url")
|
||||||
# 增加timeout的配置项,单位为秒
|
|
||||||
timeout = config.get("timeout", 300)
|
timeout = config.get("timeout", 300)
|
||||||
self.timeout = int(timeout) if timeout else 300
|
self.timeout = int(timeout) if timeout else 300
|
||||||
|
|
||||||
@@ -48,8 +47,18 @@ class LLMProvider(LLMProviderBase):
|
|||||||
logger.bind(tag=TAG).error(model_key_msg)
|
logger.bind(tag=TAG).error(model_key_msg)
|
||||||
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url, timeout=httpx.Timeout(self.timeout))
|
self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url, timeout=httpx.Timeout(self.timeout))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def normalize_dialogue(dialogue):
|
||||||
|
"""自动修复 dialogue 中缺失 content 的消息"""
|
||||||
|
for msg in dialogue:
|
||||||
|
if "role" in msg and "content" not in msg:
|
||||||
|
msg["content"] = ""
|
||||||
|
return dialogue
|
||||||
|
|
||||||
def response(self, session_id, dialogue, **kwargs):
|
def response(self, session_id, dialogue, **kwargs):
|
||||||
try:
|
try:
|
||||||
|
dialogue = self.normalize_dialogue(dialogue)
|
||||||
|
|
||||||
responses = self.client.chat.completions.create(
|
responses = self.client.chat.completions.create(
|
||||||
model=self.model_name,
|
model=self.model_name,
|
||||||
messages=dialogue,
|
messages=dialogue,
|
||||||
@@ -65,17 +74,11 @@ class LLMProvider(LLMProviderBase):
|
|||||||
is_active = True
|
is_active = True
|
||||||
for chunk in responses:
|
for chunk in responses:
|
||||||
try:
|
try:
|
||||||
# 检查是否存在有效的choice且content不为空
|
delta = chunk.choices[0].delta if getattr(chunk, "choices", None) else None
|
||||||
delta = (
|
content = getattr(delta, "content", "") if delta else ""
|
||||||
chunk.choices[0].delta
|
|
||||||
if getattr(chunk, "choices", None)
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
content = delta.content if hasattr(delta, "content") else ""
|
|
||||||
except IndexError:
|
except IndexError:
|
||||||
content = ""
|
content = ""
|
||||||
if content:
|
if content:
|
||||||
# 处理标签跨多个chunk的情况
|
|
||||||
if "<think>" in content:
|
if "<think>" in content:
|
||||||
is_active = False
|
is_active = False
|
||||||
content = content.split("<think>")[0]
|
content = content.split("<think>")[0]
|
||||||
@@ -90,17 +93,18 @@ class LLMProvider(LLMProviderBase):
|
|||||||
|
|
||||||
def response_with_functions(self, session_id, dialogue, functions=None):
|
def response_with_functions(self, session_id, dialogue, functions=None):
|
||||||
try:
|
try:
|
||||||
|
dialogue = self.normalize_dialogue(dialogue)
|
||||||
|
|
||||||
stream = self.client.chat.completions.create(
|
stream = self.client.chat.completions.create(
|
||||||
model=self.model_name, messages=dialogue, stream=True, tools=functions
|
model=self.model_name, messages=dialogue, stream=True, tools=functions
|
||||||
)
|
)
|
||||||
|
|
||||||
for chunk in stream:
|
for chunk in stream:
|
||||||
# 检查是否存在有效的choice且content不为空
|
|
||||||
if getattr(chunk, "choices", None):
|
if getattr(chunk, "choices", None):
|
||||||
yield chunk.choices[0].delta.content, chunk.choices[
|
delta = chunk.choices[0].delta
|
||||||
0
|
content = getattr(delta, "content", "")
|
||||||
].delta.tool_calls
|
tool_calls = getattr(delta, "tool_calls", None)
|
||||||
# 存在 CompletionUsage 消息时,生成 Token 消耗 log
|
yield content, tool_calls
|
||||||
elif isinstance(getattr(chunk, "usage", None), CompletionUsage):
|
elif isinstance(getattr(chunk, "usage", None), CompletionUsage):
|
||||||
usage_info = getattr(chunk, "usage", None)
|
usage_info = getattr(chunk, "usage", None)
|
||||||
logger.bind(tag=TAG).info(
|
logger.bind(tag=TAG).info(
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ class PromptManager:
|
|||||||
weather_info=weather_info,
|
weather_info=weather_info,
|
||||||
emojiList=EMOJI_List,
|
emojiList=EMOJI_List,
|
||||||
device_id=device_id,
|
device_id=device_id,
|
||||||
|
client_ip=client_ip,
|
||||||
*args, **kwargs
|
*args, **kwargs
|
||||||
)
|
)
|
||||||
device_cache_key = f"device_prompt:{device_id}"
|
device_cache_key = f"device_prompt:{device_id}"
|
||||||
|
|||||||
Reference in New Issue
Block a user