780 lines
26 KiB
Vue
780 lines
26 KiB
Vue
<template>
|
||
<div class="prompt-config-container">
|
||
<el-card class="box-card">
|
||
<div slot="header" class="clearfix">
|
||
<span class="card-title">
|
||
<i class="el-icon-setting"></i>
|
||
DS提示词模板配置
|
||
</span>
|
||
<el-button
|
||
style="float: right; padding: 3px 0"
|
||
type="text"
|
||
@click="showHelp = !showHelp">
|
||
{{ showHelp ? '隐藏帮助' : '显示帮助' }}
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 帮助说明 -->
|
||
<el-collapse-transition>
|
||
<div v-show="showHelp" class="help-section">
|
||
<el-alert
|
||
title="使用说明"
|
||
type="info"
|
||
:closable="false"
|
||
show-icon>
|
||
<div slot="default">
|
||
<p><strong>功能说明:</strong></p>
|
||
<ul>
|
||
<li>配置 DS 侧提示词与闲鱼手动文案模板,存储在 Redis,修改后立即生效</li>
|
||
<li>DS 相关模板支持占位符:%s 用于替换实际内容</li>
|
||
<li>闲鱼模板为纯文本或一条 Java 正则(标题清洗),无占位符</li>
|
||
<li>未在 Redis 中保存过时,文本框会回显当前内置默认全文,便于对照修改;保存后即写入 Redis</li>
|
||
<li>删除模板将清空 Redis 并再次回显内置默认</li>
|
||
</ul>
|
||
<p><strong>模板类型:</strong></p>
|
||
<ul>
|
||
<li><strong>关键词提取模板</strong>:用于提取商品关键词,占位符:%s = 商品名称</li>
|
||
<li><strong>小红书文案模板</strong>:用于生成小红书风格文案,占位符:%s = 商品名称,%s = 价格信息,%s = 关键词</li>
|
||
<li><strong>抖音文案模板</strong>:用于生成抖音风格文案,占位符:%s = 商品名称,%s = 价格信息,%s = 关键词</li>
|
||
<li><strong>通用文案模板</strong>:用于生成通用风格文案,占位符:%s = 商品名称,%s = 价格信息,%s = 关键词</li>
|
||
<li><strong>闲鱼·正文基础</strong>:「一键代下」「教你下单」两版共用的正文说明(在标题/型号行之后)</li>
|
||
<li><strong>闲鱼·教你下单尾部</strong>:接在「更新日期」后的附加说明</li>
|
||
<li><strong>闲鱼·标题清洗正则</strong>:从标题、型号备注中删除匹配片段(须为合法 Java 正则)</li>
|
||
</ul>
|
||
</div>
|
||
</el-alert>
|
||
</div>
|
||
</el-collapse-transition>
|
||
|
||
<!-- 模板列表 -->
|
||
<div class="template-list">
|
||
<el-tabs v-model="activeTab" type="border-card">
|
||
<el-tab-pane
|
||
v-for="(template, key) in templates"
|
||
:key="key"
|
||
:label="getTemplateLabel(key)"
|
||
:name="key">
|
||
<div class="template-editor">
|
||
<div class="template-info">
|
||
<el-alert
|
||
:title="template.description"
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
style="margin-bottom: 15px;">
|
||
</el-alert>
|
||
|
||
<div class="template-status">
|
||
<el-tag :type="template.isDefault ? 'info' : 'success'" size="small">
|
||
{{ template.isDefault ? '内置默认(未写入 Redis,可编辑后保存)' : '已自定义(已写入 Redis)' }}
|
||
</el-tag>
|
||
<el-button
|
||
v-if="!template.isDefault"
|
||
type="text"
|
||
size="small"
|
||
icon="el-icon-refresh-left"
|
||
@click="handleResetTemplate(key)"
|
||
style="margin-left: 10px;">
|
||
恢复默认
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<el-input
|
||
v-model="template.template"
|
||
type="textarea"
|
||
:rows="12"
|
||
placeholder="请输入提示词模板..."
|
||
class="template-textarea">
|
||
</el-input>
|
||
|
||
<div class="template-actions">
|
||
<el-button
|
||
type="primary"
|
||
icon="el-icon-check"
|
||
@click="handleSaveTemplate(key)"
|
||
:loading="saving[key]">
|
||
保存模板
|
||
</el-button>
|
||
<el-button
|
||
icon="el-icon-refresh"
|
||
@click="handleLoadTemplate(key)">
|
||
重新加载
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</div>
|
||
</el-card>
|
||
|
||
<el-card class="box-card llm-card" style="margin-top: 20px;">
|
||
<div slot="header" class="clearfix">
|
||
<span class="card-title">
|
||
<i class="el-icon-connection"></i>
|
||
大模型接入配置(多套 · 激活一套生效)
|
||
</span>
|
||
<div style="float: right;">
|
||
<el-button type="primary" size="small" icon="el-icon-plus" @click="openLlmDialog(false)">新增配置</el-button>
|
||
<el-button size="small" icon="el-icon-caret-right" @click="openLlmTest(null)">测试当前激活</el-button>
|
||
<el-button size="small" icon="el-icon-refresh" @click="loadLlmList">刷新</el-button>
|
||
</div>
|
||
</div>
|
||
<el-alert
|
||
v-if="llmSummary && llmSummary.redisAvailable === false"
|
||
title="当前未连接 Redis,无法保存;Jarvis 将始终使用 application.yml 中的默认 Ollama。"
|
||
type="warning"
|
||
:closable="false"
|
||
show-icon
|
||
style="margin-bottom: 16px;" />
|
||
<el-alert
|
||
type="info"
|
||
:closable="false"
|
||
show-icon
|
||
style="margin-bottom: 16px;">
|
||
<div slot="title">
|
||
<p style="margin: 0 0 8px 0;"><strong>说明:</strong>可保存多套(如本地 Ollama、远程 API),在表格中点击<strong>激活</strong>后,Jarvis 社媒 AI 仅使用<strong>当前激活</strong>的一套。未激活或清空全部时走 Jarvis 默认 Ollama。</p>
|
||
<ul style="margin: 0; padding-left: 18px;">
|
||
<li><strong>Ollama</strong>:根地址可空(用 Jarvis 默认),模型可空。</li>
|
||
<li><strong>OpenAI 兼容</strong>:须填完整 Chat Completions URL 与模型名;密钥可空。</li>
|
||
</ul>
|
||
</div>
|
||
</el-alert>
|
||
<p v-if="llmSummary && llmSummary.redisAvailable !== false" class="llm-active-hint">
|
||
<template v-if="llmSummary.activeId">
|
||
当前 Jarvis 使用:
|
||
<el-tag type="success" size="small">{{ llmActiveName || llmSummary.activeId }}</el-tag>
|
||
</template>
|
||
<template v-else>
|
||
<el-tag type="info" size="small">未激活</el-tag>
|
||
<span style="margin-left: 8px; color: #909399;">将使用 Jarvis yml 默认 Ollama</span>
|
||
</template>
|
||
</p>
|
||
<el-table
|
||
v-loading="llmListLoading"
|
||
:data="llmProfileRows"
|
||
border
|
||
stripe
|
||
empty-text="暂无配置,请点击「新增配置」"
|
||
style="width: 100%;">
|
||
<el-table-column prop="name" label="名称" min-width="120" show-overflow-tooltip />
|
||
<el-table-column label="方式" width="108">
|
||
<template slot-scope="scope">
|
||
{{ scope.row.mode === 'openai' ? 'OpenAI兼容' : 'Ollama' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="baseUrl" label="地址" min-width="200" show-overflow-tooltip />
|
||
<el-table-column prop="model" label="模型" min-width="120" show-overflow-tooltip />
|
||
<el-table-column label="密钥" width="100">
|
||
<template slot-scope="scope">
|
||
{{ scope.row.hasApiKey ? (scope.row.apiKeyMasked || '已设') : '—' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="当前" width="88" align="center">
|
||
<template slot-scope="scope">
|
||
<el-tag v-if="scope.row.active" type="success" size="mini">使用中</el-tag>
|
||
<span v-else>—</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="268" fixed="right">
|
||
<template slot-scope="scope">
|
||
<el-button type="text" size="small" :disabled="scope.row.active" @click="handleActivateLlm(scope.row)">激活</el-button>
|
||
<el-button type="text" size="small" @click="openLlmTest(scope.row)">测试</el-button>
|
||
<el-button type="text" size="small" @click="openLlmDialog(true, scope.row)">编辑</el-button>
|
||
<el-button type="text" size="small" class="llm-del-btn" @click="handleDeleteLlm(scope.row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="llm-toolbar-bottom">
|
||
<el-button size="small" @click="handleClearActiveLlm">取消激活</el-button>
|
||
<el-button type="danger" plain size="small" @click="handleResetAllLlm">清空全部配置</el-button>
|
||
</div>
|
||
|
||
<el-dialog
|
||
:title="llmDialogEditId ? '编辑大模型配置' : '新增大模型配置'"
|
||
:visible.sync="llmDialogVisible"
|
||
width="580px"
|
||
append-to-body
|
||
@close="resetLlmDialogForm">
|
||
<el-form :model="llmForm" label-width="140px" class="llm-form">
|
||
<el-form-item label="配置名称" required>
|
||
<el-input v-model="llmForm.name" placeholder="如:本机 Ollama、DeepSeek 官方" maxlength="80" show-word-limit />
|
||
</el-form-item>
|
||
<el-form-item label="接入方式">
|
||
<el-radio-group v-model="llmForm.mode">
|
||
<el-radio label="ollama">Ollama</el-radio>
|
||
<el-radio label="openai">OpenAI 兼容</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item :label="llmForm.mode === 'openai' ? 'API 完整地址' : 'Ollama 根地址'">
|
||
<el-input
|
||
v-model="llmForm.baseUrl"
|
||
:placeholder="llmForm.mode === 'openai' ? 'https://api.openai.com/v1/chat/completions' : 'http://127.0.0.1:11434(可空)'"
|
||
clearable />
|
||
</el-form-item>
|
||
<el-form-item label="模型名称">
|
||
<el-input
|
||
v-model="llmForm.model"
|
||
:placeholder="llmForm.mode === 'openai' ? '必填' : '可空'"
|
||
clearable />
|
||
</el-form-item>
|
||
<el-form-item label="API 密钥">
|
||
<el-input
|
||
v-model="llmForm.apiKeyInput"
|
||
type="password"
|
||
show-password
|
||
autocomplete="new-password"
|
||
:placeholder="llmDialogEditId ? '留空不修改;填写则覆盖' : '可选'" />
|
||
<span v-if="llmDialogEditId && llmHasApiKey" class="llm-key-hint">已保存:{{ llmApiKeyMasked || '****' }}</span>
|
||
</el-form-item>
|
||
<el-form-item v-if="llmDialogEditId && llmHasApiKey">
|
||
<el-checkbox v-model="llmClearApiKey">清除已保存的密钥</el-checkbox>
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="llmDialogVisible = false">取 消</el-button>
|
||
<el-button type="primary" :loading="llmSaving" @click="submitLlmDialog">保 存</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
|
||
<el-dialog
|
||
title="大模型连通测试"
|
||
:visible.sync="llmTestDialogVisible"
|
||
width="560px"
|
||
append-to-body
|
||
@close="resetLlmTestDialog">
|
||
<p v-if="llmTestRow" class="llm-test-hint">
|
||
将使用配置「<strong>{{ llmTestRow.name }}</strong>」的参数请求(与是否已激活无关)。
|
||
</p>
|
||
<p v-else class="llm-test-hint">
|
||
未指定某一套配置:将使用 Jarvis 当前<strong>已激活</strong>的一套;若未激活则使用 Jarvis <strong>application.yml</strong> 默认 Ollama。
|
||
</p>
|
||
<el-form label-width="88px">
|
||
<el-form-item label="测试问题">
|
||
<el-input v-model="llmTestMessage" type="textarea" :rows="4" placeholder="默认会询问 1+1,可改成任意短句做连通性验证" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<div v-if="llmTestResult" class="llm-test-result">
|
||
<el-alert v-if="llmTestResult.success" title="模型回复" type="success" :closable="false" show-icon>
|
||
<pre class="llm-test-reply">{{ llmTestResult.reply }}</pre>
|
||
</el-alert>
|
||
<el-alert v-else :description="llmTestResult.error || '请求失败'" title="调用失败" type="error" :closable="false" show-icon />
|
||
</div>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button @click="llmTestDialogVisible = false">关 闭</el-button>
|
||
<el-button type="primary" :loading="llmTestLoading" icon="el-icon-position" @click="submitLlmTest">发送测试</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</el-card>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
listPromptTemplates,
|
||
getPromptTemplate,
|
||
savePromptTemplate,
|
||
deletePromptTemplate,
|
||
listLlmProfiles,
|
||
getLlmProfile,
|
||
createLlmProfile,
|
||
updateLlmProfile,
|
||
deleteLlmProfile,
|
||
setActiveLlmProfile,
|
||
clearActiveLlmProfile,
|
||
resetAllLlmConfig,
|
||
testLlmProfile
|
||
} from '@/api/jarvis/socialMediaPrompt'
|
||
|
||
export default {
|
||
name: 'SocialMediaPromptConfig',
|
||
data() {
|
||
return {
|
||
showHelp: false,
|
||
activeTab: 'keywords',
|
||
templates: {},
|
||
saving: {},
|
||
llmSummary: null,
|
||
llmProfileRows: [],
|
||
llmListLoading: false,
|
||
llmDialogVisible: false,
|
||
llmDialogEditId: null,
|
||
llmForm: {
|
||
name: '',
|
||
mode: 'ollama',
|
||
baseUrl: '',
|
||
model: '',
|
||
apiKeyInput: ''
|
||
},
|
||
llmHasApiKey: false,
|
||
llmApiKeyMasked: null,
|
||
llmClearApiKey: false,
|
||
llmSaving: false,
|
||
llmTestDialogVisible: false,
|
||
llmTestRow: null,
|
||
llmTestMessage: '1+1等于几?请只回答一个数字或最简结果,不要多余解释。',
|
||
llmTestLoading: false,
|
||
llmTestResult: null
|
||
}
|
||
},
|
||
computed: {
|
||
llmActiveName() {
|
||
const r = this.llmProfileRows.find(p => p.active)
|
||
return r ? r.name : ''
|
||
}
|
||
},
|
||
mounted() {
|
||
this.loadTemplates()
|
||
this.loadLlmList()
|
||
},
|
||
methods: {
|
||
/** 加载所有模板 */
|
||
async loadTemplates() {
|
||
try {
|
||
const res = await listPromptTemplates()
|
||
if (res.code === 200 && res.data) {
|
||
this.templates = res.data
|
||
// 初始化保存状态
|
||
Object.keys(this.templates).forEach(key => {
|
||
this.$set(this.saving, key, false)
|
||
})
|
||
} else {
|
||
this.$message.error(res.msg || '加载模板失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('加载模板失败', error)
|
||
this.$message.error('加载模板失败:' + (error.message || '未知错误'))
|
||
}
|
||
},
|
||
|
||
/** 加载单个模板 */
|
||
async handleLoadTemplate(key) {
|
||
try {
|
||
const res = await getPromptTemplate(key)
|
||
if (res.code === 200 && res.data) {
|
||
this.$set(this.templates, key, res.data)
|
||
this.$message.success('重新加载成功')
|
||
} else {
|
||
this.$message.error(res.msg || '加载失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('加载模板失败', error)
|
||
this.$message.error('加载失败:' + (error.message || '未知错误'))
|
||
}
|
||
},
|
||
|
||
/** 保存模板 */
|
||
async handleSaveTemplate(key) {
|
||
const template = this.templates[key]
|
||
if (!template || !template.template || template.template.trim() === '') {
|
||
this.$message.warning('模板内容不能为空')
|
||
return
|
||
}
|
||
|
||
this.$set(this.saving, key, true)
|
||
try {
|
||
const res = await savePromptTemplate({
|
||
key: key,
|
||
template: template.template.trim()
|
||
})
|
||
|
||
if (res.code === 200) {
|
||
this.$message.success('保存成功!')
|
||
// 更新状态
|
||
this.$set(template, 'isDefault', false)
|
||
// 重新加载以确认
|
||
await this.handleLoadTemplate(key)
|
||
} else {
|
||
this.$message.error(res.msg || '保存失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('保存模板失败', error)
|
||
this.$message.error('保存失败:' + (error.message || '未知错误'))
|
||
} finally {
|
||
this.$set(this.saving, key, false)
|
||
}
|
||
},
|
||
|
||
/** 恢复默认模板 */
|
||
async handleResetTemplate(key) {
|
||
try {
|
||
await this.$confirm('确定要恢复默认模板吗?自定义模板将被删除。', '提示', {
|
||
type: 'warning'
|
||
})
|
||
|
||
const res = await deletePromptTemplate(key)
|
||
if (res.code === 200) {
|
||
this.$message.success('已恢复默认模板')
|
||
// 重新加载
|
||
await this.handleLoadTemplate(key)
|
||
} else {
|
||
this.$message.error(res.msg || '恢复失败')
|
||
}
|
||
} catch (error) {
|
||
if (error !== 'cancel') {
|
||
console.error('恢复默认模板失败', error)
|
||
this.$message.error('恢复失败:' + (error.message || '未知错误'))
|
||
}
|
||
}
|
||
},
|
||
|
||
/** 获取模板标签 */
|
||
getTemplateLabel(key) {
|
||
const labels = {
|
||
'keywords': '关键词提取',
|
||
'content:xhs': '小红书文案',
|
||
'content:douyin': '抖音文案',
|
||
'content:both': '通用文案',
|
||
'xianyu:wenan_base': '闲鱼·正文基础',
|
||
'xianyu:jiaonixiadan_extra': '闲鱼·教你下单尾部',
|
||
'xianyu:title_clean_regex': '闲鱼·标题清洗正则'
|
||
}
|
||
return labels[key] || key
|
||
},
|
||
|
||
async loadLlmList() {
|
||
this.llmListLoading = true
|
||
try {
|
||
const res = await listLlmProfiles()
|
||
if (res.code === 200 && res.data) {
|
||
this.llmSummary = res.data
|
||
this.llmProfileRows = Array.isArray(res.data.profiles) ? res.data.profiles : []
|
||
} else {
|
||
this.$message.error(res.msg || '加载大模型配置失败')
|
||
}
|
||
} catch (e) {
|
||
console.error(e)
|
||
this.$message.error('加载大模型配置失败:' + (e.message || '未知错误'))
|
||
} finally {
|
||
this.llmListLoading = false
|
||
}
|
||
},
|
||
|
||
resetLlmDialogForm() {
|
||
this.llmDialogEditId = null
|
||
this.llmForm = {
|
||
name: '',
|
||
mode: 'ollama',
|
||
baseUrl: '',
|
||
model: '',
|
||
apiKeyInput: ''
|
||
}
|
||
this.llmHasApiKey = false
|
||
this.llmApiKeyMasked = null
|
||
this.llmClearApiKey = false
|
||
},
|
||
|
||
async openLlmDialog(edit, row) {
|
||
if (this.llmSummary && this.llmSummary.redisAvailable === false) {
|
||
this.$message.warning('Redis 未配置,无法保存')
|
||
return
|
||
}
|
||
this.resetLlmDialogForm()
|
||
if (edit && row && row.id) {
|
||
this.llmDialogEditId = row.id
|
||
try {
|
||
const res = await getLlmProfile(row.id)
|
||
if (res.code !== 200 || !res.data) {
|
||
this.$message.error(res.msg || '加载失败')
|
||
return
|
||
}
|
||
const d = res.data
|
||
this.llmForm.name = d.name || ''
|
||
this.llmForm.mode = d.mode === 'openai' ? 'openai' : 'ollama'
|
||
this.llmForm.baseUrl = d.baseUrl || ''
|
||
this.llmForm.model = d.model || ''
|
||
this.llmHasApiKey = !!d.hasApiKey
|
||
this.llmApiKeyMasked = d.apiKeyMasked || null
|
||
} catch (e) {
|
||
console.error(e)
|
||
this.$message.error('加载失败')
|
||
return
|
||
}
|
||
}
|
||
this.llmDialogVisible = true
|
||
},
|
||
|
||
async submitLlmDialog() {
|
||
if (!this.llmForm.name || !this.llmForm.name.trim()) {
|
||
this.$message.warning('请填写配置名称')
|
||
return
|
||
}
|
||
if (this.llmForm.mode === 'openai') {
|
||
if (!this.llmForm.baseUrl || !this.llmForm.baseUrl.trim()) {
|
||
this.$message.warning('OpenAI 兼容须填写完整 API 地址')
|
||
return
|
||
}
|
||
if (!this.llmForm.model || !this.llmForm.model.trim()) {
|
||
this.$message.warning('OpenAI 兼容须填写模型名称')
|
||
return
|
||
}
|
||
}
|
||
const payload = {
|
||
name: this.llmForm.name.trim(),
|
||
mode: this.llmForm.mode,
|
||
baseUrl: (this.llmForm.baseUrl || '').trim(),
|
||
model: (this.llmForm.model || '').trim()
|
||
}
|
||
if (this.llmDialogEditId) {
|
||
if (this.llmClearApiKey) {
|
||
payload.clearApiKey = true
|
||
} else if (this.llmForm.apiKeyInput && this.llmForm.apiKeyInput.trim()) {
|
||
payload.apiKey = this.llmForm.apiKeyInput.trim()
|
||
}
|
||
} else if (this.llmForm.apiKeyInput && this.llmForm.apiKeyInput.trim()) {
|
||
payload.apiKey = this.llmForm.apiKeyInput.trim()
|
||
}
|
||
this.llmSaving = true
|
||
try {
|
||
let res
|
||
if (this.llmDialogEditId) {
|
||
res = await updateLlmProfile(this.llmDialogEditId, payload)
|
||
} else {
|
||
res = await createLlmProfile(payload)
|
||
}
|
||
if (res.code === 200) {
|
||
this.$message.success('保存成功')
|
||
this.llmDialogVisible = false
|
||
await this.loadLlmList()
|
||
} else {
|
||
this.$message.error(res.msg || '保存失败')
|
||
}
|
||
} catch (e) {
|
||
console.error(e)
|
||
this.$message.error('保存失败:' + (e.message || '未知错误'))
|
||
} finally {
|
||
this.llmSaving = false
|
||
}
|
||
},
|
||
|
||
async handleActivateLlm(row) {
|
||
try {
|
||
const res = await setActiveLlmProfile(row.id)
|
||
if (res.code === 200) {
|
||
this.$message.success(res.msg || '已激活')
|
||
await this.loadLlmList()
|
||
} else {
|
||
this.$message.error(res.msg || '操作失败')
|
||
}
|
||
} catch (e) {
|
||
console.error(e)
|
||
this.$message.error('操作失败:' + (e.message || '未知错误'))
|
||
}
|
||
},
|
||
|
||
async handleDeleteLlm(row) {
|
||
try {
|
||
await this.$confirm(`确定删除配置「${row.name || row.id}」?`, '提示', { type: 'warning' })
|
||
const res = await deleteLlmProfile(row.id)
|
||
if (res.code === 200) {
|
||
this.$message.success('已删除')
|
||
await this.loadLlmList()
|
||
} else {
|
||
this.$message.error(res.msg || '删除失败')
|
||
}
|
||
} catch (e) {
|
||
if (e !== 'cancel') {
|
||
console.error(e)
|
||
this.$message.error('删除失败:' + (e.message || '未知错误'))
|
||
}
|
||
}
|
||
},
|
||
|
||
async handleClearActiveLlm() {
|
||
try {
|
||
const res = await clearActiveLlmProfile()
|
||
if (res.code === 200) {
|
||
this.$message.success(res.msg || '已取消激活')
|
||
await this.loadLlmList()
|
||
} else {
|
||
this.$message.error(res.msg || '操作失败')
|
||
}
|
||
} catch (e) {
|
||
console.error(e)
|
||
this.$message.error('操作失败:' + (e.message || '未知错误'))
|
||
}
|
||
},
|
||
|
||
async handleResetAllLlm() {
|
||
try {
|
||
await this.$confirm('将删除所有大模型接入配置及旧版单键,Jarvis 恢复为默认 Ollama。是否继续?', '提示', { type: 'warning' })
|
||
const res = await resetAllLlmConfig()
|
||
if (res.code === 200) {
|
||
this.$message.success(res.msg || '已清空')
|
||
await this.loadLlmList()
|
||
} else {
|
||
this.$message.error(res.msg || '操作失败')
|
||
}
|
||
} catch (e) {
|
||
if (e !== 'cancel') {
|
||
console.error(e)
|
||
this.$message.error('操作失败:' + (e.message || '未知错误'))
|
||
}
|
||
}
|
||
},
|
||
|
||
resetLlmTestDialog() {
|
||
this.llmTestRow = null
|
||
this.llmTestResult = null
|
||
this.llmTestLoading = false
|
||
},
|
||
|
||
openLlmTest(row) {
|
||
if (this.llmSummary && this.llmSummary.redisAvailable === false) {
|
||
this.$message.warning('Redis 未配置时无法从后台写入多套参数;仍可直接测 Jarvis 默认,请确认 Jarvis 地址正确。')
|
||
}
|
||
this.llmTestRow = row || null
|
||
this.llmTestMessage = '1+1等于几?请只回答一个数字或最简结果,不要多余解释。'
|
||
this.llmTestResult = null
|
||
this.llmTestDialogVisible = true
|
||
},
|
||
|
||
async submitLlmTest() {
|
||
if (!this.llmTestMessage || !this.llmTestMessage.trim()) {
|
||
this.$message.warning('请填写测试问题')
|
||
return
|
||
}
|
||
const payload = { message: this.llmTestMessage.trim() }
|
||
if (this.llmTestRow && this.llmTestRow.id) {
|
||
payload.profileId = this.llmTestRow.id
|
||
}
|
||
this.llmTestLoading = true
|
||
this.llmTestResult = null
|
||
try {
|
||
const res = await testLlmProfile(payload)
|
||
if (res.code === 200 && res.data) {
|
||
this.llmTestResult = {
|
||
success: !!res.data.success,
|
||
reply: res.data.reply,
|
||
error: res.data.error
|
||
}
|
||
if (this.llmTestResult.success) {
|
||
this.$message.success('测试完成')
|
||
} else {
|
||
this.$message.error(this.llmTestResult.error || '调用失败')
|
||
}
|
||
} else {
|
||
this.$message.error(res.msg || '测试失败')
|
||
this.llmTestResult = { success: false, error: res.msg || '未知错误' }
|
||
}
|
||
} catch (e) {
|
||
console.error(e)
|
||
this.$message.error('测试失败:' + (e.message || '未知错误'))
|
||
this.llmTestResult = { success: false, error: e.message || '未知错误' }
|
||
} finally {
|
||
this.llmTestLoading = false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.prompt-config-container {
|
||
padding: 20px;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.help-section {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.help-section ul {
|
||
margin: 10px 0;
|
||
padding-left: 20px;
|
||
}
|
||
|
||
.help-section li {
|
||
margin: 5px 0;
|
||
}
|
||
|
||
.template-list {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.template-editor {
|
||
padding: 20px;
|
||
}
|
||
|
||
.template-info {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.template-status {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.template-textarea {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.template-textarea >>> .el-textarea__inner {
|
||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.template-actions {
|
||
text-align: right;
|
||
}
|
||
|
||
.template-actions .el-button {
|
||
margin-left: 10px;
|
||
}
|
||
|
||
.llm-card .llm-form {
|
||
max-width: 720px;
|
||
}
|
||
|
||
.llm-key-hint {
|
||
display: block;
|
||
margin-top: 6px;
|
||
font-size: 12px;
|
||
color: #909399;
|
||
}
|
||
|
||
.llm-active-hint {
|
||
margin-bottom: 12px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.llm-toolbar-bottom {
|
||
margin-top: 14px;
|
||
}
|
||
|
||
.llm-toolbar-bottom .el-button {
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.llm-del-btn {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
.llm-test-hint {
|
||
margin: 0 0 12px 0;
|
||
font-size: 13px;
|
||
color: #606266;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.llm-test-result {
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.llm-test-reply {
|
||
margin: 8px 0 0 0;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
font-family: 'Consolas', 'Monaco', monospace;
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
}
|
||
</style>
|
||
|