Files
st/web-app-vue/src/views/provider/components/TestMessageDialog.vue

254 lines
6.2 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>
<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>