deotalandAi/apps/frontend/src/views/AddAgent.vue

1397 lines
35 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>