feat: add field_roles configuration and role filter to metadata schema page [AC-MRS-06,15]
This commit is contained in:
parent
5c1f311656
commit
eb7bc7722b
|
|
@ -5,12 +5,20 @@ import type {
|
||||||
MetadataFieldUpdateRequest,
|
MetadataFieldUpdateRequest,
|
||||||
MetadataFieldListResponse,
|
MetadataFieldListResponse,
|
||||||
MetadataPayload,
|
MetadataPayload,
|
||||||
MetadataScope
|
MetadataScope,
|
||||||
|
FieldRole
|
||||||
} from '@/types/metadata'
|
} from '@/types/metadata'
|
||||||
|
|
||||||
export const metadataSchemaApi = {
|
export const metadataSchemaApi = {
|
||||||
list: (status?: 'draft' | 'active' | 'deprecated') =>
|
list: (status?: 'draft' | 'active' | 'deprecated', fieldRole?: FieldRole) =>
|
||||||
request<MetadataFieldListResponse>({ method: 'GET', url: '/admin/metadata-schemas', params: status ? { status } : {} }),
|
request<MetadataFieldListResponse>({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/admin/metadata-schemas',
|
||||||
|
params: {
|
||||||
|
...(status ? { status } : {}),
|
||||||
|
...(fieldRole ? { field_role: fieldRole } : {})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
get: (id: string) =>
|
get: (id: string) =>
|
||||||
request<MetadataFieldDefinition>({ method: 'GET', url: `/admin/metadata-schemas/${id}` }),
|
request<MetadataFieldDefinition>({ method: 'GET', url: `/admin/metadata-schemas/${id}` }),
|
||||||
|
|
@ -31,6 +39,13 @@ export const metadataSchemaApi = {
|
||||||
params: { scope, include_deprecated: includeDeprecated }
|
params: { scope, include_deprecated: includeDeprecated }
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
getByRole: (role: FieldRole, includeDeprecated = false) =>
|
||||||
|
request<MetadataFieldListResponse>({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/admin/metadata-schemas/by-role',
|
||||||
|
params: { role, include_deprecated: includeDeprecated }
|
||||||
|
}),
|
||||||
|
|
||||||
validate: (metadata: MetadataPayload, scope?: MetadataScope) =>
|
validate: (metadata: MetadataPayload, scope?: MetadataScope) =>
|
||||||
request<{ valid: boolean; errors?: { field_key: string; message: string }[] }>({
|
request<{ valid: boolean; errors?: { field_key: string; message: string }[] }>({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
<template>
|
||||||
|
<div class="field-roles-selector">
|
||||||
|
<label class="selector-label">
|
||||||
|
字段角色
|
||||||
|
<el-tooltip content="为字段分配角色,工具将按角色精准消费字段" placement="top">
|
||||||
|
<el-icon class="help-icon"><QuestionFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</label>
|
||||||
|
<el-checkbox-group v-model="selectedRoles" class="roles-checkbox-group">
|
||||||
|
<el-checkbox
|
||||||
|
v-for="role in availableRoles"
|
||||||
|
:key="role.value"
|
||||||
|
:value="role.value"
|
||||||
|
class="role-checkbox"
|
||||||
|
>
|
||||||
|
<div class="role-item">
|
||||||
|
<span class="role-label">{{ role.label }}</span>
|
||||||
|
<el-tooltip :content="role.description" placement="top">
|
||||||
|
<el-icon class="role-help-icon"><QuestionFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { QuestionFilled } from '@element-plus/icons-vue'
|
||||||
|
import type { FieldRole } from '@/types/metadata'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: FieldRole[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: FieldRole[]): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const availableRoles: { value: FieldRole; label: string; description: string }[] = [
|
||||||
|
{
|
||||||
|
value: 'resource_filter',
|
||||||
|
label: '资源过滤',
|
||||||
|
description: '用于 KB 文档检索时的元数据过滤,kb_search_dynamic 工具将消费此类字段'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'slot',
|
||||||
|
label: '运行时槽位',
|
||||||
|
description: '对话流程中的结构化槽位,用于信息收集,memory_recall 工具将消费此类字段'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'prompt_var',
|
||||||
|
label: '提示词变量',
|
||||||
|
description: '注入到 LLM Prompt 中的变量,template_engine 将消费此类字段'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'routing_signal',
|
||||||
|
label: '路由信号',
|
||||||
|
description: '用于意图路由和风险判断的信号,intent_hint/high_risk_check 工具将消费此类字段'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const selectedRoles = computed({
|
||||||
|
get: () => props.modelValue || [],
|
||||||
|
set: (val: FieldRole[]) => emit('update:modelValue', val)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.field-roles-selector {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roles-checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-checkbox {
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-label {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-help-icon {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
export type MetadataFieldType = 'string' | 'number' | 'boolean' | 'enum' | 'array_enum'
|
export type MetadataFieldType = 'string' | 'number' | 'boolean' | 'enum' | 'array_enum'
|
||||||
export type MetadataFieldStatus = 'draft' | 'active' | 'deprecated'
|
export type MetadataFieldStatus = 'draft' | 'active' | 'deprecated'
|
||||||
export type MetadataScope = 'kb_document' | 'intent_rule' | 'script_flow' | 'prompt_template'
|
export type MetadataScope = 'kb_document' | 'intent_rule' | 'script_flow' | 'prompt_template'
|
||||||
|
export type FieldRole = 'resource_filter' | 'slot' | 'prompt_var' | 'routing_signal'
|
||||||
|
|
||||||
export interface MetadataFieldDefinition {
|
export interface MetadataFieldDefinition {
|
||||||
id: string
|
id: string
|
||||||
|
|
@ -14,6 +15,7 @@ export interface MetadataFieldDefinition {
|
||||||
scope: MetadataScope[]
|
scope: MetadataScope[]
|
||||||
is_filterable: boolean
|
is_filterable: boolean
|
||||||
is_rank_feature: boolean
|
is_rank_feature: boolean
|
||||||
|
field_roles: FieldRole[]
|
||||||
status: MetadataFieldStatus
|
status: MetadataFieldStatus
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
|
|
@ -29,6 +31,7 @@ export interface MetadataFieldCreateRequest {
|
||||||
scope: MetadataScope[]
|
scope: MetadataScope[]
|
||||||
is_filterable?: boolean
|
is_filterable?: boolean
|
||||||
is_rank_feature?: boolean
|
is_rank_feature?: boolean
|
||||||
|
field_roles?: FieldRole[]
|
||||||
status: MetadataFieldStatus
|
status: MetadataFieldStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,6 +43,7 @@ export interface MetadataFieldUpdateRequest {
|
||||||
scope?: MetadataScope[]
|
scope?: MetadataScope[]
|
||||||
is_filterable?: boolean
|
is_filterable?: boolean
|
||||||
is_rank_feature?: boolean
|
is_rank_feature?: boolean
|
||||||
|
field_roles?: FieldRole[]
|
||||||
status?: MetadataFieldStatus
|
status?: MetadataFieldStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,3 +95,17 @@ export const STATUS_TAG_MAP: Record<MetadataFieldStatus, '' | 'success' | 'warni
|
||||||
active: 'success',
|
active: 'success',
|
||||||
deprecated: 'danger'
|
deprecated: 'danger'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const FIELD_ROLE_OPTIONS: { value: FieldRole; label: string; description: string }[] = [
|
||||||
|
{ value: 'resource_filter', label: '资源过滤', description: '用于 KB 文档检索时的元数据过滤' },
|
||||||
|
{ value: 'slot', label: '运行时槽位', description: '对话流程中的结构化槽位,用于信息收集' },
|
||||||
|
{ value: 'prompt_var', label: '提示词变量', description: '注入到 LLM Prompt 中的变量' },
|
||||||
|
{ value: 'routing_signal', label: '路由信号', description: '用于意图路由和风险判断的信号' }
|
||||||
|
]
|
||||||
|
|
||||||
|
export const FIELD_ROLE_TAG_MAP: Record<FieldRole, '' | 'success' | 'warning' | 'danger' | 'info'> = {
|
||||||
|
resource_filter: '',
|
||||||
|
slot: 'success',
|
||||||
|
prompt_var: 'warning',
|
||||||
|
routing_signal: 'danger'
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,14 @@
|
||||||
:value="opt.value"
|
:value="opt.value"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<el-select v-model="filterRole" placeholder="按角色筛选" clearable style="width: 140px;">
|
||||||
|
<el-option
|
||||||
|
v-for="opt in FIELD_ROLE_OPTIONS"
|
||||||
|
:key="opt.value"
|
||||||
|
:label="opt.label"
|
||||||
|
:value="opt.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
<el-button type="primary" @click="handleCreate">
|
<el-button type="primary" @click="handleCreate">
|
||||||
<el-icon><Plus /></el-icon>
|
<el-icon><Plus /></el-icon>
|
||||||
新建字段
|
新建字段
|
||||||
|
|
@ -57,6 +65,22 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column prop="field_roles" label="字段角色" min-width="200">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="role-tags">
|
||||||
|
<el-tag
|
||||||
|
v-for="role in row.field_roles"
|
||||||
|
:key="role"
|
||||||
|
:type="FIELD_ROLE_TAG_MAP[role as FieldRole]"
|
||||||
|
size="small"
|
||||||
|
class="role-tag"
|
||||||
|
>
|
||||||
|
{{ getRoleLabel(role) }}
|
||||||
|
</el-tag>
|
||||||
|
<span v-if="!row.field_roles || row.field_roles.length === 0" class="no-role">-</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="status" label="状态" width="100">
|
<el-table-column prop="status" label="状态" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="STATUS_TAG_MAP[row.status as MetadataFieldStatus]" size="small">
|
<el-tag :type="STATUS_TAG_MAP[row.status as MetadataFieldStatus]" size="small">
|
||||||
|
|
@ -167,6 +191,9 @@
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
</el-checkbox-group>
|
</el-checkbox-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="字段角色">
|
||||||
|
<FieldRolesSelector v-model="formData.field_roles" />
|
||||||
|
</el-form-item>
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-form-item label="是否必填">
|
<el-form-item label="是否必填">
|
||||||
|
|
@ -244,21 +271,26 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Plus, Edit, Delete, Switch, ArrowDown } from '@element-plus/icons-vue'
|
import { Plus, Edit, Delete, Switch, ArrowDown } from '@element-plus/icons-vue'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import { metadataSchemaApi } from '@/api/metadata-schema'
|
import { metadataSchemaApi } from '@/api/metadata-schema'
|
||||||
|
import FieldRolesSelector from '@/components/metadata/FieldRolesSelector.vue'
|
||||||
import {
|
import {
|
||||||
METADATA_STATUS_OPTIONS,
|
METADATA_STATUS_OPTIONS,
|
||||||
METADATA_SCOPE_OPTIONS,
|
METADATA_SCOPE_OPTIONS,
|
||||||
METADATA_TYPE_OPTIONS,
|
METADATA_TYPE_OPTIONS,
|
||||||
STATUS_TAG_MAP,
|
STATUS_TAG_MAP,
|
||||||
|
FIELD_ROLE_OPTIONS,
|
||||||
|
FIELD_ROLE_TAG_MAP,
|
||||||
type MetadataFieldDefinition,
|
type MetadataFieldDefinition,
|
||||||
type MetadataFieldCreateRequest,
|
type MetadataFieldCreateRequest,
|
||||||
type MetadataFieldUpdateRequest,
|
type MetadataFieldUpdateRequest,
|
||||||
type MetadataFieldStatus,
|
type MetadataFieldStatus,
|
||||||
type MetadataScope
|
type MetadataScope,
|
||||||
|
type FieldRole
|
||||||
} from '@/types/metadata'
|
} from '@/types/metadata'
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const fields = ref<MetadataFieldDefinition[]>([])
|
const fields = ref<MetadataFieldDefinition[]>([])
|
||||||
const filterStatus = ref<MetadataFieldStatus | ''>('')
|
const filterStatus = ref<MetadataFieldStatus | ''>('')
|
||||||
|
const filterRole = ref<FieldRole | ''>('')
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const isEdit = ref(false)
|
const isEdit = ref(false)
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
|
|
@ -276,6 +308,7 @@ const formData = reactive({
|
||||||
scope: [] as MetadataScope[],
|
scope: [] as MetadataScope[],
|
||||||
is_filterable: true,
|
is_filterable: true,
|
||||||
is_rank_feature: false,
|
is_rank_feature: false,
|
||||||
|
field_roles: [] as FieldRole[],
|
||||||
status: 'draft' as MetadataFieldStatus
|
status: 'draft' as MetadataFieldStatus
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -314,10 +347,14 @@ const getScopeLabel = (scope: MetadataScope) => {
|
||||||
return METADATA_SCOPE_OPTIONS.find(o => o.value === scope)?.label || scope
|
return METADATA_SCOPE_OPTIONS.find(o => o.value === scope)?.label || scope
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getRoleLabel = (role: FieldRole) => {
|
||||||
|
return FIELD_ROLE_OPTIONS.find(o => o.value === role)?.label || role
|
||||||
|
}
|
||||||
|
|
||||||
const fetchFields = async () => {
|
const fetchFields = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await metadataSchemaApi.list(filterStatus.value || undefined)
|
const res = await metadataSchemaApi.list(filterStatus.value || undefined, filterRole.value || undefined)
|
||||||
fields.value = res.items || []
|
fields.value = res.items || []
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
ElMessage.error(error.response?.data?.message || '获取元数据字段失败')
|
ElMessage.error(error.response?.data?.message || '获取元数据字段失败')
|
||||||
|
|
@ -339,6 +376,7 @@ const handleCreate = () => {
|
||||||
scope: [],
|
scope: [],
|
||||||
is_filterable: true,
|
is_filterable: true,
|
||||||
is_rank_feature: false,
|
is_rank_feature: false,
|
||||||
|
field_roles: [],
|
||||||
status: 'draft'
|
status: 'draft'
|
||||||
})
|
})
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
|
@ -357,6 +395,7 @@ const handleEdit = (field: MetadataFieldDefinition) => {
|
||||||
scope: [...field.scope],
|
scope: [...field.scope],
|
||||||
is_filterable: field.is_filterable,
|
is_filterable: field.is_filterable,
|
||||||
is_rank_feature: field.is_rank_feature,
|
is_rank_feature: field.is_rank_feature,
|
||||||
|
field_roles: field.field_roles || [],
|
||||||
status: field.status
|
status: field.status
|
||||||
})
|
})
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
|
@ -433,6 +472,7 @@ const handleSubmit = async () => {
|
||||||
scope: formData.scope,
|
scope: formData.scope,
|
||||||
is_filterable: formData.is_filterable,
|
is_filterable: formData.is_filterable,
|
||||||
is_rank_feature: formData.is_rank_feature,
|
is_rank_feature: formData.is_rank_feature,
|
||||||
|
field_roles: formData.field_roles,
|
||||||
status: formData.status
|
status: formData.status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -466,6 +506,10 @@ watch(filterStatus, () => {
|
||||||
fetchFields()
|
fetchFields()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(filterRole, () => {
|
||||||
|
fetchFields()
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchFields()
|
fetchFields()
|
||||||
})
|
})
|
||||||
|
|
@ -529,6 +573,20 @@ onMounted(() => {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.role-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-tag {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-role {
|
||||||
|
color: var(--el-text-color-placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
.field-hint {
|
.field-hint {
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue