1397 lines
35 KiB
Vue
1397 lines
35 KiB
Vue
<template>
|
||
<div class="add-agent-page">
|
||
<!-- 页面头部 -->
|
||
<div class="page-header">
|
||
<div class="header-content">
|
||
<el-button @click="goBack" class="back-button">
|
||
<el-icon><ArrowLeft /></el-icon>
|
||
{{ t('common.back') }}
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 页面内容 -->
|
||
<div class="page-content">
|
||
<div class="content-wrapper">
|
||
<el-form
|
||
ref="agentFormRef"
|
||
:model="agentForm"
|
||
:rules="formRules"
|
||
label-width="120px"
|
||
class="agent-form"
|
||
size="large"
|
||
>
|
||
<!-- 智能体基础信息 -->
|
||
<div class="form-section">
|
||
<h3 class="section-title">{{ t('agentTemplate.basicInfo') }}</h3>
|
||
|
||
<!-- 智能体名称 -->
|
||
<el-form-item :label="t('agentManagement.name')" prop="name">
|
||
<el-input
|
||
v-model="agentForm.name"
|
||
:placeholder="t('agentManagement.namePlaceholder')"
|
||
readonly
|
||
class="readonly-input"
|
||
>
|
||
<template #prefix>
|
||
<el-icon><User /></el-icon>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
|
||
<!-- 角色模板选择 -->
|
||
<el-form-item :label="t('agentTemplate.selectTemplate')" prop="template">
|
||
<el-select
|
||
v-model="agentForm.template"
|
||
:placeholder="t('agentTemplate.selectTemplatePlaceholder')"
|
||
class="template-select"
|
||
@change="handleTemplateChange"
|
||
>
|
||
<el-option
|
||
v-for="template in roleTemplates"
|
||
:key="template.id"
|
||
:label="template.name"
|
||
:value="template.id"
|
||
>
|
||
<div class="template-option">
|
||
<div class="template-info">
|
||
<div class="template-name">{{ template.name }}</div>
|
||
<div class="template-desc">{{ template.description }}</div>
|
||
</div>
|
||
</div>
|
||
</el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
</div>
|
||
|
||
<!-- 对话配置 -->
|
||
<div class="form-section">
|
||
<h3 class="section-title">{{ t('agentTemplate.dialogConfig') }}</h3>
|
||
|
||
<!-- 对话语言 -->
|
||
<el-form-item :label="t('agentTemplate.language')" prop="language">
|
||
<el-select
|
||
v-model="agentForm.language"
|
||
:placeholder="t('agentTemplate.languagePlaceholder')"
|
||
class="language-select"
|
||
@change="handleLanguageChange"
|
||
>
|
||
<el-option
|
||
v-for="lang in availableLanguages"
|
||
:key="lang.code"
|
||
:label="lang.name"
|
||
:value="lang.code"
|
||
>
|
||
<div class="language-option">
|
||
<span class="language-flag">{{ lang.flag }}</span>
|
||
<span class="language-name">{{ lang.name }}</span>
|
||
</div>
|
||
</el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<!-- 角色音色 -->
|
||
<el-form-item :label="t('agentTemplate.voice')" prop="voice">
|
||
<el-select
|
||
v-model="agentForm.voice"
|
||
:placeholder="t('agentTemplate.voicePlaceholder')"
|
||
class="voice-select"
|
||
@change="handleVoiceChange"
|
||
>
|
||
<el-option
|
||
v-for="voice in availableVoices"
|
||
:key="voice.id"
|
||
:label="voice.name"
|
||
:value="voice.id"
|
||
>
|
||
<div class="voice-option">
|
||
<div class="voice-info">
|
||
<div class="voice-name">{{ voice.name }}</div>
|
||
</div>
|
||
<el-button
|
||
size="small"
|
||
@click.stop="playVoiceSample(voice)"
|
||
:disabled="playingVoice === voice.id"
|
||
class="voice-sample-btn"
|
||
>
|
||
<el-icon><VideoPlay /></el-icon>
|
||
{{ playingVoice === voice.id ? t('agentTemplate.playing') : t('agentTemplate.listen') }}
|
||
</el-button>
|
||
</div>
|
||
</el-option>
|
||
</el-select>
|
||
|
||
<!-- 音频播放器 -->
|
||
<div v-if="currentAudio" class="audio-player">
|
||
<audio
|
||
ref="audioRef"
|
||
:src="currentAudio"
|
||
@ended="onAudioEnded"
|
||
@timeupdate="onAudioTimeUpdate"
|
||
@loadedmetadata="onAudioLoaded"
|
||
></audio>
|
||
<div class="audio-controls">
|
||
<el-button
|
||
size="small"
|
||
@click="toggleAudioPlay"
|
||
:icon="isPlaying ? VideoPause : VideoPlay"
|
||
>
|
||
{{ isPlaying ? t('agentTemplate.pause') : t('agentTemplate.play') }}
|
||
</el-button>
|
||
<div class="audio-progress">
|
||
<span class="time-display">{{ formatTime(currentTime) }}</span>
|
||
<el-slider
|
||
v-model="audioProgress"
|
||
:max="audioDuration"
|
||
:step="0.1"
|
||
@input="onAudioSeek"
|
||
class="progress-slider"
|
||
/>
|
||
<span class="time-display">{{ formatTime(audioDuration) }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<!-- 语言模型 -->
|
||
<el-form-item :label="t('agentTemplate.model')" prop="model">
|
||
<el-select
|
||
v-model="agentForm.model"
|
||
:placeholder="t('agentTemplate.modelPlaceholder')"
|
||
class="model-select"
|
||
>
|
||
<el-option
|
||
v-for="model in availableModels"
|
||
:key="model.id"
|
||
:label="model.name"
|
||
:value="model.id"
|
||
>
|
||
<div class="model-option">
|
||
<div class="model-name">{{ model.name }}</div>
|
||
<div class="model-desc">{{ model.description }}</div>
|
||
<div class="model-features">
|
||
<el-tag
|
||
v-for="feature in model.features"
|
||
:key="feature"
|
||
size="small"
|
||
class="feature-tag"
|
||
>
|
||
{{ feature }}
|
||
</el-tag>
|
||
</div>
|
||
</div>
|
||
</el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
</div>
|
||
|
||
<!-- 高级配置 -->
|
||
<div class="form-section advanced-config">
|
||
<h3 class="section-title">{{ t('agentTemplate.advancedConfig') }}</h3>
|
||
|
||
<div class="config-content">
|
||
<!-- 角色介绍 -->
|
||
<el-form-item :label="t('agentTemplate.introduction')" prop="introduction" class="introduction-item">
|
||
<div class="introduction-wrapper">
|
||
<el-input
|
||
v-model="agentForm.introduction"
|
||
type="textarea"
|
||
:rows="6"
|
||
:placeholder="t('agentTemplate.introductionPlaceholder')"
|
||
maxlength="500"
|
||
show-word-limit
|
||
class="introduction-textarea"
|
||
/>
|
||
<div class="ai-optimization">
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
@click="optimizeWithAI"
|
||
:loading="isOptimizing"
|
||
:disabled="!agentForm.introduction.trim()"
|
||
class="ai-optimize-btn"
|
||
>
|
||
<el-icon><MagicStick /></el-icon>
|
||
{{ t('agentTemplate.aiOptimize') }}
|
||
</el-button>
|
||
|
||
<!-- AI优化历史 -->
|
||
<div v-if="optimizationHistory.length > 0" class="optimization-history">
|
||
<el-dropdown trigger="click" @command="selectOptimization">
|
||
<el-button size="small" link>
|
||
{{ t('agentTemplate.optimizationHistory') }}
|
||
<el-icon><ArrowDown /></el-icon>
|
||
</el-button>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item
|
||
v-for="(opt, index) in optimizationHistory"
|
||
:key="index"
|
||
:command="index"
|
||
>
|
||
{{ t('agentTemplate.version') }} {{ index + 1 }}
|
||
</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- AI优化结果预览 -->
|
||
<div v-if="optimizationResult" class="optimization-preview">
|
||
<div class="preview-header">
|
||
<h4>{{ t('agentTemplate.optimizationPreview') }}</h4>
|
||
<div class="preview-actions">
|
||
<el-button size="small" @click="acceptOptimization">
|
||
{{ t('agentTemplate.accept') }}
|
||
</el-button>
|
||
<el-button size="small" @click="rejectOptimization">
|
||
{{ t('agentTemplate.reject') }}
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
<div class="preview-content">
|
||
<div class="original-text">
|
||
<strong>{{ t('agentTemplate.original') }}:</strong>
|
||
<p>{{ agentForm.introduction }}</p>
|
||
</div>
|
||
<div class="optimized-text">
|
||
<strong>{{ t('agentTemplate.optimized') }}:</strong>
|
||
<p>{{ optimizationResult }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<!-- 记忆类型 -->
|
||
<el-form-item :label="t('agentTemplate.memoryType')" prop="memoryType" class="memory-item">
|
||
<el-select
|
||
v-model="agentForm.memoryType"
|
||
:placeholder="t('agentTemplate.memoryTypePlaceholder')"
|
||
class="memory-select"
|
||
>
|
||
<el-option
|
||
v-for="memory in memoryTypes"
|
||
:key="memory.id"
|
||
:label="memory.name"
|
||
:value="memory.id"
|
||
>
|
||
<div class="memory-option">
|
||
<div class="memory-info">
|
||
<div class="memory-name">{{ memory.name }}({{ memory.description }})</div>
|
||
</div>
|
||
</div>
|
||
</el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 配置提示 -->
|
||
<div class="form-section">
|
||
<div class="config-notice">
|
||
<el-alert
|
||
:title="t('agentTemplate.restartNotice')"
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
class="restart-alert"
|
||
>
|
||
<template #default>
|
||
<div class="notice-content">
|
||
<p>{{ t('agentTemplate.restartDescription') }}</p>
|
||
<p class="notice-emphasis">{{ t('agentTemplate.restartEmphasis') }}</p>
|
||
</div>
|
||
</template>
|
||
</el-alert>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 表单操作 -->
|
||
<div class="form-actions">
|
||
<el-button size="large" @click="goBack">
|
||
{{ t('common.cancel') }}
|
||
</el-button>
|
||
<el-button
|
||
size="large"
|
||
type="primary"
|
||
@click="saveAgent"
|
||
:loading="isSaving"
|
||
class="save-btn"
|
||
>
|
||
<el-icon><Check /></el-icon>
|
||
{{ t('common.save') }}
|
||
</el-button>
|
||
</div>
|
||
</el-form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 自定义模板对话框 -->
|
||
<el-dialog
|
||
v-model="showCustomTemplateDialog"
|
||
:title="t('agentTemplate.createCustomTitle')"
|
||
width="500px"
|
||
@close="resetCustomTemplate"
|
||
>
|
||
<el-form :model="customTemplate" label-width="100px">
|
||
<el-form-item :label="t('agentTemplate.templateName')">
|
||
<el-input v-model="customTemplate.name" :placeholder="t('agentTemplate.templateNamePlaceholder')" />
|
||
</el-form-item>
|
||
<el-form-item :label="t('agentTemplate.templateDescription')">
|
||
<el-input
|
||
v-model="customTemplate.description"
|
||
type="textarea"
|
||
:rows="3"
|
||
:placeholder="t('agentTemplate.templateDescriptionPlaceholder')"
|
||
/>
|
||
</el-form-item>
|
||
<el-form-item :label="t('agentTemplate.defaultLanguage')">
|
||
<el-select v-model="customTemplate.defaultLanguage" style="width: 100%">
|
||
<el-option
|
||
v-for="lang in availableLanguages"
|
||
:key="lang.code"
|
||
:label="lang.name"
|
||
:value="lang.code"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item :label="t('agentTemplate.defaultVoice')">
|
||
<el-select v-model="customTemplate.defaultVoice" style="width: 100%">
|
||
<el-option
|
||
v-for="voice in availableVoices"
|
||
:key="voice.id"
|
||
:label="voice.name"
|
||
:value="voice.id"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="showCustomTemplateDialog = false">
|
||
{{ t('common.cancel') }}
|
||
</el-button>
|
||
<el-button type="primary" @click="createCustomTemplate">
|
||
{{ t('common.create') }}
|
||
</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
<script setup>
|
||
import { ref, reactive, onMounted, onUnmounted, computed } from 'vue'
|
||
import { useRouter, useRoute } from 'vue-router'
|
||
import { useI18n } from 'vue-i18n'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { optimizePrompt } from '../services/aiService.js'
|
||
import {
|
||
ArrowLeft,
|
||
User,
|
||
Plus,
|
||
VideoPlay,
|
||
VideoPause,
|
||
MagicStick,
|
||
ArrowDown,
|
||
Check,
|
||
Monitor
|
||
} from '@element-plus/icons-vue'
|
||
|
||
// 国际化
|
||
const { t } = useI18n()
|
||
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
|
||
// 响应式数据
|
||
const agentFormRef = ref()
|
||
const audioRef = ref()
|
||
|
||
// 表单数据
|
||
const agentForm = reactive({
|
||
name: '',
|
||
template: '',
|
||
language: 'zh-CN',
|
||
voice: '',
|
||
model: '',
|
||
introduction: '',
|
||
memoryType: 'short-term'
|
||
})
|
||
|
||
// 自定义模板数据
|
||
const showCustomTemplateDialog = ref(false)
|
||
|
||
|
||
// AI优化相关
|
||
const isOptimizing = ref(false)
|
||
const optimizationResult = ref('')
|
||
const optimizationHistory = ref([])
|
||
|
||
// 音频播放相关
|
||
const currentAudio = ref('')
|
||
const playingVoice = ref('')
|
||
const isPlaying = ref(false)
|
||
const currentTime = ref(0)
|
||
const audioDuration = ref(0)
|
||
const audioProgress = ref(0)
|
||
|
||
// 保存状态
|
||
const isSaving = ref(false)
|
||
|
||
// 角色模板数据
|
||
const roleTemplates = ref([
|
||
{
|
||
id: 'taiwan-girlfriend',
|
||
name: '台湾女友',
|
||
description: '温柔体贴,善解人意,喜欢关心和照顾人',
|
||
defaultLanguage: 'zh-CN',
|
||
defaultVoice: 'gentle-female',
|
||
defaultIntroduction: '我是你的台湾女友,温柔体贴,善解人意。我会关心你的生活起居,倾听你的烦恼,分享你的快乐。希望我们能一起度过美好的时光!'
|
||
},
|
||
{
|
||
id: 'kid-brother',
|
||
name: '小孩哥',
|
||
description: '活泼开朗,充满好奇心,喜欢探索和冒险',
|
||
defaultLanguage: 'zh-CN',
|
||
defaultVoice: 'energetic-male',
|
||
defaultIntroduction: '嗨!我是小孩哥!我是一个充满活力和好奇心的男孩,喜欢探索新事物,和你一起冒险!有什么有趣的事情想要一起尝试吗?'
|
||
},
|
||
{
|
||
id: 'professional-assistant',
|
||
name: '专业助手',
|
||
description: '严谨专业,高效可靠,提供专业的建议和帮助',
|
||
defaultLanguage: 'zh-CN',
|
||
defaultVoice: 'professional-female',
|
||
defaultIntroduction: '您好,我是您的专业AI助手。我具备丰富的专业知识,能够为您提供准确、高效的服务和专业的建议。让我帮助您解决各种问题和挑战。'
|
||
},
|
||
{
|
||
id: 'custom',
|
||
name: '自定义模板',
|
||
description: '创建您独特的角色模板',
|
||
defaultLanguage: 'zh-CN',
|
||
defaultVoice: 'gentle-female',
|
||
defaultIntroduction: ''
|
||
}
|
||
])
|
||
|
||
// 可用语言
|
||
const availableLanguages = ref([
|
||
{ code: 'zh-CN', name: '简体中文', flag: '🇨🇳' },
|
||
{ code: 'zh-TW', name: '繁体中文', flag: '🇹🇼' },
|
||
{ code: 'en-US', name: 'English', flag: '🇺🇸' },
|
||
{ code: 'ja-JP', name: '日本語', flag: '🇯🇵' },
|
||
{ code: 'ko-KR', name: '한국어', flag: '🇰🇷' },
|
||
{ code: 'es-ES', name: 'Español', flag: '🇪🇸' },
|
||
{ code: 'fr-FR', name: 'Français', flag: '🇫🇷' },
|
||
{ code: 'de-DE', name: 'Deutsch', flag: '🇩🇪' },
|
||
{ code: 'it-IT', name: 'Italiano', flag: '🇮🇹' },
|
||
{ code: 'pt-PT', name: 'Português', flag: '🇵🇹' },
|
||
{ code: 'ru-RU', name: 'Русский', flag: '🇷🇺' }
|
||
])
|
||
|
||
// 可用音色
|
||
const availableVoices = ref([
|
||
{
|
||
id: 'gentle-female',
|
||
name: '温柔女声',
|
||
description: '柔和温暖的女性声音,适合温柔的角色',
|
||
sampleUrl: '/audio/gentle-female.mp3'
|
||
},
|
||
{
|
||
id: 'energetic-male',
|
||
name: '阳光男声',
|
||
description: '充满活力的男性声音,适合活泼的角色',
|
||
sampleUrl: '/audio/energetic-male.mp3'
|
||
},
|
||
{
|
||
id: 'professional-female',
|
||
name: '专业女声',
|
||
description: '清晰专业的女性声音,适合商务场景',
|
||
sampleUrl: '/audio/professional-female.mp3'
|
||
},
|
||
{
|
||
id: 'warm-male',
|
||
name: '温和男声',
|
||
description: '沉稳温和的男性声音,适合成熟角色',
|
||
sampleUrl: '/audio/warm-male.mp3'
|
||
}
|
||
])
|
||
|
||
// 可用模型
|
||
const availableModels = ref([
|
||
{
|
||
id: 'gpt-4',
|
||
name: 'GPT-4',
|
||
description: '最新的大型语言模型,能力强,响应质量高',
|
||
features: ['多语言', '长文本', '复杂推理', '创意生成']
|
||
},
|
||
{
|
||
id: 'gpt-3.5-turbo',
|
||
name: 'GPT-3.5 Turbo',
|
||
description: '平衡性能与速度,适合大多数应用场景',
|
||
features: ['快速响应', '成本效益', '多场景适用']
|
||
},
|
||
{
|
||
id: 'gemini-pro',
|
||
name: 'Gemini Pro',
|
||
description: 'Google先进的多模态AI模型',
|
||
features: ['多模态', '实时信息', '创意任务', '逻辑推理']
|
||
}
|
||
])
|
||
|
||
// 记忆类型
|
||
const memoryTypes = ref([
|
||
{
|
||
id: 'no-memory',
|
||
name: '无记忆',
|
||
description: '每次对话都是独立的,不保留历史信息'
|
||
},
|
||
{
|
||
id: 'short-term',
|
||
name: '短期记忆',
|
||
description: '保留当前会话的记忆,适合单次对话场景'
|
||
},
|
||
{
|
||
id: 'long-term',
|
||
name: '长期记忆',
|
||
description: '跨会话保留记忆,适合持续对话的AI伴侣'
|
||
}
|
||
])
|
||
|
||
// 表单验证规则
|
||
const formRules = computed(() => ({
|
||
name: [
|
||
{ required: true, message: t('agentTemplate.validation.nameRequired'), trigger: 'blur' }
|
||
],
|
||
template: [
|
||
{ required: true, message: t('agentTemplate.validation.templateRequired'), trigger: 'change' }
|
||
],
|
||
language: [
|
||
{ required: true, message: t('agentTemplate.validation.languageRequired'), trigger: 'change' }
|
||
],
|
||
voice: [
|
||
{ required: true, message: t('agentTemplate.validation.voiceRequired'), trigger: 'change' }
|
||
],
|
||
model: [
|
||
{ required: true, message: t('agentTemplate.validation.modelRequired'), trigger: 'change' }
|
||
],
|
||
introduction: [
|
||
{ required: true, message: t('agentTemplate.validation.introductionRequired'), trigger: 'blur' },
|
||
{ min: 10, max: 500, message: t('agentTemplate.validation.introductionLength'), trigger: 'blur' }
|
||
],
|
||
memoryType: [
|
||
{ required: true, message: t('agentTemplate.validation.memoryTypeRequired'), trigger: 'change' }
|
||
]
|
||
}))
|
||
|
||
// 方法定义
|
||
const goBack = () => {
|
||
router.go(-1)
|
||
}
|
||
|
||
const handleTemplateChange = (templateId) => {
|
||
const template = roleTemplates.value.find(t => t.id === templateId)
|
||
if (template) {
|
||
// 设置模板默认值
|
||
if (template.defaultLanguage) {
|
||
agentForm.language = template.defaultLanguage
|
||
}
|
||
if (template.defaultVoice) {
|
||
agentForm.voice = template.defaultVoice
|
||
}
|
||
if (template.defaultIntroduction) {
|
||
agentForm.introduction = template.defaultIntroduction
|
||
}
|
||
}
|
||
}
|
||
|
||
const handleLanguageChange = (languageCode) => {
|
||
// 根据语言设置优化建议
|
||
console.log('Language changed to:', languageCode)
|
||
}
|
||
|
||
const handleVoiceChange = (voiceId) => {
|
||
// 根据音色调整相关设置
|
||
console.log('Voice changed to:', voiceId)
|
||
}
|
||
|
||
const playVoiceSample = (voice) => {
|
||
if (playingVoice.value === voice.id && currentAudio.value) {
|
||
// 如果正在播放同一个音频,则暂停
|
||
toggleAudioPlay()
|
||
return
|
||
}
|
||
|
||
// 停止当前播放的音频
|
||
if (audioRef.value) {
|
||
audioRef.value.pause()
|
||
audioRef.value.currentTime = 0
|
||
}
|
||
|
||
playingVoice.value = voice.id
|
||
currentAudio.value = voice.sampleUrl
|
||
isPlaying.value = false
|
||
currentTime.value = 0
|
||
audioDuration.value = 0
|
||
audioProgress.value = 0
|
||
|
||
// 自动开始播放
|
||
setTimeout(() => {
|
||
if (audioRef.value) {
|
||
audioRef.value.play()
|
||
isPlaying.value = true
|
||
}
|
||
}, 100)
|
||
}
|
||
|
||
const toggleAudioPlay = () => {
|
||
if (!audioRef.value || !currentAudio.value) return
|
||
|
||
if (isPlaying.value) {
|
||
audioRef.value.pause()
|
||
isPlaying.value = false
|
||
} else {
|
||
audioRef.value.play()
|
||
isPlaying.value = true
|
||
}
|
||
}
|
||
|
||
const onAudioEnded = () => {
|
||
isPlaying.value = false
|
||
currentTime.value = 0
|
||
audioProgress.value = 0
|
||
playingVoice.value = ''
|
||
}
|
||
|
||
const onAudioTimeUpdate = () => {
|
||
if (audioRef.value) {
|
||
currentTime.value = audioRef.value.currentTime
|
||
audioProgress.value = audioRef.value.currentTime
|
||
}
|
||
}
|
||
|
||
const onAudioLoaded = () => {
|
||
if (audioRef.value) {
|
||
audioDuration.value = audioRef.value.duration
|
||
}
|
||
}
|
||
|
||
const onAudioSeek = () => {
|
||
if (audioRef.value) {
|
||
audioRef.value.currentTime = audioProgress.value
|
||
}
|
||
}
|
||
|
||
// AI优化功能
|
||
const optimizeWithAI = async () => {
|
||
if (!agentForm.introduction.trim()) {
|
||
ElMessage.warning('请先输入角色介绍')
|
||
return
|
||
}
|
||
|
||
isOptimizing.value = true
|
||
try {
|
||
console.log('开始AI优化...')
|
||
|
||
// 调用真实的AI优化服务
|
||
const optimizedResult = await optimizePrompt(agentForm.introduction)
|
||
|
||
// 从优化结果中提取相关文本
|
||
optimizationResult.value = optimizedResult.personality || optimizedResult.appearance || JSON.stringify(optimizedResult)
|
||
|
||
// 保存到优化历史
|
||
optimizationHistory.value.unshift({
|
||
timestamp: new Date().toLocaleString(),
|
||
originalText: agentForm.introduction,
|
||
optimizedText: optimizedResult.personality || optimizedResult.appearance || JSON.stringify(optimizedResult),
|
||
optimizedResult: optimizedResult
|
||
})
|
||
|
||
ElMessage.success('AI优化完成!请查看优化结果')
|
||
} catch (error) {
|
||
console.error('AI优化失败:', error)
|
||
ElMessage.error(error.message || 'AI优化失败,请重试')
|
||
} finally {
|
||
isOptimizing.value = false
|
||
}
|
||
}
|
||
|
||
const selectOptimization = (index) => {
|
||
if (optimizationHistory.value[index]) {
|
||
optimizationResult.value = optimizationHistory.value[index].optimizedText
|
||
}
|
||
}
|
||
|
||
const acceptOptimization = () => {
|
||
if (optimizationResult.value) {
|
||
agentForm.introduction = optimizationResult.value
|
||
optimizationResult.value = ''
|
||
ElMessage.success('已应用AI优化结果')
|
||
}
|
||
}
|
||
|
||
const rejectOptimization = () => {
|
||
optimizationResult.value = ''
|
||
ElMessage.info('已取消AI优化结果')
|
||
}
|
||
|
||
// 保存智能体
|
||
const saveAgent = async () => {
|
||
try {
|
||
await validateForm()
|
||
isSaving.value = true
|
||
|
||
// 模拟保存API调用
|
||
await new Promise(resolve => setTimeout(resolve, 1500))
|
||
|
||
ElMessage.success('智能体创建成功!')
|
||
|
||
// 跳转到智能体页面
|
||
router.push('/agent-management')
|
||
} catch (error) {
|
||
if (error.message) {
|
||
ElMessage.error(error.message)
|
||
}
|
||
} finally {
|
||
isSaving.value = false
|
||
}
|
||
}
|
||
|
||
// 表单验证
|
||
const validateForm = async () => {
|
||
return new Promise((resolve, reject) => {
|
||
formRef.value.validate((valid) => {
|
||
if (valid) {
|
||
resolve(true)
|
||
} else {
|
||
reject(new Error('请填写完整的表单信息'))
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
const customTemplate = reactive({
|
||
name: '',
|
||
description: '',
|
||
defaultLanguage: '',
|
||
defaultVoice: ''
|
||
})
|
||
|
||
const resetCustomTemplate = () => {
|
||
customTemplate.name = ''
|
||
customTemplate.description = ''
|
||
customTemplate.defaultLanguage = ''
|
||
customTemplate.defaultVoice = ''
|
||
}
|
||
|
||
const createCustomTemplate = () => {
|
||
if (!customTemplate.name.trim()) {
|
||
ElMessage.warning('请输入模板名称')
|
||
return
|
||
}
|
||
|
||
// 创建新的自定义模板
|
||
const newTemplate = {
|
||
id: `custom-${Date.now()}`,
|
||
name: customTemplate.name,
|
||
description: customTemplate.description || '用户自定义模板',
|
||
icon: 'User',
|
||
defaultLanguage: customTemplate.defaultLanguage,
|
||
defaultVoice: customTemplate.defaultVoice,
|
||
defaultIntroduction: '',
|
||
isCustom: true
|
||
}
|
||
|
||
// 添加到模板列表
|
||
roleTemplates.value.push(newTemplate)
|
||
|
||
// 关闭对话框
|
||
showCustomTemplateDialog.value = false
|
||
resetCustomTemplate()
|
||
|
||
// 自动选择新创建的模板
|
||
agentForm.template = newTemplate.id
|
||
handleTemplateChange(newTemplate.id)
|
||
|
||
ElMessage.success('自定义模板创建成功!')
|
||
}
|
||
|
||
const formatTime = (seconds) => {
|
||
if (!seconds || isNaN(seconds)) return '00:00'
|
||
const mins = Math.floor(seconds / 60)
|
||
const secs = Math.floor(seconds % 60)
|
||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
||
}
|
||
|
||
// 生命周期
|
||
onMounted(() => {
|
||
// 从路由参数获取智能体名称
|
||
const agentName = route.query.name || route.params.name || '新智能体'
|
||
agentForm.name = agentName
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
// 清理音频资源
|
||
if (audioRef.value) {
|
||
audioRef.value.pause()
|
||
audioRef.value.src = ''
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.add-agent-page {
|
||
height: 90vh;
|
||
background: var(--el-bg-color-page);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* 页面头部样式 */
|
||
.page-header {
|
||
position: sticky;
|
||
top:24px;
|
||
z-index: 10;
|
||
padding-left: 24px;
|
||
}
|
||
|
||
.header-content {
|
||
margin: 0 auto;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
}
|
||
|
||
.back-button {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 16px;
|
||
}
|
||
|
||
.page-title {
|
||
margin: 0;
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
text-align: center;
|
||
flex: 1;
|
||
}
|
||
|
||
.header-placeholder {
|
||
width: 80px;
|
||
}
|
||
|
||
/* 页面内容样式 */
|
||
.page-content {
|
||
flex: 1;
|
||
padding: 32px 24px 48px;
|
||
}
|
||
|
||
.content-wrapper {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.agent-form {
|
||
background: var(--el-bg-color);
|
||
border-radius: 16px;
|
||
padding: 32px;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||
border: 1px solid var(--el-border-color-lighter);
|
||
}
|
||
|
||
/* 高级配置区域样式 */
|
||
.advanced-config {
|
||
background: var(--el-fill-color-extra-light);
|
||
border-radius: 12px;
|
||
padding: 24px;
|
||
border: 1px solid var(--el-border-color-lighter);
|
||
}
|
||
|
||
.config-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 32px;
|
||
}
|
||
|
||
.introduction-item,
|
||
.memory-item {
|
||
margin-bottom: 0;
|
||
width: 100%;
|
||
}
|
||
|
||
.introduction-item :deep(.el-form-item__content),
|
||
.memory-item :deep(.el-form-item__content) {
|
||
width: 100%;
|
||
}
|
||
|
||
.introduction-item .el-form-item__label,
|
||
.memory-item .el-form-item__label {
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
|
||
/* 表单区块样式 */
|
||
.form-section {
|
||
margin-bottom: 40px;
|
||
padding-bottom: 32px;
|
||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||
}
|
||
|
||
.form-section:last-child {
|
||
border-bottom: none;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.section-title {
|
||
margin: 0 0 24px;
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.section-title::before {
|
||
content: '';
|
||
width: 4px;
|
||
height: 20px;
|
||
background: var(--el-color-primary);
|
||
border-radius: 2px;
|
||
}
|
||
|
||
/* 输入框样式 */
|
||
.readonly-input {
|
||
background: var(--el-fill-color-lighter);
|
||
}
|
||
|
||
.template-select,
|
||
.language-select,
|
||
.voice-select,
|
||
.model-select,
|
||
.memory-select {
|
||
width: 100%;
|
||
}
|
||
|
||
/* 选项样式 */
|
||
.template-option {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 4px 0;
|
||
}
|
||
|
||
.template-icon {
|
||
color: var(--el-color-primary);
|
||
font-size: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.template-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.template-name {
|
||
font-weight: 500;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
|
||
.template-desc {
|
||
font-size: 12px;
|
||
color: var(--el-text-color-secondary);
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.language-option {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.language-flag {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.language-name {
|
||
flex: 1;
|
||
}
|
||
|
||
.voice-option {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
width: 100%;
|
||
padding: 6px 0;
|
||
}
|
||
|
||
.voice-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
}
|
||
|
||
.voice-name {
|
||
font-weight: 500;
|
||
color: var(--el-text-color-primary);
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.voice-desc {
|
||
font-size: 12px;
|
||
color: var(--el-text-color-secondary);
|
||
margin-top: 2px;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.voice-sample-btn {
|
||
flex-shrink: 0;
|
||
align-self: center;
|
||
height: 28px;
|
||
}
|
||
|
||
.model-option {
|
||
width: 100%;
|
||
}
|
||
|
||
.model-name {
|
||
font-weight: 500;
|
||
color: var(--el-text-color-primary);
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.model-desc {
|
||
font-size: 12px;
|
||
color: var(--el-text-color-secondary);
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.model-features {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 4px;
|
||
}
|
||
|
||
.feature-tag {
|
||
font-size: 11px;
|
||
padding: 2px 6px;
|
||
}
|
||
|
||
/* 自定义模板区域 */
|
||
.custom-template-section {
|
||
margin-top: 8px;
|
||
margin-left: 120px;
|
||
}
|
||
|
||
.custom-template-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 音频播放器样式 */
|
||
.audio-player {
|
||
margin-top: 12px;
|
||
padding: 16px;
|
||
background: var(--el-fill-color-light);
|
||
border-radius: 8px;
|
||
border: 1px solid var(--el-border-color-lighter);
|
||
}
|
||
|
||
.audio-controls {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.audio-progress {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
flex: 1;
|
||
}
|
||
|
||
.time-display {
|
||
font-size: 12px;
|
||
color: var(--el-text-color-secondary);
|
||
white-space: nowrap;
|
||
min-width: 35px;
|
||
}
|
||
|
||
.progress-slider {
|
||
flex: 1;
|
||
}
|
||
|
||
/* 角色介绍区域 */
|
||
.introduction-wrapper {
|
||
position: relative;
|
||
width: 100%;
|
||
}
|
||
|
||
.introduction-textarea {
|
||
width: 100%;
|
||
}
|
||
|
||
.introduction-wrapper :deep(.el-textarea__inner) {
|
||
width: 100% !important;
|
||
}
|
||
|
||
.ai-optimization {
|
||
margin-top: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.ai-optimize-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.optimization-history {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* AI优化预览样式 */
|
||
.optimization-preview {
|
||
margin-top: 16px;
|
||
padding: 20px;
|
||
background: var(--el-fill-color-extra-light);
|
||
border-radius: 12px;
|
||
border: 1px solid var(--el-color-primary-light-7);
|
||
}
|
||
|
||
.preview-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.preview-header h4 {
|
||
margin: 0;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: var(--el-color-primary);
|
||
}
|
||
|
||
.preview-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.preview-content {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 20px;
|
||
}
|
||
|
||
.original-text,
|
||
.optimized-text {
|
||
padding: 16px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.original-text {
|
||
background: var(--el-fill-color-lighter);
|
||
border-left: 4px solid var(--el-color-warning);
|
||
}
|
||
|
||
.optimized-text {
|
||
background: var(--el-color-primary-light-9);
|
||
border-left: 4px solid var(--el-color-primary);
|
||
}
|
||
|
||
.original-text strong,
|
||
.optimized-text strong {
|
||
display: block;
|
||
margin-bottom: 8px;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
|
||
.original-text p,
|
||
.optimized-text p {
|
||
margin: 0;
|
||
line-height: 1.6;
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
|
||
/* 记忆类型样式 */
|
||
.memory-option {
|
||
width: 100%;
|
||
padding: 6px 0;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.memory-info {
|
||
width: 100%;
|
||
}
|
||
|
||
.memory-name {
|
||
font-weight: 500;
|
||
color: var(--el-text-color-primary);
|
||
margin-bottom: 4px;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.memory-desc {
|
||
font-size: 12px;
|
||
color: var(--el-text-color-secondary);
|
||
line-height: 1.2;
|
||
}
|
||
|
||
/* 配置提示样式 */
|
||
.config-notice {
|
||
margin-top: 24px;
|
||
}
|
||
|
||
.restart-alert {
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.notice-content {
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.notice-content p {
|
||
margin: 8px 0;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.notice-emphasis {
|
||
font-weight: 500;
|
||
color: var(--el-color-primary);
|
||
}
|
||
|
||
/* 表单操作按钮 */
|
||
.form-actions {
|
||
margin-top: 40px;
|
||
padding-top: 32px;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 16px;
|
||
}
|
||
|
||
.save-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 12px 24px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 768px) {
|
||
.page-header {
|
||
padding: 12px 16px;
|
||
}
|
||
|
||
.page-content {
|
||
padding: 24px 16px 32px;
|
||
}
|
||
|
||
.agent-form {
|
||
padding: 24px 20px;
|
||
}
|
||
|
||
.page-title {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.header-content {
|
||
gap: 12px;
|
||
}
|
||
|
||
.custom-template-section {
|
||
margin-left: 0;
|
||
}
|
||
|
||
.preview-content {
|
||
grid-template-columns: 1fr;
|
||
gap: 16px;
|
||
}
|
||
|
||
.audio-progress {
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.time-display {
|
||
min-width: auto;
|
||
text-align: center;
|
||
}
|
||
|
||
.form-actions {
|
||
flex-direction: column-reverse;
|
||
gap: 12px;
|
||
}
|
||
|
||
.save-btn,
|
||
.el-button[size="large"] {
|
||
width: 100%;
|
||
justify-content: center;
|
||
}
|
||
|
||
.advanced-config {
|
||
padding: 20px 16px;
|
||
}
|
||
|
||
.config-content {
|
||
gap: 24px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.header-content {
|
||
gap: 8px;
|
||
}
|
||
|
||
.page-title {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.header-placeholder {
|
||
width: 60px;
|
||
}
|
||
|
||
.agent-form {
|
||
padding: 20px 16px;
|
||
}
|
||
|
||
.form-section {
|
||
margin-bottom: 32px;
|
||
padding-bottom: 24px;
|
||
}
|
||
|
||
.preview-header {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 12px;
|
||
}
|
||
|
||
.ai-optimization {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 12px;
|
||
}
|
||
|
||
.advanced-config {
|
||
padding: 16px 12px;
|
||
}
|
||
|
||
.config-content {
|
||
gap: 20px;
|
||
}
|
||
}
|
||
</style> |