ai-config-panel.vue 5.82 KB
<template>
  <ConfigPanelShell
    :modules="modules"
    :loading="loading"
    :saving="saving"
    storage-key="aiConfigActiveModule"
    alert-title="通义千问(DashScope)语音识别与文本总结配置;语音与对话可使用不同密钥。"
    alert-type="info"
    @save="$emit('save')"
  >
    <template #speech>
      <el-card shadow="never" class="config-section">
        <div slot="header" class="config-section__header">
          <span>语音识别</span>
          <span class="config-section__tag">Paraformer 异步转写</span>
        </div>
        <p class="config-section__tip">
          录音文件识别专用;留空地址则使用官方默认接口。
          <strong>禁止</strong>填写对话用的 <code>compatible-mode/v1</code>。
        </p>
        <p class="config-section__tip config-section__tip--code">
          默认地址:<code>{{ defaultParaformerSubmitUrl }}</code>
        </p>
        <el-form-item label="语音服务地址" prop="aiSpeechApiUrl">
          <el-input v-model="form.aiSpeechApiUrl" clearable
            placeholder="留空即可;请勿填 compatible-mode/v1(那是对话网关)" />
        </el-form-item>
        <el-form-item label="语音 API Key" prop="aiSpeechApiKey">
          <el-input v-model="form.aiSpeechApiKey" show-password clearable autocomplete="new-password"
            placeholder="语音识别使用的 API Key" />
        </el-form-item>
        <el-form-item label="语音识别模型" prop="aiSpeechModel">
          <el-input v-model="form.aiSpeechModel" clearable
            placeholder="paraformer-v2 或 paraformer-8k-v2" />
        </el-form-item>
        <p class="config-section__tip config-section__tip--inline">
          与百炼「录音文件识别」的 model 一致;留空默认 <code>paraformer-v2</code>。禁止填 qwen / livetranslate / realtime 类模型。
        </p>
      </el-card>
    </template>

    <template #chat>
      <el-card shadow="never" class="config-section">
        <div slot="header" class="config-section__header">
          <span>对话与文本总结</span>
          <span class="config-section__tag">OpenAI 兼容模式</span>
        </div>
        <p class="config-section__tip">用于文案总结等场景,需与模型名称搭配(如兼容模式接入千问)。</p>
        <el-form-item label="对话接口地址" prop="aiChatApiUrl">
          <el-input v-model="form.aiChatApiUrl" clearable
            placeholder="示例:https://dashscope.aliyuncs.com/compatible-mode/v1" />
        </el-form-item>
        <el-form-item label="模型名称" prop="aiChatModel">
          <el-input v-model="form.aiChatModel" clearable placeholder="如 qwen-plus、qwen-turbo" />
        </el-form-item>
        <el-form-item label="文本 API Key" prop="aiChatApiKey">
          <el-input v-model="form.aiChatApiKey" show-password clearable autocomplete="new-password"
            placeholder="可与语音使用同一密钥或单独的子 Key" />
        </el-form-item>
      </el-card>
    </template>

    <template #prompt>
      <el-card shadow="never" class="config-section">
        <div slot="header" class="config-section__header">
          <span>备注归纳提示词</span>
          <span class="config-section__tag">system 角色</span>
        </div>
        <p class="config-section__tip">
          发给千问的 system 角色,用来约束「删掉废话、保留关键」。留空则使用系统内置默认(美业健康师备注风格)。
        </p>
        <el-form-item label="归纳提示词" prop="aiSummarizeSystemPrompt">
          <el-input v-model="form.aiSummarizeSystemPrompt" type="textarea"
            :autosize="{ minRows: 8, maxRows: 20 }"
            placeholder="例如:你是一名资深美业咨询师,请将下方口语转写整理为一句专业、可追溯的工单备注……" />
        </el-form-item>
      </el-card>
    </template>
  </ConfigPanelShell>
</template>

<script>
import ConfigPanelShell from './config-panel-shell.vue'

const MODULES = [
  { key: 'speech', label: '语音识别', desc: 'Paraformer 录音转写', icon: 'el-icon-microphone', iconClass: 'module-icon--speech' },
  { key: 'chat', label: '对话总结', desc: '千问兼容模式接口', icon: 'el-icon-chat-dot-round', iconClass: 'module-icon--chat' },
  { key: 'prompt', label: '归纳提示词', desc: '备注整理 system 提示', icon: 'el-icon-edit-outline', iconClass: 'module-icon--prompt' }
]

export default {
  name: 'AiConfigPanel',
  components: { ConfigPanelShell },
  props: {
    form: { type: Object, required: true },
    loading: { type: Boolean, default: false },
    saving: { type: Boolean, default: false },
    defaultParaformerSubmitUrl: { type: String, default: '' }
  },
  data() {
    return { modules: MODULES }
  }
}
</script>

<style lang="scss" scoped>
.config-section {
  border: 1px solid #ebeef5;

  &__header {
    display: flex;
    align-items: center;
    gap: 8px;
    font-weight: 500;
  }

  &__tag {
    padding: 0 8px;
    height: 22px;
    line-height: 22px;
    border-radius: 4px;
    background: #f4f4f5;
    color: #909399;
    font-size: 12px;
    font-weight: normal;
  }

  &__tip {
    margin: 0 0 12px;
    color: #909399;
    font-size: 13px;
    line-height: 1.6;

    &--inline {
      margin-top: 0;
    }

    &--code {
      margin-top: -4px;
    }

    code {
      padding: 1px 4px;
      border-radius: 3px;
      background: #f4f4f5;
      font-size: 12px;
    }
  }
}

.module-icon--speech { color: #409EFF; }
.module-icon--chat { color: #67C23A; }
.module-icon--prompt { color: #E6A23C; }

.ncc-content-dark .config-section {
  border-color: rgba(255, 255, 255, 0.08);

  &__tag {
    background: rgba(255, 255, 255, 0.08);
    color: rgba(255, 255, 255, 0.55);
  }

  &__tip {
    color: rgba(255, 255, 255, 0.55);

    code {
      background: rgba(255, 255, 255, 0.08);
    }
  }
}
</style>