@@ -141,11 +141,12 @@
< th class = "px-4 py-3 text-left text-sm font-medium text-gray-600" > ID< / th >
< th class = "px-4 py-3 text-left text-sm font-medium text-gray-600" > 名称< / th >
< th class = "px-4 py-3 text-left text-sm font-medium text-gray-600" > 类型< / th >
< th class = "px-4 py-3 text-left text-sm font-medium text-gray-600" > 参数字段< / th >
< th class = "px-4 py-3 text-left text-sm font-medium text-gray-600" > 子类别< / th >
< th class = "px-4 py-3 text-center text-sm font-medium text-gray-600" > 操作< / th >
< / tr >
< / thead >
< tbody id = "admin-categories-table" > < tr > < td colspan = "6 " class = "text-center text-gray-400 py-8" > 加载中...< / td > < / tr > < / tbody >
< tbody id = "admin-categories-table" > < tr > < td colspan = "7 " class = "text-center text-gray-400 py-8" > 加载中...< / td > < / tr > < / tbody >
< / table >
< / div >
< / section >
@@ -392,21 +393,54 @@
<!-- 子类别编辑弹框 -->
< div id = "subcategoryModal" class = "fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center" >
< div class = "bg-white rounded-xl max-w-lg w-full mx-4" >
< div class = "p-6 border-b flex justify-between items-center" >
< div class = "bg-white rounded-xl max-w-2x l w-full mx-4 max-h-[90vh] overflow-auto " >
< div class = "p-6 border-b flex justify-between items-center sticky top-0 bg-white z-10 " >
< h2 class = "text-xl font-bold text-gray-800" id = "subcategoryModalTitle" > < i class = "ri-folder-line mr-2" > < / i > 添加子类别< / h2 >
< button onclick = "closeSubcategoryModal()" class = "text-gray-400 hover:text-gray-600" > < i class = "ri-close-line text-2xl" > < / i > < / button >
< / div >
< div id = "subcategoryModalContent" class = "p-6" >
<!-- 动态内容 -->
< / div >
< div class = "p-6 border-t flex justify-end gap-4" >
< div class = "p-6 border-t flex justify-end gap-4 sticky bottom-0 bg-white " >
< button onclick = "closeSubcategoryModal()" class = "px-4 py-2 bg-gray-200 text-gray-600 rounded-lg hover:bg-gray-300" > 取消< / button >
< button onclick = "saveSubcategory()" class = "px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700" > < i class = "ri-save-line mr-1" > < / i > 保存< / button >
< / div >
< / div >
< / div >
<!-- 参数字段编辑弹框 -->
< div id = "fieldModal" class = "fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center" >
< div class = "bg-white rounded-xl max-w-lg w-full mx-4" >
< div class = "p-6 border-b flex justify-between items-center" >
< h2 class = "text-xl font-bold text-gray-800" id = "fieldModalTitle" > < i class = "ri-list-settings-line mr-2" > < / i > 添加参数字段< / h2 >
< button onclick = "closeFieldModal()" class = "text-gray-400 hover:text-gray-600" > < i class = "ri-close-line text-2xl" > < / i > < / button >
< / div >
< div id = "fieldModalContent" class = "p-6" >
< div class = "space-y-4" >
< div > < label class = "text-sm text-gray-600 mb-1 block" > 字段名 *< / label > < input type = "text" id = "field_key" class = "w-full px-3 py-2 border rounded-lg" placeholder = "如: context_length" > < / div >
< div > < label class = "text-sm text-gray-600 mb-1 block" > 显示名 *< / label > < input type = "text" id = "field_label" class = "w-full px-3 py-2 border rounded-lg" placeholder = "如:上下文长度" > < / div >
< div > < label class = "text-sm text-gray-600 mb-1 block" > 字段类型< / label > < select id = "field_type" class = "w-full px-3 py-2 border rounded-lg" >
< option value = "text" > 文本< / option >
< option value = "number" > 数字< / option >
< option value = "date" > 日期< / option >
< option value = "boolean" > 布尔值< / option >
< option value = "json" > JSON对象< / option >
< option value = "url" > URL链接< / option >
< / select > < / div >
< div > < label class = "text-sm text-gray-600 mb-1 block" > 说明< / label > < textarea id = "field_desc" rows = "2" class = "w-full px-3 py-2 border rounded-lg" placeholder = "字段用途说明" > < / textarea > < / div >
< div class = "flex items-center gap-2" >
< input type = "checkbox" id = "field_required" class = "rounded" >
< label class = "text-sm text-gray-600" > 必填字段< / label >
< / div >
< / div >
< / div >
< div class = "p-6 border-t flex justify-end gap-4" >
< button onclick = "closeFieldModal()" class = "px-4 py-2 bg-gray-200 text-gray-600 rounded-lg hover:bg-gray-300" > 取消< / button >
< button onclick = "saveField()" class = "px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700" > < i class = "ri-save-line mr-1" > < / i > 保存< / button >
< / div >
< / div >
< / div >
<!-- 智能补充弹窗 -->
< div id = "smartUpdateModal" class = "fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center" >
< div class = "bg-white rounded-xl max-w-4xl w-full mx-4 max-h-[90vh] overflow-auto" >
@@ -859,6 +893,7 @@
document . getElementById ( 'admin-categories-table' ) . innerHTML = categories . map ( c => {
const isBuiltin = builtinCategories . includes ( c . id ) ;
const subcatCount = ( c . subcategories || [ ] ) . length ;
const fieldsCount = ( c . fields || [ ] ) . length ;
return `
<tr class="border-b hover:bg-gray-50 ${ c . visible === false ? 'bg-gray-100 opacity-60' : '' } ${ isBuiltin ? 'bg-indigo-50' : '' } ">
<td class="px-4 py-3"><div class="w-10 h-10 rounded-lg ${ colorMap [ c . color ] || 'bg-gray-100 text-gray-600' } flex items-center justify-center"><i class=" ${ c . icon } text-xl"></i></div></td>
@@ -867,11 +902,14 @@
<td class="px-4 py-3 text-sm">
${ isBuiltin ? '<span class="px-2 py-1 bg-indigo-100 text-indigo-600 rounded text-xs">内置</span>' : '<span class="text-gray-500">自定义</span>' }
</td>
<td class="px-4 py-3 text-sm">
${ fieldsCount > 0 ? ` <span class="px-2 py-1 bg-blue-100 text-blue-600 rounded text-xs"> ${ fieldsCount } 个</span> ` : '<span class="text-gray-400">无</span>' }
</td>
<td class="px-4 py-3 text-sm">
${ subcatCount > 0 ? ` <span class="px-2 py-1 bg-green-100 text-green-600 rounded text-xs"> ${ subcatCount } 个</span> ` : '<span class="text-gray-400">无</span>' }
</td>
<td class="px-4 py-3 text-center">
<button onclick="editItem('category', ' ${ c . id } ')" class="text-blue-600 hover:text-blue-800 mr-2" title="编辑子类别 "><i class="ri-edit-line"></i></button>
<button onclick="editItem('category', ' ${ c . id } ')" class="text-blue-600 hover:text-blue-800 mr-2" title="编辑"><i class="ri-edit-line"></i></button>
${ ! isBuiltin ? ` <button onclick="deleteItem('category', ' ${ c . id } ')" class="text-red-600 hover:text-red-800" title="删除"><i class="ri-delete-bin-line"></i></button> ` : '<span class="text-gray-300 cursor-not-allowed"><i class="ri-delete-bin-line"></i></span>' }
</td>
</tr>
@@ -1136,6 +1174,12 @@
return ;
}
}
else if ( key === 'fields' ) {
// 解析参数字段JSON
try { data [ key ] = JSON . parse ( value ) ; } catch {
data [ key ] = [ ] ;
}
}
else data [ key ] = value ;
}
} ) ;
@@ -1317,9 +1361,12 @@
// 内置类别只显示子类别管理
if ( isBuiltin ) {
const fields = data . fields || [ ] ;
window . currentEditingFields = JSON . parse ( JSON . stringify ( fields ) ) ;
return ` <form id="itemForm" class="space-y-4">
<div class="bg-indigo-50 rounded-lg p-4 mb-4">
<p class="text-sm text-indigo-700"><i class="ri-information-line mr-1"></i>内置分类的基础信息不可修改,只可编辑子类别配置。</p>
<p class="text-sm text-indigo-700"><i class="ri-information-line mr-1"></i>内置分类的基础信息不可修改,只可编辑参数字段和 子类别配置。</p>
</div>
<div class="grid grid-cols-2 gap-4 bg-gray-50 p-4 rounded-lg">
<div><label class="text-sm text-gray-500 mb-1 block">ID</label><div class="text-gray-700 font-mono"> ${ data . id } </div></div>
@@ -1335,6 +1382,20 @@
<input type="hidden" name="visible" value=" ${ data . visible !== false ? 'true' : 'false' } ">
<input type="hidden" name="description" value=" ${ data . description || '' } ">
<!-- 参数字段管理 -->
<div class="border-t pt-4">
<div class="flex justify-between items-center mb-3">
<label class="text-sm font-medium text-gray-700"><i class="ri-list-settings-line mr-1"></i>参数字段管理</label>
<button onclick="openFieldAddModal('category')" class="px-3 py-1.5 bg-indigo-600 text-white rounded-lg text-sm hover:bg-indigo-700">
<i class="ri-add-line mr-1"></i>添加字段
</button>
</div>
<div id="fieldsList" class="space-y-2">
${ renderFieldsList ( fields ) }
</div>
<input type="hidden" name="fields" id="fieldsHidden" value=' ${ JSON . stringify ( fields ) } '>
</div>
<!-- 子类别管理 -->
<div class="border-t pt-4">
<div class="flex justify-between items-center mb-3">
@@ -1353,6 +1414,9 @@
// 自定义类别完整编辑表单
const autoId = data . id || generateId ( ) ;
const fields = data . fields || [ ] ;
window . currentEditingFields = JSON . parse ( JSON . stringify ( fields ) ) ;
return ` <form id="itemForm" class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div><label class="text-sm text-gray-600 mb-1 block">ID</label><input type="text" name="id" value=" ${ autoId } " readonly class="w-full px-3 py-2 border rounded-lg bg-gray-100 text-gray-500 font-mono text-xs"><p class="text-xs text-gray-400 mt-1">自动生成,无需填写</p></div>
@@ -1374,6 +1438,20 @@
</div>
<div><label class="text-sm text-gray-600 mb-1 block">描述</label><textarea name="description" rows="2" class="w-full px-3 py-2 border rounded-lg" placeholder="分类描述"> ${ data . description || '' } </textarea></div>
<!-- 参数字段管理 -->
<div class="border-t pt-4">
<div class="flex justify-between items-center mb-3">
<label class="text-sm font-medium text-gray-700"><i class="ri-list-settings-line mr-1"></i>参数字段管理(基础字段,所有子类别共享)</label>
<button onclick="openFieldAddModal('category')" class="px-3 py-1.5 bg-indigo-600 text-white rounded-lg text-sm hover:bg-indigo-700">
<i class="ri-add-line mr-1"></i>添加字段
</button>
</div>
<div id="fieldsList" class="space-y-2">
${ renderFieldsList ( fields ) }
</div>
<input type="hidden" name="fields" id="fieldsHidden" value=' ${ JSON . stringify ( fields ) } '>
</div>
<!-- 子类别管理 -->
<div class="border-t pt-4">
<div class="flex justify-between items-center mb-3">
@@ -1404,7 +1482,7 @@
</div>
<div>
<div class="font-medium text-gray-800"> ${ sub . name } </div>
<div class="text-xs text-gray-500">ID: ${ sub . id } | 特性: ${ ( sub . key _features || [ ] ) . join ( ', ' ) } </div>
<div class="text-xs text-gray-500">ID: ${ sub . id } | 特性: ${ ( sub . key _features || [ ] ) . join ( ', ' ) } | 额外字段: ${ ( sub . extra _fields || [ ] ) . length } 个 </div>
</div>
</div>
<div class="flex gap-2 opacity-0 group-hover:opacity-100 transition">
@@ -1419,6 +1497,124 @@
` ) . join ( '' ) ;
}
// ============ 参数字段管理 ============
let currentFieldTarget = '' ; // 'category' 或 'subcategory'
let currentFieldIndex = - 1 ;
// 渲染参数字段列表
function renderFieldsList ( fields ) {
if ( ! fields || fields . length === 0 ) {
return '<div class="text-gray-400 text-sm py-4 text-center bg-gray-50 rounded-lg">暂无参数字段,点击上方按钮添加</div>' ;
}
const typeLabels = { text : '文本' , number : '数字' , date : '日期' , boolean : '布尔' , json : 'JSON' , url : 'URL' } ;
return fields . map ( ( field , index ) => `
<div class="bg-gray-50 rounded-lg p-3 flex justify-between items-center group hover:bg-gray-100">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-lg bg-indigo-100 flex items-center justify-center font-mono text-xs text-indigo-600">
${ field . key ? field . key . substring ( 0 , 4 ) : '-' }
</div>
<div>
<div class="font-medium text-gray-800"> ${ field . label || field . key } </div>
<div class="text-xs text-gray-500">
字段: ${ field . key } | 类型: ${ typeLabels [ field . type ] || '文本' } |
${ field . required ? '<span class="text-red-500">必填</span>' : '可选' }
${ field . description ? ` | ${ field . description } ` : '' }
</div>
</div>
</div>
<div class="flex gap-2 opacity-0 group-hover:opacity-100 transition">
<button onclick="editField( ${ index } )" class="px-2 py-1 text-blue-600 hover:bg-blue-50 rounded text-sm">
<i class="ri-edit-line"></i>
</button>
<button onclick="deleteField( ${ index } )" class="px-2 py-1 text-red-600 hover:bg-red-50 rounded text-sm">
<i class="ri-delete-bin-line"></i>
</button>
</div>
</div>
` ) . join ( '' ) ;
}
// 打开添加字段弹框
function openFieldAddModal ( target ) {
currentFieldTarget = target ;
currentFieldIndex = - 1 ;
document . getElementById ( 'fieldModalTitle' ) . textContent = '添加参数字段' ;
document . getElementById ( 'field_key' ) . value = '' ;
document . getElementById ( 'field_label' ) . value = '' ;
document . getElementById ( 'field_type' ) . value = 'text' ;
document . getElementById ( 'field_desc' ) . value = '' ;
document . getElementById ( 'field_required' ) . checked = false ;
document . getElementById ( 'fieldModal' ) . classList . remove ( 'hidden' ) ;
}
// 编辑字段
function editField ( index ) {
currentFieldTarget = 'category' ;
currentFieldIndex = index ;
const field = window . currentEditingFields [ index ] ;
document . getElementById ( 'fieldModalTitle' ) . textContent = '编辑参数字段' ;
document . getElementById ( 'field_key' ) . value = field . key || '' ;
document . getElementById ( 'field_label' ) . value = field . label || '' ;
document . getElementById ( 'field_type' ) . value = field . type || 'text' ;
document . getElementById ( 'field_desc' ) . value = field . description || '' ;
document . getElementById ( 'field_required' ) . checked = field . required || false ;
document . getElementById ( 'fieldModal' ) . classList . remove ( 'hidden' ) ;
}
// 删除字段
function deleteField ( index ) {
if ( ! confirm ( '确定删除此字段?' ) ) return ;
window . currentEditingFields . splice ( index , 1 ) ;
document . getElementById ( 'fieldsList' ) . innerHTML = renderFieldsList ( window . currentEditingFields ) ;
document . getElementById ( 'fieldsHidden' ) . value = JSON . stringify ( window . currentEditingFields ) ;
}
// 关闭字段弹框
function closeFieldModal ( ) {
document . getElementById ( 'fieldModal' ) . classList . add ( 'hidden' ) ;
}
// 保存字段
function saveField ( ) {
const key = document . getElementById ( 'field_key' ) . value . trim ( ) ;
const label = document . getElementById ( 'field_label' ) . value . trim ( ) ;
const type = document . getElementById ( 'field_type' ) . value ;
const description = document . getElementById ( 'field_desc' ) . value . trim ( ) ;
const required = document . getElementById ( 'field_required' ) . checked ;
if ( ! key || ! label ) {
alert ( '字段名和显示名不能为空' ) ;
return ;
}
const field = { key , label , type , description , required } ;
if ( currentFieldTarget === 'subcategory' ) {
// 子类别额外字段
if ( currentFieldIndex === - 1 ) {
window . currentEditingSubcategoryFields . push ( field ) ;
} else {
window . currentEditingSubcategoryFields [ currentFieldIndex ] = field ;
}
document . getElementById ( 'subcategoryFieldsList' ) . innerHTML = renderFieldsList ( window . currentEditingSubcategoryFields ) ;
} else {
// 类别基础字段
if ( currentFieldIndex === - 1 ) {
window . currentEditingFields . push ( field ) ;
} else {
window . currentEditingFields [ currentFieldIndex ] = field ;
}
document . getElementById ( 'fieldsList' ) . innerHTML = renderFieldsList ( window . currentEditingFields ) ;
document . getElementById ( 'fieldsHidden' ) . value = JSON . stringify ( window . currentEditingFields ) ;
}
closeFieldModal ( ) ;
}
// 打开子类别添加弹框
function openSubcategoryAddModal ( ) {
document . getElementById ( 'subcategoryModalTitle' ) . textContent = '添加子类别' ;
@@ -1450,6 +1646,8 @@
const featureLabels = data . feature _labels || { } ;
const featureLabelsStr = Object . entries ( featureLabels ) . map ( ( [ k , v ] ) => ` ${ k } : ${ v } ` ) . join ( ', ' ) ;
const autoSubId = data . id || generateId ( ) ;
const extraFields = data . extra _fields || [ ] ;
window . currentEditingSubcategoryFields = JSON . parse ( JSON . stringify ( extraFields ) ) ;
return ` <div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
@@ -1458,18 +1656,44 @@
<div><label class="text-sm text-gray-600 mb-1 block">图标</label><input type="text" id="sub_icon" value=" ${ data . icon || 'ri-folder-line' } " class="w-full px-3 py-2 border rounded-lg" placeholder="ri-folder-line"></div>
</div>
<div>
<label class="text-sm text-gray-600 mb-1 block">关键特性字段</label>
<label class="text-sm text-gray-600 mb-1 block">关键特性字段(表格显示的关键列) </label>
<input type="text" id="sub_key_features" value=" ${ keyFeatures } " class="w-full px-3 py-2 border rounded-lg" placeholder="context_length, mmlu, input_price">
<p class="text-xs text-gray-500 mt-1">逗号分隔,如: context_length, mmlu, input_price </p>
<p class="text-xs text-gray-500 mt-1">逗号分隔,选择要重点显示的字段 </p>
</div>
<div>
<label class="text-sm text-gray-600 mb-1 block">特性标签(显示名)</label>
<label class="text-sm text-gray-600 mb-1 block">特性标签(显示名映射 ) </label>
<input type="text" id="sub_feature_labels" value=" ${ featureLabelsStr } " class="w-full px-3 py-2 border rounded-lg" placeholder="context_length:上下文, mmlu:MMLU">
<p class="text-xs text-gray-500 mt-1">格式:字段名:显示名,逗号分隔</p>
</div>
<!-- 额外参数字段(子类别特有) -->
<div class="border-t pt-4">
<div class="flex justify-between items-center mb-3">
<label class="text-sm font-medium text-gray-700"><i class="ri-list-settings-line mr-1"></i>额外参数字段(子类别特有,继承父类别字段)</label>
<button onclick="openSubcategoryFieldAddModal()" class="px-3 py-1.5 bg-indigo-600 text-white rounded-lg text-sm hover:bg-indigo-700">
<i class="ri-add-line mr-1"></i>添加字段
</button>
</div>
<div id="subcategoryFieldsList" class="space-y-2">
${ renderFieldsList ( extraFields ) }
</div>
</div>
</div> ` ;
}
// 子类别字段添加弹框
function openSubcategoryFieldAddModal ( ) {
currentFieldTarget = 'subcategory' ;
currentFieldIndex = - 1 ;
document . getElementById ( 'fieldModalTitle' ) . textContent = '添加额外参数字段' ;
document . getElementById ( 'field_key' ) . value = '' ;
document . getElementById ( 'field_label' ) . value = '' ;
document . getElementById ( 'field_type' ) . value = 'text' ;
document . getElementById ( 'field_desc' ) . value = '' ;
document . getElementById ( 'field_required' ) . checked = false ;
document . getElementById ( 'fieldModal' ) . classList . remove ( 'hidden' ) ;
}
// 保存子类别
function saveSubcategory ( ) {
const id = document . getElementById ( 'sub_id' ) . value . trim ( ) ;
@@ -1483,8 +1707,6 @@
return ;
}
// ID自动生成, 无需校验
// 解析 key_features
const key _features = keyFeaturesStr ? keyFeaturesStr . split ( ',' ) . map ( s => s . trim ( ) ) . filter ( s => s ) : [ ] ;
@@ -1504,22 +1726,19 @@
name ,
icon ,
key _features ,
feature _labels
feature _labels ,
extra _fields : window . currentEditingSubcategoryFields || [ ]
} ;
if ( window . editingSubcategoryIndex === - 1 ) {
// 添加新子类别
window . currentEditingSubcategories . push ( subcategory ) ;
} else {
// 编辑现有子类别
window . currentEditingSubcategories [ window . editingSubcategoryIndex ] = subcategory ;
}
// 更新显示
document . getElementById ( 'subcategoriesList' ) . innerHTML = renderSubcategoriesList ( window . currentEditingSubcategories ) ;
document . getElementById ( 'subcategoriesHidden' ) . value = JSON . stringify ( window . currentEditingSubcategories ) ;
// 关闭弹框
closeSubcategoryModal ( ) ;
}
@@ -1672,6 +1891,7 @@
document . getElementById ( 'rawDataModal' ) . addEventListener ( 'click' , function ( e ) { if ( e . target === this ) closeRawDataModal ( ) ; } ) ;
document . getElementById ( 'subcategoryModal' ) . addEventListener ( 'click' , function ( e ) { if ( e . target === this ) closeSubcategoryModal ( ) ; } ) ;
document . getElementById ( 'smartUpdateModal' ) . addEventListener ( 'click' , function ( e ) { if ( e . target === this ) closeSmartUpdateModal ( ) ; } ) ;
document . getElementById ( 'fieldModal' ) . addEventListener ( 'click' , function ( e ) { if ( e . target === this ) closeFieldModal ( ) ; } ) ;
// ============ 智能添加功能 ============