feat(ASA-P5): 实现动态配置表单与测试连接组件 [AC-ASA-09, AC-ASA-10, AC-ASA-11, AC-ASA-12]
This commit is contained in:
parent
c1d76093aa
commit
f2116b95f2
|
|
@ -0,0 +1,88 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export interface EmbeddingProviderInfo {
|
||||||
|
name: string
|
||||||
|
display_name: string
|
||||||
|
description?: string
|
||||||
|
config_schema: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmbeddingConfig {
|
||||||
|
provider: string
|
||||||
|
config: Record<string, any>
|
||||||
|
updated_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmbeddingConfigUpdate {
|
||||||
|
provider: string
|
||||||
|
config?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmbeddingTestResult {
|
||||||
|
success: boolean
|
||||||
|
dimension: number
|
||||||
|
latency_ms?: number
|
||||||
|
message?: string
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmbeddingTestRequest {
|
||||||
|
test_text?: string
|
||||||
|
config?: EmbeddingConfigUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DocumentFormat {
|
||||||
|
extension: string
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmbeddingProvidersResponse {
|
||||||
|
providers: EmbeddingProviderInfo[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmbeddingConfigUpdateResponse {
|
||||||
|
success: boolean
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SupportedFormatsResponse {
|
||||||
|
formats: DocumentFormat[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProviders() {
|
||||||
|
return request({
|
||||||
|
url: '/admin/embedding/providers',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConfig() {
|
||||||
|
return request({
|
||||||
|
url: '/admin/embedding/config',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveConfig(data: EmbeddingConfigUpdate) {
|
||||||
|
return request({
|
||||||
|
url: '/admin/embedding/config',
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function testEmbedding(data: EmbeddingTestRequest): Promise<EmbeddingTestResult> {
|
||||||
|
return request({
|
||||||
|
url: '/admin/embedding/test',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSupportedFormats() {
|
||||||
|
return request({
|
||||||
|
url: '/admin/embedding/formats',
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
<template>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
:label-width="labelWidth"
|
||||||
|
v-bind="$attrs"
|
||||||
|
>
|
||||||
|
<el-form-item
|
||||||
|
v-for="(field, key) in schemaProperties"
|
||||||
|
:key="key"
|
||||||
|
:label="field.title || key"
|
||||||
|
:prop="key"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<span>{{ field.title || key }}</span>
|
||||||
|
<el-tooltip v-if="field.description" :content="field.description" placement="top">
|
||||||
|
<el-icon class="ml-1 cursor-help"><QuestionFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
<el-input
|
||||||
|
v-if="field.type === 'string'"
|
||||||
|
v-model="formData[key]"
|
||||||
|
:placeholder="`请输入${field.title || key}`"
|
||||||
|
clearable
|
||||||
|
:show-password="isPasswordField(key)"
|
||||||
|
/>
|
||||||
|
<el-input-number
|
||||||
|
v-else-if="field.type === 'integer' || field.type === 'number'"
|
||||||
|
v-model="formData[key]"
|
||||||
|
:placeholder="`请输入${field.title || key}`"
|
||||||
|
:min="field.minimum"
|
||||||
|
:max="field.maximum"
|
||||||
|
:step="field.type === 'number' ? 0.1 : 1"
|
||||||
|
:precision="field.type === 'number' ? 2 : 0"
|
||||||
|
controls-position="right"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
<el-switch
|
||||||
|
v-else-if="field.type === 'boolean'"
|
||||||
|
v-model="formData[key]"
|
||||||
|
/>
|
||||||
|
<el-select
|
||||||
|
v-else-if="field.enum && field.enum.length > 0"
|
||||||
|
v-model="formData[key]"
|
||||||
|
:placeholder="`请选择${field.title || key}`"
|
||||||
|
clearable
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="option in field.enum"
|
||||||
|
:key="option"
|
||||||
|
:label="option"
|
||||||
|
:value="option"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch, onMounted } from 'vue'
|
||||||
|
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
|
||||||
|
interface SchemaProperty {
|
||||||
|
type: string
|
||||||
|
title?: string
|
||||||
|
description?: string
|
||||||
|
default?: any
|
||||||
|
enum?: string[]
|
||||||
|
minimum?: number
|
||||||
|
maximum?: number
|
||||||
|
required?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConfigSchema {
|
||||||
|
type?: string
|
||||||
|
properties?: Record<string, SchemaProperty>
|
||||||
|
required?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
schema: ConfigSchema
|
||||||
|
modelValue: Record<string, any>
|
||||||
|
labelWidth?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: Record<string, any>): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const formData = ref<Record<string, any>>({})
|
||||||
|
|
||||||
|
const schemaProperties = computed(() => {
|
||||||
|
return props.schema?.properties || {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const requiredFields = computed(() => {
|
||||||
|
const required = props.schema?.required || []
|
||||||
|
const propsRequired = Object.entries(schemaProperties.value)
|
||||||
|
.filter(([, field]) => field.required)
|
||||||
|
.map(([key]) => key)
|
||||||
|
return [...new Set([...required, ...propsRequired])]
|
||||||
|
})
|
||||||
|
|
||||||
|
const formRules = computed<FormRules>(() => {
|
||||||
|
const rules: FormRules = {}
|
||||||
|
Object.entries(schemaProperties.value).forEach(([key, field]) => {
|
||||||
|
const fieldRules: any[] = []
|
||||||
|
if (requiredFields.value.includes(key)) {
|
||||||
|
fieldRules.push({
|
||||||
|
required: true,
|
||||||
|
message: `${field.title || key}不能为空`,
|
||||||
|
trigger: ['blur', 'change']
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (field.type === 'string' && field.minimum !== undefined) {
|
||||||
|
fieldRules.push({
|
||||||
|
min: field.minimum,
|
||||||
|
message: `${field.title || key}长度不能小于${field.minimum}`,
|
||||||
|
trigger: ['blur']
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (field.type === 'string' && field.maximum !== undefined) {
|
||||||
|
fieldRules.push({
|
||||||
|
max: field.maximum,
|
||||||
|
message: `${field.title || key}长度不能大于${field.maximum}`,
|
||||||
|
trigger: ['blur']
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (rules[key]) {
|
||||||
|
rules[key] = fieldRules
|
||||||
|
} else if (fieldRules.length > 0) {
|
||||||
|
rules[key] = fieldRules
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return rules
|
||||||
|
})
|
||||||
|
|
||||||
|
const isPasswordField = (key: string): boolean => {
|
||||||
|
const lowerKey = key.toLowerCase()
|
||||||
|
return lowerKey.includes('password') || lowerKey.includes('secret') || lowerKey.includes('key') || lowerKey.includes('token')
|
||||||
|
}
|
||||||
|
|
||||||
|
const initFormData = () => {
|
||||||
|
const data: Record<string, any> = {}
|
||||||
|
Object.entries(schemaProperties.value).forEach(([key, field]) => {
|
||||||
|
if (props.modelValue && props.modelValue[key] !== undefined) {
|
||||||
|
data[key] = props.modelValue[key]
|
||||||
|
} else if (field.default !== undefined) {
|
||||||
|
data[key] = field.default
|
||||||
|
} else {
|
||||||
|
switch (field.type) {
|
||||||
|
case 'string':
|
||||||
|
data[key] = ''
|
||||||
|
break
|
||||||
|
case 'integer':
|
||||||
|
case 'number':
|
||||||
|
data[key] = field.minimum ?? 0
|
||||||
|
break
|
||||||
|
case 'boolean':
|
||||||
|
data[key] = false
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
data[key] = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
formData.value = data
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
() => {
|
||||||
|
initFormData()
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.schema,
|
||||||
|
() => {
|
||||||
|
initFormData()
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
formData,
|
||||||
|
(val) => {
|
||||||
|
emit('update:modelValue', val)
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initFormData()
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
validate: () => formRef.value?.validate(),
|
||||||
|
resetFields: () => formRef.value?.resetFields(),
|
||||||
|
clearValidate: () => formRef.value?.clearValidate()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.ml-1 {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
.cursor-help {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
<template>
|
||||||
|
<el-card shadow="never" class="test-panel">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>连接测试</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="test-content">
|
||||||
|
<el-form :model="testForm" label-width="80px">
|
||||||
|
<el-form-item label="测试文本">
|
||||||
|
<el-input
|
||||||
|
v-model="testForm.test_text"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="请输入测试文本(可选,默认使用系统预设文本)"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="!config?.provider"
|
||||||
|
@click="handleTest"
|
||||||
|
>
|
||||||
|
<el-icon v-if="!loading"><Connection /></el-icon>
|
||||||
|
{{ loading ? '测试中...' : '测试连接' }}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<div v-if="testResult" class="test-result">
|
||||||
|
<el-divider />
|
||||||
|
|
||||||
|
<el-alert
|
||||||
|
v-if="testResult.success"
|
||||||
|
:title="testResult.message || '连接成功'"
|
||||||
|
type="success"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
class="result-alert"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<div class="success-details">
|
||||||
|
<div class="detail-item">
|
||||||
|
<span class="label">向量维度:</span>
|
||||||
|
<el-tag type="success">{{ testResult.dimension }}</el-tag>
|
||||||
|
</div>
|
||||||
|
<div v-if="testResult.latency_ms" class="detail-item">
|
||||||
|
<span class="label">响应延迟:</span>
|
||||||
|
<el-tag type="info">{{ testResult.latency_ms.toFixed(2) }} ms</el-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
|
||||||
|
<el-alert
|
||||||
|
v-else
|
||||||
|
:title="testResult.error || '连接失败'"
|
||||||
|
type="error"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
class="result-alert"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<div class="error-details">
|
||||||
|
<p class="error-message">{{ testResult.error || '未知错误' }}</p>
|
||||||
|
<div class="troubleshooting">
|
||||||
|
<p class="troubleshoot-title">排查建议:</p>
|
||||||
|
<ul class="troubleshoot-list">
|
||||||
|
<li v-for="(tip, index) in troubleshootingTips" :key="index">
|
||||||
|
{{ tip }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-alert>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { Connection } from '@element-plus/icons-vue'
|
||||||
|
import { testEmbedding, type EmbeddingConfigUpdate, type EmbeddingTestResult } from '@/api/embedding'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
config: EmbeddingConfigUpdate | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const testResult = ref<EmbeddingTestResult | null>(null)
|
||||||
|
|
||||||
|
const testForm = ref({
|
||||||
|
test_text: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const troubleshootingTips = computed(() => {
|
||||||
|
const tips: string[] = []
|
||||||
|
const error = testResult.value?.error?.toLowerCase() || ''
|
||||||
|
|
||||||
|
if (error.includes('timeout') || error.includes('超时')) {
|
||||||
|
tips.push('检查网络连接是否正常')
|
||||||
|
tips.push('确认服务地址是否正确且可访问')
|
||||||
|
tips.push('尝试增加请求超时时间')
|
||||||
|
} else if (error.includes('auth') || error.includes('unauthorized') || error.includes('认证') || error.includes('api key')) {
|
||||||
|
tips.push('检查 API Key 是否正确')
|
||||||
|
tips.push('确认 API Key 是否已过期或被禁用')
|
||||||
|
tips.push('验证 API Key 是否具有足够的权限')
|
||||||
|
} else if (error.includes('connection') || error.includes('连接') || error.includes('refused')) {
|
||||||
|
tips.push('确认服务地址(host/port)配置正确')
|
||||||
|
tips.push('检查目标服务是否正在运行')
|
||||||
|
tips.push('验证防火墙是否允许访问')
|
||||||
|
} else if (error.includes('model') || error.includes('模型')) {
|
||||||
|
tips.push('确认模型名称是否正确')
|
||||||
|
tips.push('检查模型是否已部署或可用')
|
||||||
|
tips.push('验证模型配置参数是否符合要求')
|
||||||
|
} else {
|
||||||
|
tips.push('检查所有配置参数是否正确')
|
||||||
|
tips.push('确认服务是否正常运行')
|
||||||
|
tips.push('查看服务端日志获取详细错误信息')
|
||||||
|
}
|
||||||
|
|
||||||
|
return tips
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleTest = async () => {
|
||||||
|
if (!props.config?.provider) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
testResult.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestData: any = {
|
||||||
|
config: props.config
|
||||||
|
}
|
||||||
|
if (testForm.value.test_text?.trim()) {
|
||||||
|
requestData.test_text = testForm.value.test_text.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await testEmbedding(requestData)
|
||||||
|
testResult.value = result
|
||||||
|
} catch (error: any) {
|
||||||
|
testResult.value = {
|
||||||
|
success: false,
|
||||||
|
dimension: 0,
|
||||||
|
error: error?.message || '请求失败,请检查网络连接'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.test-panel {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-content {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-result {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-alert {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-details {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 20px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-item .label {
|
||||||
|
color: #606266;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-details {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: #f56c6c;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.troubleshooting {
|
||||||
|
background-color: #fef0f0;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.troubleshoot-title {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #f56c6c;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.troubleshoot-list {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.troubleshoot-list li {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
module: ai-service-admin
|
module: ai-service-admin
|
||||||
title: "AI 中台管理界面(ai-service-admin)任务清单"
|
title: "AI 中台管理界面(ai-service-admin)任务清单"
|
||||||
status: "draft"
|
status: "draft"
|
||||||
version: "0.1.0"
|
version: "0.2.0"
|
||||||
owners:
|
owners:
|
||||||
- "frontend"
|
- "frontend"
|
||||||
- "backend"
|
- "backend"
|
||||||
|
|
@ -116,3 +116,48 @@ principles:
|
||||||
- `/admin/rag/experiments/run`(POST 实验结果:retrievalResults + finalPrompt)
|
- `/admin/rag/experiments/run`(POST 实验结果:retrievalResults + finalPrompt)
|
||||||
- `/admin/sessions`(GET 列表,分页 + 筛选)
|
- `/admin/sessions`(GET 列表,分页 + 筛选)
|
||||||
- `/admin/sessions/{sessionId}`(GET 详情:messages + trace)
|
- `/admin/sessions/{sessionId}`(GET 详情:messages + trace)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: 嵌入模型管理(配置页面/测试连接)
|
||||||
|
|
||||||
|
> 页面导向:嵌入模型配置页面,支持提供者切换、参数配置、连接测试。
|
||||||
|
|
||||||
|
- [ ] (P5-01) 嵌入模型配置页面骨架:创建 `/admin/embedding` 路由,布局包含提供者选择区、配置表单区、测试连接区、支持格式展示区。
|
||||||
|
- AC: [AC-ASA-08]
|
||||||
|
|
||||||
|
- [x] (P5-02) 提供者选择组件:实现 `EmbeddingProviderSelect` 下拉组件,对接 `/admin/embedding/providers`,展示提供者列表(name、display_name、description)。
|
||||||
|
- AC: [AC-ASA-09]
|
||||||
|
|
||||||
|
- [x] (P5-03) 动态配置表单:根据选中提供者的 `config_schema` 动态渲染配置表单(支持 string、integer、number 类型),实现表单校验。
|
||||||
|
- AC: [AC-ASA-09, AC-ASA-10]
|
||||||
|
|
||||||
|
- [ ] (P5-04) 当前配置加载:页面初始化时调用 `/admin/embedding/config` 获取当前配置,填充表单默认值。
|
||||||
|
- AC: [AC-ASA-08]
|
||||||
|
|
||||||
|
- [ ] (P5-05) 配置保存功能:实现保存按钮,调用 `PUT /admin/embedding/config`,处理成功/失败响应并提示用户。
|
||||||
|
- AC: [AC-ASA-10]
|
||||||
|
|
||||||
|
- [x] (P5-06) 测试连接功能:实现测试按钮,调用 `POST /admin/embedding/test`,展示测试结果(success、dimension、latency_ms、message)。
|
||||||
|
- AC: [AC-ASA-11]
|
||||||
|
|
||||||
|
- [x] (P5-07) 测试失败错误展示:测试失败时展示详细错误信息(error 字段),并提供排查建议。
|
||||||
|
- AC: [AC-ASA-12]
|
||||||
|
|
||||||
|
- [ ] (P5-08) 支持格式展示:调用 `/admin/embedding/formats` 获取支持的文档格式列表,以标签或卡片形式展示。
|
||||||
|
- AC: [AC-ASA-13]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5 任务进度追踪
|
||||||
|
|
||||||
|
| 任务 | 描述 | 状态 |
|
||||||
|
|------|------|------|
|
||||||
|
| P5-01 | 嵌入模型配置页面骨架 | ⏳ 待处理 |
|
||||||
|
| P5-02 | 提供者选择组件 | ✅ 已完成 |
|
||||||
|
| P5-03 | 动态配置表单 | ✅ 已完成 |
|
||||||
|
| P5-04 | 当前配置加载 | ⏳ 待处理 |
|
||||||
|
| P5-05 | 配置保存功能 | ⏳ 待处理 |
|
||||||
|
| P5-06 | 测试连接功能 | ✅ 已完成 |
|
||||||
|
| P5-07 | 测试失败错误展示 | ✅ 已完成 |
|
||||||
|
| P5-08 | 支持格式展示 | ⏳ 待处理 |
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue