|
|
@@ -0,0 +1,890 @@
|
|
|
+<template>
|
|
|
+ <div class="ai-account-form-root">
|
|
|
+ <div class="ai-account-form-scroll">
|
|
|
+ <el-form
|
|
|
+ ref="form"
|
|
|
+ :model="form"
|
|
|
+ :rules="rules"
|
|
|
+ label-width="120px"
|
|
|
+ class="account-form"
|
|
|
+ label-position="top"
|
|
|
+ >
|
|
|
+ <!-- 新增/复制:可选绑定公司 -->
|
|
|
+ <div v-if="!form.id || form.id < 0" class="form-section">
|
|
|
+ <div class="section-title">
|
|
|
+ <i class="el-icon-office-building" />
|
|
|
+ <span>公司绑定</span>
|
|
|
+ </div>
|
|
|
+ <el-form-item label="绑定公司">
|
|
|
+ <el-select
|
|
|
+ v-model="createBindCompanyId"
|
|
|
+ placeholder="可选,新增后绑定到该公司"
|
|
|
+ filterable
|
|
|
+ clearable
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in companyOptions"
|
|
|
+ :key="item.companyId"
|
|
|
+ :label="item.companyName"
|
|
|
+ :value="item.companyId"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 编辑:绑定/解绑公司 -->
|
|
|
+ <div v-if="form.id && form.id > 0" class="form-section">
|
|
|
+ <div class="section-title">
|
|
|
+ <i class="el-icon-office-building" />
|
|
|
+ <span>公司绑定管理</span>
|
|
|
+ </div>
|
|
|
+ <el-form-item label="目标公司">
|
|
|
+ <el-select
|
|
|
+ v-model="bindCompanyId"
|
|
|
+ placeholder="请选择公司"
|
|
|
+ filterable
|
|
|
+ clearable
|
|
|
+ style="width: 100%; max-width: 360px"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in companyOptions"
|
|
|
+ :key="item.companyId"
|
|
|
+ :label="item.companyName"
|
|
|
+ :value="item.companyId"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ plain
|
|
|
+ size="small"
|
|
|
+ style="margin-left: 8px"
|
|
|
+ :disabled="!bindCompanyId"
|
|
|
+ @click="handleBindCompany"
|
|
|
+ >绑定</el-button>
|
|
|
+ <el-button
|
|
|
+ type="danger"
|
|
|
+ plain
|
|
|
+ size="small"
|
|
|
+ :disabled="!bindCompanyId"
|
|
|
+ @click="handleUnbindCompany"
|
|
|
+ >解绑</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 基础信息 -->
|
|
|
+ <div class="form-section">
|
|
|
+ <div class="section-title">
|
|
|
+ <i class="el-icon-setting" />
|
|
|
+ <span>基础信息</span>
|
|
|
+ </div>
|
|
|
+ <el-row :gutter="16">
|
|
|
+ <el-col :xs="24" :sm="24" :md="12">
|
|
|
+ <el-form-item label="模型名称" prop="name" required>
|
|
|
+ <el-input
|
|
|
+ v-model="form.name"
|
|
|
+ placeholder="请输入模型名称"
|
|
|
+ clearable
|
|
|
+ maxlength="128"
|
|
|
+ show-word-limit
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="24" :md="12">
|
|
|
+ <el-form-item label="实现类" prop="providerClassName" required>
|
|
|
+ <el-select
|
|
|
+ v-model="form.providerClassName"
|
|
|
+ placeholder="请选择实现类"
|
|
|
+ filterable
|
|
|
+ clearable
|
|
|
+ style="width: 100%"
|
|
|
+ @change="handleProviderChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in providerOptions"
|
|
|
+ :key="item"
|
|
|
+ :label="item"
|
|
|
+ :value="item"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :xs="24" :sm="24" :md="12">
|
|
|
+ <el-form-item prop="concurrentNum" required>
|
|
|
+ <span slot="label">
|
|
|
+ 模型并发数
|
|
|
+ <el-tooltip
|
|
|
+ content="由系统配置自动带出,如需调整请联系管理员"
|
|
|
+ placement="top"
|
|
|
+ >
|
|
|
+ <i class="el-icon-warning-outline hint-icon" />
|
|
|
+ </el-tooltip>
|
|
|
+ </span>
|
|
|
+ <el-input
|
|
|
+ v-model="form.concurrentNum"
|
|
|
+ placeholder="请输入并发数"
|
|
|
+ disabled
|
|
|
+ @input="handleConcurrentNumInput"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 模型连接与参数 -->
|
|
|
+ <div v-if="dynamicFields.length > 0" class="form-section">
|
|
|
+ <div class="section-title">
|
|
|
+ <i class="el-icon-link" />
|
|
|
+ <span>连接与参数</span>
|
|
|
+ <span class="section-desc">不同实现类需填写的密钥、地址等业务参数</span>
|
|
|
+ </div>
|
|
|
+ <div v-for="field in dynamicFields" :key="field.name">
|
|
|
+ <el-form-item
|
|
|
+ :label="field.label"
|
|
|
+ :prop="'accountJson.' + field.name"
|
|
|
+ :required="field.required"
|
|
|
+ >
|
|
|
+ <!-- 文本框 -->
|
|
|
+ <el-input
|
|
|
+ v-if="field.type === 'input'"
|
|
|
+ v-model="form.accountJson[field.name]"
|
|
|
+ :placeholder="'请输入' + field.label"
|
|
|
+ :disabled="!!field.disableProp"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 文本域 -->
|
|
|
+ <el-input
|
|
|
+ v-else-if="field.type === 'textarea'"
|
|
|
+ v-model="form.accountJson[field.name]"
|
|
|
+ type="textarea"
|
|
|
+ :rows="field.rows || 3"
|
|
|
+ :placeholder="'请输入' + field.label"
|
|
|
+ :disabled="!!field.disableProp"
|
|
|
+ />
|
|
|
+
|
|
|
+ <!-- 下拉框 -->
|
|
|
+ <el-select
|
|
|
+ v-else-if="field.type === 'select'"
|
|
|
+ v-model="form.accountJson[field.name]"
|
|
|
+ :placeholder="'请选择' + field.label"
|
|
|
+ style="width: 100%"
|
|
|
+ filterable
|
|
|
+ clearable
|
|
|
+ :disabled="!!field.disableProp"
|
|
|
+ @change="(val) => handleSelectChange(field.name, val)"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="option in field.options"
|
|
|
+ :key="option.value"
|
|
|
+ :label="option.label"
|
|
|
+ :value="option.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+
|
|
|
+ <!-- 大文本域 -->
|
|
|
+ <el-input
|
|
|
+ v-else-if="field.type === 'large-textarea'"
|
|
|
+ v-model="form.accountJson[field.name]"
|
|
|
+ type="textarea"
|
|
|
+ :rows="14"
|
|
|
+ :placeholder="'请输入' + field.label"
|
|
|
+ class="large-textarea"
|
|
|
+ :disabled="!!field.disableProp"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <!-- Coze的token类型特殊字段 -->
|
|
|
+ <template v-if="field.name === 'tokenType' && form.accountJson.tokenType">
|
|
|
+ <el-form-item
|
|
|
+ v-if="form.accountJson.tokenType === 'oauth'"
|
|
|
+ label="OAuth配置"
|
|
|
+ prop="accountJson.oauthFields"
|
|
|
+ >
|
|
|
+ <el-card class="oauth-card" shadow="never">
|
|
|
+ <el-form-item label="Client ID" prop="accountJson.oauthClientId" required>
|
|
|
+ <el-input v-model="form.accountJson.oauthClientId" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="Private Key" prop="accountJson.oauthPrivateKey" required>
|
|
|
+ <el-input v-model="form.accountJson.oauthPrivateKey" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="Public Key ID" prop="accountJson.oauthPublicKeyId" required>
|
|
|
+ <el-input v-model="form.accountJson.oauthPublicKeyId" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-card>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item
|
|
|
+ v-if="form.accountJson.tokenType === 'pat'"
|
|
|
+ label="PAT Token"
|
|
|
+ prop="accountJson.patToken"
|
|
|
+ required
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="form.accountJson.patToken"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="请输入PAT Token"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 通话与打断 -->
|
|
|
+ <div class="form-section">
|
|
|
+ <div class="section-title">
|
|
|
+ <i class="el-icon-microphone" />
|
|
|
+ <span>通话与打断</span>
|
|
|
+ </div>
|
|
|
+ <el-form-item label="打断开关" prop="interruptFlag" required>
|
|
|
+ <el-radio-group
|
|
|
+ v-model="form.interruptFlag"
|
|
|
+ class="interrupt-radio-group"
|
|
|
+ size="small"
|
|
|
+ @change="onInterruptFlagChange"
|
|
|
+ >
|
|
|
+ <el-radio-button :label="0">不打断</el-radio-button>
|
|
|
+ <el-radio-button :label="1">关键词打断</el-radio-button>
|
|
|
+ <el-radio-button :label="2">有声音就打断</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ <div class="field-hint">
|
|
|
+ 「关键词打断」需配置下方打断词;「有声音即打断」适合对实时性要求高的场景。
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item
|
|
|
+ v-if="form.interruptFlag === 1"
|
|
|
+ label="打断关键词"
|
|
|
+ prop="interruptKeywords"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="form.interruptKeywords"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="请输入打断关键词,多条可用换行或逗号分隔"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item
|
|
|
+ v-if="form.interruptFlag === 1"
|
|
|
+ label="忽略打断关键词"
|
|
|
+ prop="interruptIgnoreKeywords"
|
|
|
+ >
|
|
|
+ <el-input
|
|
|
+ v-model="form.interruptIgnoreKeywords"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="语气词等可在此配置为不触发打断"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="转人工数字按键" prop="transferManualDigit">
|
|
|
+ <el-input
|
|
|
+ v-model="form.transferManualDigit"
|
|
|
+ placeholder="例如:1(单键 0–9)"
|
|
|
+ maxlength="1"
|
|
|
+ clearable
|
|
|
+ style="max-width: 120px"
|
|
|
+ @input="handleTransferManualDigitInput"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 客户意向 -->
|
|
|
+ <div v-if="showIntentionTips" class="form-section">
|
|
|
+ <div class="section-title">
|
|
|
+ <i class="el-icon-chat-dot-round" />
|
|
|
+ <span>客户意向</span>
|
|
|
+ </div>
|
|
|
+ <el-form-item label="客户意向提示词" prop="intentionTips">
|
|
|
+ <el-input
|
|
|
+ v-model="form.intentionTips"
|
|
|
+ type="textarea"
|
|
|
+ :rows="5"
|
|
|
+ placeholder="请输入客户意向提示词"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="drawer-footer-bar">
|
|
|
+ <el-button @click="handleCancel">取 消</el-button>
|
|
|
+ <el-button type="primary" :loading="submitting" @click="handleSubmit">确 定</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import { add, update, getCidConfig, bindCompany, unbindCompany } from '@/api/aicall/account'
|
|
|
+import { all } from '@/api/aicall/kbcat'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: 'AccountForm',
|
|
|
+ props: {
|
|
|
+ providerOptions: { type: Array, default: () => [] },
|
|
|
+ companyOptions: { type: Array, default: () => [] },
|
|
|
+ defaultBindCompanyId: { type: [Number, String], default: undefined },
|
|
|
+ initialData: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({})
|
|
|
+ },
|
|
|
+ errorMsg: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ submitting: false,
|
|
|
+ form: {
|
|
|
+ id: undefined,
|
|
|
+ name: '',
|
|
|
+ providerClassName: '',
|
|
|
+ concurrentNum: '',
|
|
|
+ interruptFlag: 0,
|
|
|
+ interruptKeywords: '',
|
|
|
+ interruptIgnoreKeywords: '呃 哦 哦哦 嗯 嗯嗯 嗯好的 好的 对 对对 是的 明白 啊 这样啊 是这样啊这样的 您好 你好',
|
|
|
+ transferManualDigit: '',
|
|
|
+ intentionTips: '',
|
|
|
+ accountJson: {}
|
|
|
+ },
|
|
|
+ kbCatOptions: [],
|
|
|
+ dynamicFields: [],
|
|
|
+ showIntentionTips: false,
|
|
|
+ cidConf: {},
|
|
|
+ createBindCompanyId: undefined,
|
|
|
+ bindCompanyId: undefined
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ rules() {
|
|
|
+ const baseRules = {
|
|
|
+ name: [
|
|
|
+ { required: true, message: '请输入模型名称', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ providerClassName: [
|
|
|
+ { required: true, message: '请选择实现类', trigger: 'change' }
|
|
|
+ ],
|
|
|
+ concurrentNum: [
|
|
|
+ { required: true, message: '请输入模型并发数', trigger: 'blur' },
|
|
|
+ { pattern: /^\d+$/, message: '请输入数字', trigger: 'blur' },
|
|
|
+ { validator: this.validateConcurrentNum, trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ transferManualDigit: [
|
|
|
+ { pattern: /^[0-9]?$/, message: '请输入单个数字0-9', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ interruptFlag: [
|
|
|
+ { required: true, message: '请选择打断开关', trigger: 'change' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ this.dynamicFields.forEach(field => {
|
|
|
+ if (field.required) {
|
|
|
+ const prop = 'accountJson.' + field.name
|
|
|
+ const isSelect = field.type === 'select'
|
|
|
+ baseRules[prop] = [
|
|
|
+ {
|
|
|
+ required: true,
|
|
|
+ message: isSelect ? `请选择${field.label}` : `请输入${field.label}`,
|
|
|
+ trigger: isSelect ? 'change' : 'blur'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ if (this.form.accountJson.tokenType === 'oauth') {
|
|
|
+ baseRules['accountJson.oauthClientId'] = [
|
|
|
+ { required: true, message: '请输入Client ID', trigger: 'blur' }
|
|
|
+ ]
|
|
|
+ baseRules['accountJson.oauthPrivateKey'] = [
|
|
|
+ { required: true, message: '请输入Private Key', trigger: 'blur' }
|
|
|
+ ]
|
|
|
+ baseRules['accountJson.oauthPublicKeyId'] = [
|
|
|
+ { required: true, message: '请输入Public Key ID', trigger: 'blur' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.form.accountJson.tokenType === 'pat') {
|
|
|
+ baseRules['accountJson.patToken'] = [
|
|
|
+ { required: true, message: '请输入PAT Token', trigger: 'blur' }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ return baseRules
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ initialData: {
|
|
|
+ handler(val) {
|
|
|
+ if (val && Object.keys(val).length > 0) {
|
|
|
+ this.initFormData(val)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ deep: true,
|
|
|
+ immediate: true
|
|
|
+ },
|
|
|
+ errorMsg: {
|
|
|
+ handler(val) {
|
|
|
+ if (val) {
|
|
|
+ this.$alert(val, '错误', {
|
|
|
+ type: 'error',
|
|
|
+ confirmButtonText: '确定'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ immediate: true
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ this.loadKbCatOptions();
|
|
|
+ this.initCidConfData();
|
|
|
+ if (this.defaultBindCompanyId) {
|
|
|
+ this.createBindCompanyId = this.defaultBindCompanyId
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ stripMaskedFields(obj) {
|
|
|
+ if (!obj || typeof obj !== 'object') return
|
|
|
+ Object.keys(obj).forEach(key => {
|
|
|
+ const val = obj[key]
|
|
|
+ if (typeof val === 'string' && val.indexOf('****') !== -1) {
|
|
|
+ delete obj[key]
|
|
|
+ } else if (val && typeof val === 'object') {
|
|
|
+ this.stripMaskedFields(val)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ handleBindCompany() {
|
|
|
+ if (!this.form.id || !this.bindCompanyId) return
|
|
|
+ bindCompany(this.form.id, this.bindCompanyId).then(res => {
|
|
|
+ this.$message.success(res.msg || '绑定成功')
|
|
|
+ })
|
|
|
+ },
|
|
|
+ handleUnbindCompany() {
|
|
|
+ if (!this.form.id || !this.bindCompanyId) return
|
|
|
+ this.$confirm('确认解绑该公司?', '提示', { type: 'warning' }).then(() => {
|
|
|
+ unbindCompany(this.form.id, this.bindCompanyId).then(res => {
|
|
|
+ this.$message.success(res.msg || '解绑成功')
|
|
|
+ })
|
|
|
+ }).catch(() => {})
|
|
|
+ },
|
|
|
+ onInterruptFlagChange() {
|
|
|
+ this.$nextTick(() => {
|
|
|
+ if (this.$refs.form) {
|
|
|
+ this.$refs.form.clearValidate(['interruptKeywords', 'interruptIgnoreKeywords'])
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ initCidConfData() {
|
|
|
+ getCidConfig().then(res => {
|
|
|
+ console.log(JSON.parse(res.data));
|
|
|
+ this.cidConf = JSON.parse(res.data);
|
|
|
+ }).catch(() => {})
|
|
|
+ },
|
|
|
+ initFormData(data) {
|
|
|
+ this.form.id = data.id
|
|
|
+ this.form.name = data.name || ''
|
|
|
+ this.form.providerClassName = data.providerClassName || ''
|
|
|
+ this.form.concurrentNum = (data.concurrentNum === null || data.concurrentNum === undefined || data.concurrentNum === '') ? '0' : String(data.concurrentNum)
|
|
|
+ this.form.interruptFlag = data.interruptFlag || 0
|
|
|
+ this.form.interruptKeywords = data.interruptKeywords || ''
|
|
|
+ this.form.interruptIgnoreKeywords = data.interruptIgnoreKeywords || ''
|
|
|
+ this.form.transferManualDigit = data.transferManualDigit || ''
|
|
|
+ this.form.intentionTips = data.intentionTips || ''
|
|
|
+ try {
|
|
|
+ this.form.accountJson = data.accountJson ?
|
|
|
+ (typeof data.accountJson === 'string' ? JSON.parse(data.accountJson) : data.accountJson)
|
|
|
+ : {}
|
|
|
+ } catch (e) {
|
|
|
+ console.error('解析accountJson失败:', e)
|
|
|
+ this.form.accountJson = {}
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.form.providerClassName) {
|
|
|
+ this.updateDynamicFields(this.form.providerClassName, true)
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ handleConcurrentNumInput(value) {
|
|
|
+ let val = value.replace(/[^0-9]/g, '')
|
|
|
+ val = val.replace(/^0+(\d)/, '$1')
|
|
|
+ if (parseInt(val, 10) > 200) val = '200'
|
|
|
+ this.$set(this.form, 'concurrentNum', val)
|
|
|
+ },
|
|
|
+
|
|
|
+ handleTransferManualDigitInput(value) {
|
|
|
+ let val = value.replace(/[^0-9]/g, '')
|
|
|
+ if (val.length > 1) val = val.substring(0, 1)
|
|
|
+ this.$set(this.form, 'transferManualDigit', val)
|
|
|
+ },
|
|
|
+
|
|
|
+ validateConcurrentNum(rule, value, callback) {
|
|
|
+ const num = parseInt(value, 10)
|
|
|
+ if (isNaN(num) || num < 0 || num > 200) {
|
|
|
+ callback(new Error('请输入0-200之间的整数'))
|
|
|
+ } else {
|
|
|
+ callback()
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ handleProviderChange(val) {
|
|
|
+ this.updateDynamicFields(val)
|
|
|
+ },
|
|
|
+
|
|
|
+ updateDynamicFields(providerClassName, isUpdate) {
|
|
|
+ this.dynamicFields = []
|
|
|
+ this.showIntentionTips = false
|
|
|
+
|
|
|
+ const newAccountJson = {}
|
|
|
+
|
|
|
+ if (['DeepSeekChat', 'ChatGpt4o', 'JiutianChat'].includes(providerClassName)) {
|
|
|
+ this.dynamicFields = [
|
|
|
+ { name: 'serverUrl', label: '服务地址', type: 'input', required: true, disableProp: true },
|
|
|
+ { name: 'apiKey', label: 'apiKey', type: 'input', required: true, disableProp: true },
|
|
|
+ { name: 'modelName', label: '模型名称', type: 'input', required: true, disableProp: true },
|
|
|
+ { name: 'llmTips', label: '大模型提示词', type: 'large-textarea', required: true },
|
|
|
+ { name: 'faqContext', label: 'FAQ上下文', type: 'large-textarea', required: true },
|
|
|
+ { name: 'kbCatId', label: '知识库分类', type: 'select', required: false, options: this.kbCatOptions },
|
|
|
+ { name: 'transferToAgentTips', label: '转人工提示词', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'hangupTips', label: '挂机提示', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'customerNoVoiceTips', label: '客户不说话提示', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'openingRemarks', label: '开场白', type: 'textarea', rows: 3, required: true }
|
|
|
+ ]
|
|
|
+ this.showIntentionTips = true
|
|
|
+ }
|
|
|
+
|
|
|
+ if (['LocalLlmChat'].includes(providerClassName)) {
|
|
|
+ this.dynamicFields = [
|
|
|
+ { name: 'serverUrl', label: '服务地址', type: 'input', required: true },
|
|
|
+ { name: 'modelName', label: '模型名称', type: 'input', required: true },
|
|
|
+ { name: 'transferToAgentTips', label: '转人工提示词', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'hangupTips', label: '挂机提示', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'customerNoVoiceTips', label: '客户不说话提示', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'openingRemarks', label: '开场白', type: 'textarea', rows: 3, required: true }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ if (['LocalNlpChat'].includes(providerClassName)) {
|
|
|
+ this.dynamicFields = [
|
|
|
+ { name: 'serverUrl', label: '服务地址', type: 'input', required: true },
|
|
|
+ { name: 'botId', label: 'botId', type: 'input', required: true },
|
|
|
+ { name: 'transferToAgentTips', label: '转人工提示词', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'hangupTips', label: '挂机提示', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'customerNoVoiceTips', label: '客户不说话提示', type: 'textarea', rows: 3, required: true }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ if (['Coze'].includes(providerClassName)) {
|
|
|
+ this.dynamicFields = [
|
|
|
+ { name: 'serverUrl', label: '服务地址', type: 'input', required: true },
|
|
|
+ { name: 'botId', label: 'botId', type: 'input', required: true },
|
|
|
+ {
|
|
|
+ name: 'tokenType',
|
|
|
+ label: 'Token类型',
|
|
|
+ type: 'select',
|
|
|
+ required: true,
|
|
|
+ options: [
|
|
|
+ { label: 'OAuth', value: 'oauth' },
|
|
|
+ { label: 'PAT', value: 'pat' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ { name: 'transferToAgentTips', label: '转人工提示词', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'hangupTips', label: '挂机提示', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'customerNoVoiceTips', label: '客户不说话提示', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'openingRemarks', label: '开场白', type: 'textarea', rows: 3, required: true }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ if (['MaxKB', 'Dify'].includes(providerClassName)) {
|
|
|
+ this.dynamicFields = [
|
|
|
+ { name: 'serverUrl', label: '服务地址', type: 'input', required: true },
|
|
|
+ { name: 'apiKey', label: 'apiKey', type: 'input', required: true },
|
|
|
+ { name: 'transferToAgentTips', label: '转人工提示词', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'hangupTips', label: '挂机提示', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'customerNoVoiceTips', label: '客户不说话提示', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'openingRemarks', label: '开场白', type: 'textarea', rows: 3, required: true }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ if (['JiutianWorkflow', 'JiutianAgent'].includes(providerClassName)) {
|
|
|
+ this.dynamicFields = [
|
|
|
+ { name: 'serverUrl', label: '服务地址', type: 'input', required: true },
|
|
|
+ { name: 'apiKey', label: 'apiKey', type: 'input', required: true },
|
|
|
+ { name: 'botId', label: 'botId', type: 'input', required: true },
|
|
|
+ { name: 'transferToAgentTips', label: '转人工提示词', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'hangupTips', label: '挂机提示', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'customerNoVoiceTips', label: '客户不说话提示', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'openingRemarks', label: '开场白', type: 'textarea', rows: 3, required: true }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ this.dynamicFields.forEach(field => {
|
|
|
+ if (this.form.accountJson[field.name]) {
|
|
|
+ newAccountJson[field.name] = this.form.accountJson[field.name]
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ this.form.accountJson = {
|
|
|
+ ...newAccountJson,
|
|
|
+ ...this.form.accountJson
|
|
|
+ }
|
|
|
+
|
|
|
+ if (providerClassName === 'DeepSeekChat' && !!this.cidConf && !!!isUpdate) {
|
|
|
+ this.$set(this.form.accountJson, 'serverUrl', this.cidConf.serverAddress);
|
|
|
+ this.$set(this.form.accountJson, 'apiKey', this.cidConf.apiKey);
|
|
|
+ this.$set(this.form.accountJson, 'modelName', this.cidConf.modelName);
|
|
|
+ this.$set(this.form, 'concurrentNum', this.cidConf.concurrency);
|
|
|
+ this.dynamicFields = [
|
|
|
+ { name: 'serverUrl', label: '服务地址', type: 'input', required: true, disableProp: true },
|
|
|
+ { name: 'apiKey', label: 'apiKey', type: 'input', required: true, disableProp: true },
|
|
|
+ { name: 'modelName', label: '模型名称', type: 'input', required: true, disableProp: true },
|
|
|
+ { name: 'llmTips', label: '大模型提示词', type: 'large-textarea', required: true },
|
|
|
+ { name: 'faqContext', label: 'FAQ上下文', type: 'large-textarea', required: true },
|
|
|
+ { name: 'kbCatId', label: '知识库分类', type: 'select', required: false, options: this.kbCatOptions },
|
|
|
+ { name: 'transferToAgentTips', label: '转人工提示词', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'hangupTips', label: '挂机提示', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'customerNoVoiceTips', label: '客户不说话提示', type: 'textarea', rows: 3, required: true },
|
|
|
+ { name: 'openingRemarks', label: '开场白', type: 'textarea', rows: 3, required: true }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ handleSelectChange(fieldName) {
|
|
|
+ if (fieldName === 'tokenType') {
|
|
|
+ delete this.form.accountJson.oauthClientId
|
|
|
+ delete this.form.accountJson.oauthPrivateKey
|
|
|
+ delete this.form.accountJson.oauthPublicKeyId
|
|
|
+ delete this.form.accountJson.patToken
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ async loadKbCatOptions() {
|
|
|
+ try {
|
|
|
+ const response = await all();
|
|
|
+ const list = response?.data ?? [];
|
|
|
+ this.kbCatOptions = Array.isArray(list)
|
|
|
+ ? list.map(item => ({ label: item.cat, value: item.id }))
|
|
|
+ : [];
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载知识库分类失败:', error);
|
|
|
+ this.kbCatOptions = [];
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ handleCancel() {
|
|
|
+ this.$emit('cancel')
|
|
|
+ },
|
|
|
+
|
|
|
+ async handleSubmit() {
|
|
|
+ await this.$refs.form.validate(async (valid) => {
|
|
|
+ if (valid) {
|
|
|
+ this.submitting = true
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (!this.showIntentionTips) {
|
|
|
+ this.form.intentionTips = ''
|
|
|
+ }
|
|
|
+
|
|
|
+ const submitData = {
|
|
|
+ id: this.form.id,
|
|
|
+ name: this.form.name,
|
|
|
+ providerClassName: this.form.providerClassName,
|
|
|
+ concurrentNum: this.form.concurrentNum,
|
|
|
+ interruptFlag: this.form.interruptFlag,
|
|
|
+ interruptKeywords: this.form.interruptKeywords || '',
|
|
|
+ interruptIgnoreKeywords: this.form.interruptIgnoreKeywords || '',
|
|
|
+ transferManualDigit: this.form.transferManualDigit || '',
|
|
|
+ intentionTips: this.form.intentionTips || ''
|
|
|
+ }
|
|
|
+
|
|
|
+ const accountJsonPayload = JSON.parse(JSON.stringify(this.form.accountJson || {}))
|
|
|
+ this.stripMaskedFields(accountJsonPayload)
|
|
|
+
|
|
|
+ if (accountJsonPayload) {
|
|
|
+ Object.keys(accountJsonPayload).forEach(key => {
|
|
|
+ submitData[key] = accountJsonPayload[key]
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ submitData.accountJson = JSON.stringify(accountJsonPayload)
|
|
|
+
|
|
|
+ let response
|
|
|
+ if (submitData.id && submitData.id > 0) {
|
|
|
+ response = await update(submitData)
|
|
|
+ } else {
|
|
|
+ const query = {}
|
|
|
+ if (this.createBindCompanyId) {
|
|
|
+ query.companyId = this.createBindCompanyId
|
|
|
+ }
|
|
|
+ if (!submitData.id || submitData.id >= 0) {
|
|
|
+ delete submitData.id
|
|
|
+ }
|
|
|
+ response = await add(submitData, query)
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$message.success('操作成功!')
|
|
|
+ this.$emit('success', response)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('提交失败:', error)
|
|
|
+ this.$message.error('操作失败')
|
|
|
+ } finally {
|
|
|
+ this.submitting = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.ai-account-form-root {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ height: 100%;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.ai-account-form-scroll {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow-y: auto;
|
|
|
+ overflow-x: hidden;
|
|
|
+ padding: 16px 20px;
|
|
|
+ padding-bottom: 8px;
|
|
|
+ background: #f5f7fa;
|
|
|
+}
|
|
|
+
|
|
|
+.account-form {
|
|
|
+ max-width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.form-section {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 18px 20px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ box-shadow: 0 1px 6px rgba(0, 0, 0, 0.06);
|
|
|
+ border: 1px solid #eef0f3;
|
|
|
+}
|
|
|
+
|
|
|
+.form-section:last-of-type {
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.section-title {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ font-size: 15px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ margin-bottom: 14px;
|
|
|
+ padding-bottom: 12px;
|
|
|
+ border-bottom: 1px solid #f0f2f5;
|
|
|
+}
|
|
|
+
|
|
|
+.section-title i {
|
|
|
+ font-size: 16px;
|
|
|
+ color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+.section-desc {
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 400;
|
|
|
+ color: #909399;
|
|
|
+ width: 100%;
|
|
|
+ margin-left: 24px;
|
|
|
+ margin-bottom: -4px;
|
|
|
+}
|
|
|
+
|
|
|
+.hint-icon {
|
|
|
+ margin-left: 4px;
|
|
|
+ color: #c0c4cc;
|
|
|
+ cursor: help;
|
|
|
+}
|
|
|
+
|
|
|
+.field-hint {
|
|
|
+ margin-top: 8px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+.interrupt-radio-group {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.interrupt-radio-group ::v-deep .el-radio-button__inner {
|
|
|
+ border-radius: 4px !important;
|
|
|
+ border-left: 1px solid #dcdfe6 !important;
|
|
|
+ box-shadow: none !important;
|
|
|
+}
|
|
|
+
|
|
|
+.interrupt-radio-group ::v-deep .el-radio-button:first-child .el-radio-button__inner {
|
|
|
+ border-radius: 4px !important;
|
|
|
+}
|
|
|
+
|
|
|
+.interrupt-radio-group ::v-deep .el-radio-button:last-child .el-radio-button__inner {
|
|
|
+ border-radius: 4px !important;
|
|
|
+}
|
|
|
+
|
|
|
+.drawer-footer-bar {
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 12px 20px;
|
|
|
+ border-top: 1px solid #ebeef5;
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.large-textarea ::v-deep textarea {
|
|
|
+ font-family: inherit;
|
|
|
+}
|
|
|
+
|
|
|
+.oauth-card {
|
|
|
+ width: 100%;
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.oauth-card ::v-deep .el-card__body {
|
|
|
+ padding: 12px 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.account-form ::v-deep .el-form-item {
|
|
|
+ margin-bottom: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.account-form ::v-deep .el-form-item__label {
|
|
|
+ font-weight: 500;
|
|
|
+ color: #606266;
|
|
|
+ padding-bottom: 4px !important;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .ai-account-form-scroll {
|
|
|
+ padding: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-section {
|
|
|
+ padding: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .interrupt-radio-group ::v-deep .el-radio-button {
|
|
|
+ flex: 1 1 auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .interrupt-radio-group ::v-deep .el-radio-button__inner {
|
|
|
+ width: 100%;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|