Enhance ToolScaffolder with Pattern and Examples validation, support AI prompt generation for Pattern
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 6m22s

This commit is contained in:
jade
2026-08-14 10:29:27 +09:00
parent b8d6fa28df
commit b53dfe3601
3 changed files with 105 additions and 55 deletions

View File

@@ -1181,7 +1181,8 @@
<th style="min-width: 170px;">Enum values / List item type</th>
<th style="min-width: 260px;">Object list item fields (JSON)</th>
<th style="min-width: 210px;">Description</th>
<th style="min-width: 180px;">Example</th>
<th style="min-width: 150px;">Pattern (Regex)</th>
<th style="min-width: 180px;">Examples (Enter 단위 구분)</th>
<th style="min-width: 125px;">Required</th>
<th style="width: 1%;">삭제</th>
</tr>
@@ -1205,7 +1206,7 @@
<!-- Sub Field Editor Modal -->
<div class="modal fade" id="subFieldEditorModal" tabindex="-1" aria-hidden="true" style="z-index: 1060;">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">하위 필드(itemFields) 편집</h5>
@@ -1218,9 +1219,10 @@
<tr>
<th style="width:15%">Name</th>
<th style="width:15%">Type</th>
<th style="width:20%">Details</th>
<th style="width:20%">Description</th>
<th style="width:15%">Example</th>
<th style="width:15%">Details</th>
<th style="width:15%">Description</th>
<th style="width:10%">Pattern</th>
<th style="width:15%">Examples</th>
<th style="width:10%">Required</th>
<th style="width:5%"></th>
</tr>
@@ -1728,7 +1730,7 @@
}
}
const fieldTypes = ['String', 'Integer', 'Long', 'Double', 'Boolean', 'BigDecimal', 'Enum', 'List'];
const fieldTypes = ['String', 'Integer', 'Long', 'Double', 'Boolean', 'BigDecimal', 'List'];
const typeExamples = {
String: 'example',
Integer: '1',
@@ -1736,7 +1738,6 @@
Double: '1.0',
Boolean: 'true',
BigDecimal: '1000.00',
Enum: 'OPEN',
List: 'C001'
};
const fieldTemplates = {
@@ -1805,24 +1806,27 @@
};
const n = makeInput(field.name, 'name'); n.dataset.field = 'name';
const d = makeInput(field.description, 'desc'); d.dataset.field = 'description';
const e = makeInput(field.example, 'ex'); e.dataset.field = 'example';
const p = makeInput(field.pattern, 'pattern'); p.dataset.field = 'pattern';
const e = document.createElement('textarea');
e.className = 'form-control form-control-sm';
e.rows = 2; e.dataset.field = 'example';
e.value = (field.examples || []).join('\n') || '';
const t = document.createElement('select');
t.className = 'form-select form-select-sm'; t.dataset.field = 'type';
['String', 'Integer', 'Long', 'Double', 'Boolean', 'BigDecimal', 'Enum'].forEach(type => {
['String', 'Integer', 'Long', 'Double', 'Boolean', 'BigDecimal'].forEach(type => {
t.add(new Option(type, type, false, (field.type || 'String') === type));
});
const det = makeInput(field.type === 'Enum' ? (field.enumValues||[]).join(',') : '', 'Enum csv');
const det = makeInput(field.type !== 'List' ? (field.enumValues||[]).join(',') : '', 'Enum csv (콤마 구분)');
det.dataset.field = 'details';
t.addEventListener('change', () => {
if (t.value === 'Enum') det.placeholder = 'Enum csv';
else det.value = '';
det.placeholder = 'Enum csv (콤마 구분)';
});
const req = document.createElement('select');
req.className = 'form-select form-select-sm'; req.dataset.field = 'required';
req.add(new Option('Required', 'true', false, field.required === true || field.required === 'true'));
req.add(new Option('Optional', 'false', false, !(field.required === true || field.required === 'true')));
[n, t, det, d, e, req].forEach(ctrl => {
[n, t, det, d, p, e, req].forEach(ctrl => {
const td = document.createElement('td'); td.appendChild(ctrl); row.appendChild(td);
});
const del = document.createElement('button');
@@ -1841,9 +1845,10 @@
name: row.querySelector('[data-field="name"]').value.trim(),
type,
description: row.querySelector('[data-field="description"]').value.trim(),
example: row.querySelector('[data-field="example"]').value.trim(),
pattern: row.querySelector('[data-field="pattern"]').value.trim(),
examples: row.querySelector('[data-field="example"]').value.split('\n').map(s=>s.trim()).filter(Boolean),
required: row.querySelector('[data-field="required"]').value === 'true',
enumValues: type === 'Enum' ? details.split(',').map(s=>s.trim()).filter(Boolean) : [],
enumValues: type !== 'List' ? details.split(',').map(s=>s.trim()).filter(Boolean) : [],
itemType: null, itemFields: []
};
}).filter(f => f.name);
@@ -1898,8 +1903,16 @@
nameInput.dataset.field = 'name';
const descriptionInput = makeInput(field.description, 'e.g. Employee ID');
descriptionInput.dataset.field = 'description';
const exampleInput = makeInput(field.example, 'e.g. EMP10001');
const patternInput = makeInput(field.pattern, 'e.g. ^[0-9]{4}$');
patternInput.dataset.field = 'pattern';
const exampleInput = document.createElement('textarea');
exampleInput.className = 'form-control form-control-sm';
exampleInput.placeholder = 'e.g. EMP10001\nEMP10002';
exampleInput.rows = 2;
exampleInput.dataset.field = 'example';
exampleInput.value = (field.examples || []).join('\n') || '';
const typeSelect = document.createElement('select');
typeSelect.className = 'form-select form-select-sm';
@@ -1917,13 +1930,13 @@
input.add(new Option(itemType, itemType, false, (value || 'String') === itemType));
});
} else {
input = makeInput(type === 'Enum' ? value : '', 'Enum: OPEN, CLOSED');
input = makeInput(value, 'Enum csv (콤마 구분)');
}
input.dataset.field = 'details';
return input;
};
let detailsInput = createDetailsInput(field.type || 'String',
field.type === 'Enum' ? (field.enumValues || []).join(', ') : field.itemType);
field.type !== 'List' ? (field.enumValues || []).join(', ') : field.itemType);
const requiredSelect = document.createElement('select');
requiredSelect.className = 'form-select form-select-sm';
@@ -1978,12 +1991,12 @@
});
bindDetailsInput();
updateObjectListFieldsState();
[nameInput, descriptionInput, exampleInput, requiredSelect, itemFieldsInput].forEach(control => {
[nameInput, descriptionInput, patternInput, exampleInput, requiredSelect, itemFieldsInput].forEach(control => {
control.addEventListener('input', updateFieldEditorPreview);
control.addEventListener('change', updateFieldEditorPreview);
});
[nameInput, typeSelect, detailsInput, itemFieldsContainer, descriptionInput, exampleInput, requiredSelect].forEach(control => {
[nameInput, typeSelect, detailsInput, itemFieldsContainer, descriptionInput, patternInput, exampleInput, requiredSelect].forEach(control => {
const cell = document.createElement('td');
cell.appendChild(control);
row.appendChild(cell);
@@ -2021,9 +2034,10 @@
return {
name: row.querySelector('[data-field="name"]').value.trim(), type,
description: row.querySelector('[data-field="description"]').value.trim(),
example: row.querySelector('[data-field="example"]').value.trim(),
pattern: row.querySelector('[data-field="pattern"]').value.trim(),
examples: row.querySelector('[data-field="example"]').value.split('\n').map(e => e.trim()).filter(Boolean),
required: row.querySelector('[data-field="required"]').value === 'true',
enumValues: type === 'Enum' ? details.split(',').map(value => value.trim()).filter(Boolean) : [],
enumValues: type !== 'List' ? details.split(',').map(value => value.trim()).filter(Boolean) : [],
itemType: type === 'List' ? (details || 'String') : null,
itemFields
};