mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-27 01:23:55 +08:00
update: mobile 编辑源和语速音调页面样式调整
This commit is contained in:
@@ -208,11 +208,8 @@ export function updateAgentTags(agentId: string, data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取所有语言
|
// 获取所有语言
|
||||||
export function getAllLanguage(modelId, voiceName) {
|
export function getAllLanguage(modelId: string) {
|
||||||
const queryParams = new URLSearchParams({
|
return http.Get<{ id: string, name: string, languages: string }[]>(`/models/${modelId}/voices`, {
|
||||||
voiceName: voiceName || '',
|
|
||||||
}).toString()
|
|
||||||
return http.Get(`/models/${modelId}/voices?${queryParams}`, {
|
|
||||||
meta: {
|
meta: {
|
||||||
ignoreAuth: false,
|
ignoreAuth: false,
|
||||||
toast: false,
|
toast: false,
|
||||||
|
|||||||
@@ -48,6 +48,15 @@ export interface AgentDetail {
|
|||||||
ttsRate: number
|
ttsRate: number
|
||||||
ttsPitch: number
|
ttsPitch: number
|
||||||
functions: AgentFunction[]
|
functions: AgentFunction[]
|
||||||
|
contextProviders: Providers[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Providers {
|
||||||
|
url: string
|
||||||
|
headers: Array<{
|
||||||
|
key: string
|
||||||
|
value: string
|
||||||
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentFunction {
|
export interface AgentFunction {
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -42,6 +42,12 @@ const alovaInstance = createAlova({
|
|||||||
statesHook: VueHook,
|
statesHook: VueHook,
|
||||||
|
|
||||||
beforeRequest: onAuthRequired((method) => {
|
beforeRequest: onAuthRequired((method) => {
|
||||||
|
// h5动态获取最新的 baseURL,确保使用用户设置的服务器地址
|
||||||
|
const currentBaseUrl = getEnvBaseUrl()
|
||||||
|
if (currentBaseUrl !== method.baseURL) {
|
||||||
|
method.baseURL = currentBaseUrl
|
||||||
|
}
|
||||||
|
|
||||||
// 检查混合内容错误(HTTPS页面请求HTTP接口)
|
// 检查混合内容错误(HTTPS页面请求HTTP接口)
|
||||||
const currentProtocol = typeof window !== 'undefined' && window.location.protocol
|
const currentProtocol = typeof window !== 'undefined' && window.location.protocol
|
||||||
const requestProtocol = method.baseURL?.split(':')[0]
|
const requestProtocol = method.baseURL?.split(':')[0]
|
||||||
|
|||||||
@@ -2,10 +2,8 @@
|
|||||||
import type { AgentDetail, ModelOption, PluginDefinition, RoleTemplate } from '@/api/agent/types'
|
import type { AgentDetail, ModelOption, PluginDefinition, RoleTemplate } from '@/api/agent/types'
|
||||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
||||||
import { getAgentDetail, getAgentTags, getAllLanguage, getModelOptions, getPluginFunctions, getRoleTemplates, updateAgent, updateAgentTags } from '@/api/agent/agent'
|
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 { t } from '@/i18n'
|
||||||
import { usePluginStore } from '@/store'
|
import { usePluginStore, useProvider, useSpeedPitch } from '@/store'
|
||||||
import { toast } from '@/utils/toast'
|
import { toast } from '@/utils/toast'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -105,29 +103,17 @@ const pickerShow = ref<{
|
|||||||
report: false,
|
report: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const ttsSettings = ref({
|
|
||||||
volume: 0,
|
|
||||||
speed: 0,
|
|
||||||
pitch: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
const allFunctions = ref<PluginDefinition[]>([])
|
const allFunctions = ref<PluginDefinition[]>([])
|
||||||
const dynamicTags = ref([])
|
const dynamicTags = ref([])
|
||||||
const inputValue = ref('')
|
const inputValue = ref('')
|
||||||
const inputVisible = ref(false)
|
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 languageOptions = ref([])
|
||||||
const isVisibleReport = ref(false)
|
const isVisibleReport = ref(false)
|
||||||
|
|
||||||
// 使用插件store
|
// 使用插件store
|
||||||
const pluginStore = usePluginStore()
|
const pluginStore = usePluginStore()
|
||||||
|
const speedPitchStore = useSpeedPitch()
|
||||||
|
const providerStore = useProvider()
|
||||||
|
|
||||||
// tabs
|
// tabs
|
||||||
const tabList = [
|
const tabList = [
|
||||||
@@ -174,27 +160,15 @@ function handleInputConfirm() {
|
|||||||
|
|
||||||
// 打开上下文源编辑弹窗
|
// 打开上下文源编辑弹窗
|
||||||
function openContextProviderDialog() {
|
function openContextProviderDialog() {
|
||||||
showContextProviderDialog.value = true
|
uni.navigateTo({
|
||||||
}
|
url: '/pages/agent/provider',
|
||||||
|
})
|
||||||
// 处理上下文源更新
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRegulate() {
|
function handleRegulate() {
|
||||||
openVoiceSettingsDialog()
|
uni.navigateTo({
|
||||||
|
url: '/pages/agent/speedPitch',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载智能体详情
|
// 加载智能体详情
|
||||||
@@ -211,14 +185,15 @@ async function loadAgentDetail() {
|
|||||||
pluginStore.setCurrentAgentId(agentId.value)
|
pluginStore.setCurrentAgentId(agentId.value)
|
||||||
pluginStore.setCurrentFunctions(detail.functions || [])
|
pluginStore.setCurrentFunctions(detail.functions || [])
|
||||||
|
|
||||||
|
// 更新语速音调
|
||||||
|
speedPitchStore.updateSpeedPitch({
|
||||||
|
ttsVolume: detail.ttsVolume || 0,
|
||||||
|
ttsRate: detail.ttsRate || 0,
|
||||||
|
ttsPitch: detail.ttsPitch || 0,
|
||||||
|
})
|
||||||
|
|
||||||
// 加载上下文配置
|
// 加载上下文配置
|
||||||
currentContextProviders.value = (detail as any).contextProviders || []
|
providerStore.updateProviders(detail.contextProviders || [])
|
||||||
// 加载语音设置
|
|
||||||
voiceSettings.value = {
|
|
||||||
volume: (detail as any).volume || 0,
|
|
||||||
speed: (detail as any).speed || 0,
|
|
||||||
pitch: (detail as any).pitch || 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有TTS模型,加载对应的音色选项
|
// 如果有TTS模型,加载对应的音色选项
|
||||||
if (detail.ttsModelId) {
|
if (detail.ttsModelId) {
|
||||||
@@ -373,17 +348,17 @@ function filterVoicesByLanguage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 同步到ttsSettings(如果值为null,使用0作为显示默认值,但不修改form中的值)
|
// 同步到ttsSettings(如果值为null,使用0作为显示默认值,但不修改form中的值)
|
||||||
ttsSettings.value = {
|
speedPitchStore.updateSpeedPitch({
|
||||||
volume: formData.value.ttsVolume !== null && formData.value.ttsVolume !== undefined ? formData.value.ttsVolume : 0,
|
ttsVolume: 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,
|
ttsRate: 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,
|
ttsPitch: formData.value.ttsPitch !== null && formData.value.ttsPitch !== undefined ? formData.value.ttsPitch : 0,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据语音合成模型加载语言
|
// 根据语音合成模型加载语言
|
||||||
async function fetchAllLanguag(ttsModelId: string) {
|
async function fetchAllLanguag(ttsModelId: string) {
|
||||||
try {
|
try {
|
||||||
const res = await getAllLanguage(ttsModelId, '') as any[]
|
const res = await getAllLanguage(ttsModelId)
|
||||||
// 保存完整的音色信息
|
// 保存完整的音色信息
|
||||||
voiceDetails.value = res.reduce((acc, voice) => {
|
voiceDetails.value = res.reduce((acc, voice) => {
|
||||||
acc[voice.id] = voice
|
acc[voice.id] = voice
|
||||||
@@ -564,8 +539,9 @@ async function saveAgent() {
|
|||||||
// 构建保存数据,包含上下文配置和语音设置
|
// 构建保存数据,包含上下文配置和语音设置
|
||||||
const saveData = {
|
const saveData = {
|
||||||
...formData.value,
|
...formData.value,
|
||||||
|
...speedPitchStore.speedPitch,
|
||||||
ttsLanguage: formData.value.language,
|
ttsLanguage: formData.value.language,
|
||||||
contextProviders: currentContextProviders.value,
|
contextProviders: providerStore.providers,
|
||||||
}
|
}
|
||||||
await updateAgent(agentId.value, saveData)
|
await updateAgent(agentId.value, saveData)
|
||||||
loadAgentDetail()
|
loadAgentDetail()
|
||||||
@@ -726,7 +702,7 @@ onMounted(async () => {
|
|||||||
</text>
|
</text>
|
||||||
<view class="mt-0 flex flex-wrap items-center gap-[12rpx]">
|
<view class="mt-0 flex flex-wrap items-center gap-[12rpx]">
|
||||||
<text class="text-[26rpx] text-[#65686f]">
|
<text class="text-[26rpx] text-[#65686f]">
|
||||||
{{ t('agent.contextProviderSuccess', { count: currentContextProviders.length }) }}
|
{{ t('agent.contextProviderSuccess', { count: providerStore.providers.length }) }}
|
||||||
</text>
|
</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">
|
<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') }}
|
{{ t('agent.contextProviderDocLink') }}
|
||||||
@@ -994,16 +970,6 @@ onMounted(async () => {
|
|||||||
@close="onPickerCancel('report')"
|
@close="onPickerCancel('report')"
|
||||||
@select="({ item }) => onPickerConfirm('report', item.value, item.name)"
|
@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>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -77,11 +77,6 @@ const areaCodeList = computed(() => {
|
|||||||
return configStore.config.mobileAreaList || [{ name: '中国大陆', key: '+86' }]
|
return configStore.config.mobileAreaList || [{ name: '中国大陆', key: '+86' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
// SM2公钥
|
|
||||||
const sm2PublicKey = computed(() => {
|
|
||||||
return configStore.config.sm2PublicKey
|
|
||||||
})
|
|
||||||
|
|
||||||
// 切换登录方式
|
// 切换登录方式
|
||||||
function toggleLoginType() {
|
function toggleLoginType() {
|
||||||
loginType.value = loginType.value === 'username' ? 'mobile' : 'username'
|
loginType.value = loginType.value === 'username' ? 'mobile' : 'username'
|
||||||
@@ -131,8 +126,6 @@ function generateUUID() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let skipReLaunch = false // 全局或组件作用域
|
|
||||||
|
|
||||||
// 跳转至服务端设置页面
|
// 跳转至服务端设置页面
|
||||||
function goToServerSetting() {
|
function goToServerSetting() {
|
||||||
uni.switchTab({
|
uni.switchTab({
|
||||||
@@ -178,7 +171,8 @@ async function handleLogin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查SM2公钥是否配置
|
// 检查SM2公钥是否配置
|
||||||
if (!sm2PublicKey.value) {
|
const sm2PublicKey = configStore.config.sm2PublicKey
|
||||||
|
if (!sm2PublicKey) {
|
||||||
toast.warning(t('sm2.publicKeyNotConfigured'))
|
toast.warning(t('sm2.publicKeyNotConfigured'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -191,7 +185,7 @@ async function handleLogin() {
|
|||||||
try {
|
try {
|
||||||
// 拼接验证码和密码
|
// 拼接验证码和密码
|
||||||
const captchaAndPassword = formData.value.captcha + formData.value.password
|
const captchaAndPassword = formData.value.captcha + formData.value.password
|
||||||
encryptedPassword = sm2Encrypt(sm2PublicKey.value, captchaAndPassword)
|
encryptedPassword = sm2Encrypt(sm2PublicKey, captchaAndPassword)
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('密码加密失败:', error)
|
console.error('密码加密失败:', error)
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
<route lang="jsonc" type="page">{
|
<route lang="jsonc" type="page">
|
||||||
|
{
|
||||||
"layout": "default",
|
"layout": "default",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "设置",
|
"navigationBarTitleText": "设置",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
}</route>
|
}
|
||||||
|
</route>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { changeLanguage, getCurrentLanguage, getSupportedLanguages, t } from '@/i18n'
|
|
||||||
import type { Language } from '@/store/lang'
|
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 { computed, onMounted, reactive, ref } from 'vue'
|
||||||
import { useToast } from 'wot-design-uni'
|
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({
|
defineOptions({
|
||||||
name: 'SettingsPage',
|
name: 'SettingsPage',
|
||||||
@@ -27,6 +30,8 @@ const cacheInfo = reactive({
|
|||||||
dataCache: '0MB',
|
dataCache: '0MB',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
// 服务端地址设置
|
// 服务端地址设置
|
||||||
const baseUrlInput = ref('')
|
const baseUrlInput = ref('')
|
||||||
const urlError = ref('')
|
const urlError = ref('')
|
||||||
@@ -81,19 +86,21 @@ async function testServerBaseUrl() {
|
|||||||
const response = await uni.request({
|
const response = await uni.request({
|
||||||
url: `${baseUrlInput.value}/api/ping`,
|
url: `${baseUrlInput.value}/api/ping`,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
timeout: 3000
|
timeout: 3000,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
return true
|
return true
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
toast.error({
|
toast.error({
|
||||||
msg: t('message.invalidAddress'),
|
msg: t('message.invalidAddress'),
|
||||||
duration: 3000,
|
duration: 3000,
|
||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} catch (error) {
|
}
|
||||||
|
catch (error) {
|
||||||
console.error('测试服务端地址失败:', error)
|
console.error('测试服务端地址失败:', error)
|
||||||
toast.error({
|
toast.error({
|
||||||
msg: t('message.invalidAddress'),
|
msg: t('message.invalidAddress'),
|
||||||
@@ -116,6 +123,20 @@ async function saveServerBaseUrl() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
setServerBaseUrlOverride(baseUrlInput.value)
|
setServerBaseUrlOverride(baseUrlInput.value)
|
||||||
|
// 处理config缓存无法更新的问题
|
||||||
|
uni.request({
|
||||||
|
url: `${getEnvBaseUrl()}/user/pub-config`,
|
||||||
|
method: 'GET',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
configStore.setConfig(res.data.data)
|
||||||
|
uni.setStorageSync('config', res.data.data.sm2PubKey)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('获取SM2公钥失败:', err)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// 切换请求地址后清空所有缓存
|
// 切换请求地址后清空所有缓存
|
||||||
clearAllCacheAfterUrlChange()
|
clearAllCacheAfterUrlChange()
|
||||||
|
|||||||
@@ -15,5 +15,7 @@ export default store
|
|||||||
|
|
||||||
export * from './config'
|
export * from './config'
|
||||||
export * from './plugin'
|
export * from './plugin'
|
||||||
|
export * from './provider'
|
||||||
|
export * from './speedPitch'
|
||||||
// 模块统一导出
|
// 模块统一导出
|
||||||
export * from './user'
|
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) }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user