mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-10 06:18:37 +02:00
Add files via upload
This commit is contained in:
@@ -31778,6 +31778,188 @@ html[data-theme="dark"] .form-group select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.workflow-tool-native-select {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.workflow-tool-select {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.workflow-tool-select-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
min-height: 38px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 7px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-size: 0.875rem;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.workflow-tool-select-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workflow-tool-select-caret {
|
||||
flex: 0 0 auto;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.75rem;
|
||||
line-height: 1;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.workflow-tool-select.open .workflow-tool-select-caret {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.workflow-tool-select-trigger:hover:not(:disabled) {
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.workflow-tool-select.open .workflow-tool-select-trigger {
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
|
||||
}
|
||||
|
||||
.workflow-tool-select.is-disabled .workflow-tool-select-trigger {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.56;
|
||||
background: var(--bg-secondary, #f5f6f8);
|
||||
}
|
||||
|
||||
.workflow-tool-select-dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 300;
|
||||
max-height: 280px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.workflow-tool-select.open .workflow-tool-select-dropdown {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.workflow-tool-select-search {
|
||||
flex-shrink: 0;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.workflow-tool-select-search-input {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
font-size: 0.8125rem;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
font-family: inherit;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.workflow-tool-select-search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.1);
|
||||
}
|
||||
|
||||
.workflow-tool-select-search-input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.workflow-tool-select-options {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.workflow-tool-select-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: 8px 10px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.8125rem;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.workflow-tool-select-option:hover {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.workflow-tool-select-option.is-selected {
|
||||
color: var(--accent-color);
|
||||
font-weight: 600;
|
||||
background: rgba(0, 102, 255, 0.08);
|
||||
}
|
||||
|
||||
.workflow-tool-select-check {
|
||||
flex: 0 0 14px;
|
||||
width: 14px;
|
||||
color: var(--accent-color);
|
||||
opacity: 0;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.workflow-tool-select-option.is-selected .workflow-tool-select-check {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.workflow-tool-select-label {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.workflow-tool-select-empty {
|
||||
padding: 10px 12px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
.workflow-page-content {
|
||||
grid-template-columns: clamp(180px, 14vw, 220px) minmax(0, 1fr);
|
||||
|
||||
@@ -3079,6 +3079,9 @@
|
||||
"inputKeys": "Input variables",
|
||||
"mcpTool": "MCP tool",
|
||||
"selectTool": "Select a tool",
|
||||
"searchTool": "Search tools…",
|
||||
"noToolsFound": "No matching tools",
|
||||
"noToolsAvailable": "No tools available",
|
||||
"toolDisabled": " (disabled)",
|
||||
"argumentsTemplate": "Arguments template",
|
||||
"argumentsStatic": "Tool arguments (JSON)",
|
||||
|
||||
@@ -3067,6 +3067,9 @@
|
||||
"inputKeys": "输入变量",
|
||||
"mcpTool": "MCP 工具",
|
||||
"selectTool": "请选择工具",
|
||||
"searchTool": "搜索工具…",
|
||||
"noToolsFound": "未找到匹配的工具",
|
||||
"noToolsAvailable": "暂无可用工具",
|
||||
"toolDisabled": "(未启用)",
|
||||
"argumentsTemplate": "参数模板",
|
||||
"argumentsStatic": "工具参数(JSON)",
|
||||
|
||||
+216
-1
@@ -23,6 +23,9 @@
|
||||
let selectedElement = null;
|
||||
let workflowToolOptions = [];
|
||||
let workflowToolsLoaded = false;
|
||||
const WORKFLOW_TOOL_SELECT_ID = 'workflow-tool-name';
|
||||
let workflowToolSelectRegistry = null;
|
||||
let workflowToolSelectDocBound = false;
|
||||
|
||||
const KNOWN_NODE_LABELS = {
|
||||
start: ['开始', 'Start'],
|
||||
@@ -359,6 +362,217 @@
|
||||
return workflowToolOptions;
|
||||
}
|
||||
|
||||
function workflowToolOptionLabel(tool) {
|
||||
return tool.key + (tool.enabled ? '' : _t('workflows.config.toolDisabled'));
|
||||
}
|
||||
|
||||
function closeWorkflowToolSelect() {
|
||||
const reg = workflowToolSelectRegistry;
|
||||
if (!reg || !reg.wrapper) return;
|
||||
reg.wrapper.classList.remove('open');
|
||||
if (reg.trigger) reg.trigger.setAttribute('aria-expanded', 'false');
|
||||
if (reg.searchInput) reg.searchInput.value = '';
|
||||
}
|
||||
|
||||
function createWorkflowToolOptionButton(value, label, selectedValue) {
|
||||
const item = document.createElement('button');
|
||||
item.type = 'button';
|
||||
item.className = 'workflow-tool-select-option';
|
||||
item.setAttribute('role', 'option');
|
||||
item.setAttribute('data-value', value);
|
||||
item.title = label;
|
||||
if (value === selectedValue) {
|
||||
item.classList.add('is-selected');
|
||||
item.setAttribute('aria-selected', 'true');
|
||||
} else {
|
||||
item.setAttribute('aria-selected', 'false');
|
||||
}
|
||||
const check = document.createElement('span');
|
||||
check.className = 'workflow-tool-select-check';
|
||||
check.setAttribute('aria-hidden', 'true');
|
||||
check.textContent = '✓';
|
||||
const labelEl = document.createElement('span');
|
||||
labelEl.className = 'workflow-tool-select-label';
|
||||
labelEl.textContent = label;
|
||||
labelEl.title = label;
|
||||
item.appendChild(check);
|
||||
item.appendChild(labelEl);
|
||||
return item;
|
||||
}
|
||||
|
||||
function renderWorkflowToolSelectOptions(reg, query) {
|
||||
const { select, optionsList } = reg;
|
||||
optionsList.innerHTML = '';
|
||||
const q = (query || '').trim().toLowerCase();
|
||||
let matchCount = 0;
|
||||
|
||||
Array.prototype.forEach.call(select.options, (opt) => {
|
||||
if (opt.value === '') {
|
||||
if (!q) {
|
||||
optionsList.appendChild(createWorkflowToolOptionButton(opt.value, opt.textContent || '', select.value));
|
||||
}
|
||||
return;
|
||||
}
|
||||
const label = opt.textContent || opt.value;
|
||||
if (q && !label.toLowerCase().includes(q) && !opt.value.toLowerCase().includes(q)) return;
|
||||
matchCount += 1;
|
||||
optionsList.appendChild(createWorkflowToolOptionButton(opt.value, label, select.value));
|
||||
});
|
||||
|
||||
if (matchCount === 0) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'workflow-tool-select-empty';
|
||||
empty.textContent = q
|
||||
? _t('workflows.config.noToolsFound')
|
||||
: _t('workflows.config.noToolsAvailable');
|
||||
optionsList.appendChild(empty);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureWorkflowToolSearchUi(reg) {
|
||||
if (reg.searchInput && reg.optionsList) return;
|
||||
const { dropdown } = reg;
|
||||
dropdown.innerHTML = '';
|
||||
|
||||
const searchWrap = document.createElement('div');
|
||||
searchWrap.className = 'workflow-tool-select-search';
|
||||
const searchInput = document.createElement('input');
|
||||
searchInput.type = 'search';
|
||||
searchInput.className = 'workflow-tool-select-search-input';
|
||||
searchInput.setAttribute('autocomplete', 'off');
|
||||
searchInput.setAttribute('data-i18n', 'workflows.config.searchTool');
|
||||
searchInput.setAttribute('data-i18n-attr', 'placeholder');
|
||||
searchInput.placeholder = _t('workflows.config.searchTool');
|
||||
searchWrap.appendChild(searchInput);
|
||||
dropdown.appendChild(searchWrap);
|
||||
reg.searchInput = searchInput;
|
||||
|
||||
const optionsList = document.createElement('div');
|
||||
optionsList.className = 'workflow-tool-select-options';
|
||||
dropdown.appendChild(optionsList);
|
||||
reg.optionsList = optionsList;
|
||||
|
||||
searchInput.addEventListener('input', () => renderWorkflowToolSelectOptions(reg, searchInput.value));
|
||||
searchInput.addEventListener('click', (e) => e.stopPropagation());
|
||||
searchInput.addEventListener('keydown', (e) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Escape') closeWorkflowToolSelect();
|
||||
});
|
||||
}
|
||||
|
||||
function syncWorkflowToolSelect() {
|
||||
const select = document.getElementById(WORKFLOW_TOOL_SELECT_ID);
|
||||
const reg = workflowToolSelectRegistry;
|
||||
if (!select || !reg || reg.select !== select) return;
|
||||
const selected = select.options[select.selectedIndex];
|
||||
reg.valueSpan.textContent = selected && selected.value
|
||||
? selected.textContent
|
||||
: _t('workflows.config.selectTool');
|
||||
if (reg.optionsList) {
|
||||
renderWorkflowToolSelectOptions(reg, reg.searchInput ? reg.searchInput.value : '');
|
||||
}
|
||||
reg.trigger.disabled = !!select.disabled;
|
||||
reg.wrapper.classList.toggle('is-disabled', !!select.disabled);
|
||||
}
|
||||
|
||||
function enhanceWorkflowToolSelect() {
|
||||
const select = document.getElementById(WORKFLOW_TOOL_SELECT_ID);
|
||||
if (!select) {
|
||||
workflowToolSelectRegistry = null;
|
||||
return;
|
||||
}
|
||||
if (select.dataset.workflowToolCustom === '1' && workflowToolSelectRegistry && workflowToolSelectRegistry.select === select) {
|
||||
syncWorkflowToolSelect();
|
||||
return;
|
||||
}
|
||||
workflowToolSelectRegistry = null;
|
||||
|
||||
select.dataset.workflowToolCustom = '1';
|
||||
select.classList.add('workflow-tool-native-select');
|
||||
select.tabIndex = -1;
|
||||
select.setAttribute('aria-hidden', 'true');
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'workflow-tool-select';
|
||||
|
||||
const trigger = document.createElement('button');
|
||||
trigger.type = 'button';
|
||||
trigger.className = 'workflow-tool-select-trigger';
|
||||
trigger.setAttribute('aria-haspopup', 'listbox');
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
const valueSpan = document.createElement('span');
|
||||
valueSpan.className = 'workflow-tool-select-value';
|
||||
trigger.appendChild(valueSpan);
|
||||
const caret = document.createElement('span');
|
||||
caret.className = 'workflow-tool-select-caret';
|
||||
caret.setAttribute('aria-hidden', 'true');
|
||||
caret.textContent = '▾';
|
||||
trigger.appendChild(caret);
|
||||
|
||||
const dropdown = document.createElement('div');
|
||||
dropdown.className = 'workflow-tool-select-dropdown';
|
||||
dropdown.setAttribute('role', 'listbox');
|
||||
|
||||
const parent = select.parentNode;
|
||||
parent.insertBefore(wrapper, select);
|
||||
wrapper.appendChild(trigger);
|
||||
wrapper.appendChild(dropdown);
|
||||
wrapper.appendChild(select);
|
||||
|
||||
workflowToolSelectRegistry = {
|
||||
wrapper,
|
||||
trigger,
|
||||
dropdown,
|
||||
select,
|
||||
valueSpan,
|
||||
searchInput: null,
|
||||
optionsList: null
|
||||
};
|
||||
|
||||
trigger.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
if (select.disabled) return;
|
||||
const open = wrapper.classList.contains('open');
|
||||
closeWorkflowToolSelect();
|
||||
if (!open) {
|
||||
wrapper.classList.add('open');
|
||||
trigger.setAttribute('aria-expanded', 'true');
|
||||
ensureWorkflowToolSearchUi(workflowToolSelectRegistry);
|
||||
if (workflowToolSelectRegistry.searchInput) {
|
||||
workflowToolSelectRegistry.searchInput.value = '';
|
||||
renderWorkflowToolSelectOptions(workflowToolSelectRegistry, '');
|
||||
requestAnimationFrame(() => workflowToolSelectRegistry.searchInput.focus());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
dropdown.addEventListener('click', (e) => {
|
||||
const opt = e.target.closest('.workflow-tool-select-option');
|
||||
if (!opt) return;
|
||||
e.stopPropagation();
|
||||
const val = opt.getAttribute('data-value');
|
||||
if (val === null) return;
|
||||
if (select.value !== val) {
|
||||
select.value = val;
|
||||
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
closeWorkflowToolSelect();
|
||||
syncWorkflowToolSelect();
|
||||
});
|
||||
|
||||
select.addEventListener('change', () => syncWorkflowToolSelect());
|
||||
|
||||
if (!workflowToolSelectDocBound) {
|
||||
workflowToolSelectDocBound = true;
|
||||
document.addEventListener('click', closeWorkflowToolSelect);
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') closeWorkflowToolSelect();
|
||||
});
|
||||
}
|
||||
|
||||
syncWorkflowToolSelect();
|
||||
}
|
||||
|
||||
function readWorkflowMetaFromForm() {
|
||||
const idEl = document.getElementById('workflow-id');
|
||||
const nameEl = document.getElementById('workflow-name');
|
||||
@@ -644,7 +858,7 @@
|
||||
wrap.innerHTML = `
|
||||
${joinStrategyHtml(cfg)}
|
||||
<div class="form-group">
|
||||
<label for="workflow-tool-name">${esc(_t('workflows.config.mcpTool'))}</label>
|
||||
<label>${esc(_t('workflows.config.mcpTool'))}</label>
|
||||
<select id="workflow-tool-name" onchange="updateWorkflowTypedConfig()">
|
||||
<option value="">${esc(_t('workflows.config.selectTool'))}</option>
|
||||
${workflowToolOptions.map(tool => `<option value="${esc(tool.key)}" ${tool.key === cfg.tool_name ? 'selected' : ''}>${esc(tool.key)}${tool.enabled ? '' : esc(_t('workflows.config.toolDisabled'))}</option>`).join('')}
|
||||
@@ -653,6 +867,7 @@
|
||||
${typedTextarea('workflow-tool-arguments', _t('workflows.config.argumentsStatic'), cfg.arguments, '{"target":"example.com"}')}
|
||||
${typedField('workflow-tool-timeout', _t('workflows.config.timeoutSeconds'), cfg.timeout_seconds, _t('workflows.config.optional'))}
|
||||
`;
|
||||
enhanceWorkflowToolSelect();
|
||||
if (!workflowToolsLoaded) {
|
||||
loadWorkflowTools().then(() => {
|
||||
if (selectedElement === ele) renderTypedConfig(ele);
|
||||
|
||||
Reference in New Issue
Block a user