🎨 优化扩展模块,完成ai接入和对话功能
This commit is contained in:
253
web-app-vue/src/views/provider/components/TestMessageDialog.vue
Normal file
253
web-app-vue/src/views/provider/components/TestMessageDialog.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="modelValue"
|
||||
title="发送测试消息"
|
||||
width="560px"
|
||||
:close-on-click-modal="false"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
@open="onOpen"
|
||||
@closed="onClosed"
|
||||
>
|
||||
<div class="test-message-content">
|
||||
<!-- 提供商信息 -->
|
||||
<div class="provider-info">
|
||||
<span class="provider-label">{{ provider?.providerName }}</span>
|
||||
<el-tag size="small">{{ getProviderTypeLabel(provider?.providerType || '') }}</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 模型选择 -->
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="选择模型">
|
||||
<el-select
|
||||
v-model="selectedModel"
|
||||
placeholder="请选择要测试的模型"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="model in chatModels"
|
||||
:key="model.id"
|
||||
:label="model.displayName || model.modelName"
|
||||
:value="model.modelName"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 消息内容 -->
|
||||
<el-form-item label="测试消息">
|
||||
<el-input
|
||||
v-model="message"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="留空使用默认消息:你好,请用一句话介绍你自己。"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 发送按钮 -->
|
||||
<div class="send-section">
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="sending"
|
||||
:disabled="!selectedModel"
|
||||
@click="handleSend"
|
||||
>
|
||||
<el-icon class="mr-1"><Promotion /></el-icon>
|
||||
{{ sending ? '等待回复...' : '发送测试消息' }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 测试结果 -->
|
||||
<div v-if="testResult" class="result-section">
|
||||
<div class="result-header" :class="{ success: testResult.success, error: !testResult.success }">
|
||||
<el-icon v-if="testResult.success"><CircleCheckFilled /></el-icon>
|
||||
<el-icon v-else><CircleCloseFilled /></el-icon>
|
||||
<span>{{ testResult.message }}</span>
|
||||
<span v-if="testResult.latency" class="result-meta">
|
||||
{{ testResult.latency }}ms
|
||||
</span>
|
||||
<span v-if="testResult.tokens" class="result-meta">
|
||||
{{ testResult.tokens }} tokens
|
||||
</span>
|
||||
<span v-if="testResult.model" class="result-meta">
|
||||
模型: {{ testResult.model }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- AI 回复内容 -->
|
||||
<div v-if="testResult.reply" class="result-reply">
|
||||
<div class="reply-label">AI 回复:</div>
|
||||
<div class="reply-content">{{ testResult.reply }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="$emit('update:modelValue', false)">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { Promotion, CircleCheckFilled, CircleCloseFilled } from '@element-plus/icons-vue'
|
||||
import { useProviderStore } from '@/stores/provider'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
provider?: AIProvider | null
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
}>()
|
||||
|
||||
const store = useProviderStore()
|
||||
const selectedModel = ref('')
|
||||
const message = ref('')
|
||||
const sending = ref(false)
|
||||
const testResult = ref<SendTestMessageResponse | null>(null)
|
||||
|
||||
/** 只显示对话类型模型 */
|
||||
const chatModels = computed(() => {
|
||||
if (!props.provider) return []
|
||||
return props.provider.models.filter(m => m.modelType === 'chat' && m.isEnabled)
|
||||
})
|
||||
|
||||
function getProviderTypeLabel(type: string) {
|
||||
const labels: Record<string, string> = {
|
||||
openai: 'OpenAI / 兼容接口',
|
||||
claude: 'Anthropic Claude',
|
||||
gemini: 'Google Gemini',
|
||||
custom: '自定义接口',
|
||||
}
|
||||
return labels[type] || type
|
||||
}
|
||||
|
||||
/** 发送测试消息 */
|
||||
async function handleSend() {
|
||||
if (!props.provider || !selectedModel.value) return
|
||||
|
||||
sending.value = true
|
||||
testResult.value = null
|
||||
|
||||
const result = await store.sendTestMessageExisting(
|
||||
props.provider.id,
|
||||
selectedModel.value,
|
||||
message.value || undefined,
|
||||
)
|
||||
|
||||
sending.value = false
|
||||
|
||||
if (result) {
|
||||
testResult.value = result
|
||||
} else {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: '请求失败,请检查网络',
|
||||
reply: '',
|
||||
model: '',
|
||||
latency: 0,
|
||||
tokens: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 对话框打开 */
|
||||
function onOpen() {
|
||||
testResult.value = null
|
||||
message.value = ''
|
||||
// 默认选中第一个对话模型
|
||||
if (chatModels.value.length > 0) {
|
||||
selectedModel.value = chatModels.value[0].modelName
|
||||
} else {
|
||||
selectedModel.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
/** 对话框关闭 */
|
||||
function onClosed() {
|
||||
testResult.value = null
|
||||
sending.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.test-message-content {
|
||||
.provider-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
padding: 10px 14px;
|
||||
background: var(--el-bg-color-page);
|
||||
border-radius: 8px;
|
||||
|
||||
.provider-label {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.send-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.result-section {
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
.result-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&.success {
|
||||
background: var(--el-color-success-light-9);
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
&.error {
|
||||
background: var(--el-color-danger-light-9);
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
|
||||
.result-meta {
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
margin-left: 4px;
|
||||
|
||||
&::before {
|
||||
content: '·';
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.result-reply {
|
||||
padding: 14px;
|
||||
|
||||
.reply-label {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.reply-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
color: var(--el-text-color-primary);
|
||||
background: var(--el-bg-color-page);
|
||||
padding: 12px 14px;
|
||||
border-radius: 8px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mr-1 { margin-right: 4px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user