From cca6796176b3a9207a3e6c5fe6f1d86465c14921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:32:15 +0800 Subject: [PATCH] Add files via upload --- web/static/css/c2.css | 150 ++++++ web/static/css/style.css | 920 +++++++++++++++++++++++++++++++++++- web/static/js/c2.js | 170 ++++++- web/static/js/chat-files.js | 156 ++++++ web/static/js/hitl.js | 154 ++++++ web/static/js/projects.js | 192 +++++++- web/static/js/roles.js | 155 ++++++ web/static/js/tasks.js | 153 ++++++ web/static/js/workflows.js | 170 ++++++- web/templates/index.html | 26 +- 10 files changed, 2221 insertions(+), 25 deletions(-) diff --git a/web/static/css/c2.css b/web/static/css/c2.css index 89af3bbc..f95048b1 100644 --- a/web/static/css/c2.css +++ b/web/static/css/c2.css @@ -179,6 +179,156 @@ html[data-theme="dark"] .c2-session-main-empty__icon { padding-right: 14px; } +.c2-modal select.form-control.c2-native-select { + appearance: auto; + -webkit-appearance: menulist-button; + background-image: none; + padding-right: 14px; +} + +.c2-form-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.c2-form-select-ui { + position: relative; + width: 100%; + min-width: 0; +} + +.c2-form-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-width: 0; + min-height: 40px; + padding: 10px 14px; + border: 1px solid var(--c2-border); + border-radius: var(--c2-radius-sm); + background: var(--c2-surface); + color: var(--c2-text); + font-size: 14px; + line-height: 1.25; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.c2-form-select-trigger:hover:not(:disabled) { + border-color: var(--c2-accent); +} + +.c2-form-select-ui.open .c2-form-select-trigger { + border-color: var(--c2-accent); + box-shadow: 0 0 0 2px var(--c2-accent-dim); +} + +.c2-form-select-ui.open { + z-index: 120; +} + +.c2-form-select-ui.is-disabled .c2-form-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + +.c2-form-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.c2-form-select-caret { + flex-shrink: 0; + color: var(--c2-text-muted); + transition: transform 0.15s ease; +} + +.c2-form-select-ui.open .c2-form-select-caret { + transform: rotate(180deg); +} + +.c2-form-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--c2-surface); + border: 1px solid var(--c2-border); + border-radius: var(--c2-radius-sm); + box-shadow: var(--c2-shadow-lg); +} + +.c2-form-select-ui.open .c2-form-select-dropdown { + display: block; +} + +.c2-form-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 8px 10px; + border: none; + border-radius: var(--c2-radius-xs); + background: transparent; + color: var(--c2-text); + font-size: 13px; + font-family: inherit; + cursor: pointer; + text-align: left; + transition: background 0.12s ease, color 0.12s ease; +} + +.c2-form-select-option:hover { + background: var(--c2-surface-alt); +} + +.c2-form-select-option.is-selected { + background: var(--c2-accent-dim); + color: var(--c2-accent); + font-weight: 600; +} + +.c2-form-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: var(--c2-accent); + font-size: 12px; + line-height: 1; +} + +.c2-form-select-option.is-selected .c2-form-select-check { + visibility: visible; +} + +.c2-form-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + #page-c2-payloads textarea.form-control, .c2-modal textarea.form-control { resize: vertical; diff --git a/web/static/css/style.css b/web/static/css/style.css index ea84834d..1bb7439c 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -2009,6 +2009,155 @@ html[data-theme="dark"] .openapi-doc-btn:hover { background: #fff; } +.hitl-filter-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.hitl-filters--logs .hitl-filter-select-ui { + position: relative; + min-width: 160px; +} + +.hitl-filters--logs .hitl-filter-select-ui--decision { + min-width: 132px; +} + +.hitl-filters--logs .hitl-filter-select-ui--decidedby { + min-width: 168px; +} + +.hitl-filter-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-width: 0; + padding: 8px 10px; + border: 1px solid var(--border-color, #e2e8f0); + border-radius: 8px; + background: var(--bg-primary, #fff); + color: var(--text-primary, #0f172a); + font-size: 13px; + line-height: 1.25; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.hitl-filter-select-trigger:hover:not(:disabled) { + border-color: rgba(59, 130, 246, 0.45); +} + +.hitl-filter-select-ui.open .hitl-filter-select-trigger { + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12); +} + +.hitl-filter-select-ui.open { + z-index: 120; +} + +.hitl-filter-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.hitl-filter-select-caret { + flex-shrink: 0; + color: var(--text-secondary, #64748b); + transition: transform 0.15s ease; +} + +.hitl-filter-select-ui.open .hitl-filter-select-caret { + transform: rotate(180deg); +} + +.hitl-filter-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--bg-primary, #fff); + border: 1px solid var(--border-color, #e2e8f0); + border-radius: 8px; + box-shadow: var(--shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1)); +} + +.hitl-filter-select-ui.open .hitl-filter-select-dropdown { + display: block; +} + +.hitl-filter-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 8px 10px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--text-primary, #0f172a); + font-size: 13px; + font-family: inherit; + cursor: pointer; + text-align: left; + transition: background 0.12s ease, color 0.12s ease; +} + +.hitl-filter-select-option:hover { + background: var(--bg-secondary, #f8fafc); +} + +.hitl-filter-select-option.is-selected { + background: rgba(59, 130, 246, 0.08); + color: #2563eb; + font-weight: 500; +} + +.hitl-filter-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: #2563eb; + font-size: 12px; + line-height: 1; +} + +.hitl-filter-select-option.is-selected .hitl-filter-select-check { + visibility: visible; +} + +.hitl-filter-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.hitl-filter-select-ui.is-disabled .hitl-filter-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + .hitl-logs-table-wrap { overflow-x: auto; border: 1px solid #e2e8f0; @@ -14618,6 +14767,147 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible { font-size: 0.8125rem; } +.tasks-filter-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.batch-queues-filters.tasks-filters.batch-queues-filters--compact .tasks-filter-select-ui { + position: relative; + min-width: 120px; +} + +.tasks-filter-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-width: 0; + padding: 6px 10px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 0.8125rem; + line-height: 1.25; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.tasks-filter-select-trigger:hover:not(:disabled) { + border-color: var(--accent-color); +} + +.tasks-filter-select-ui.open .tasks-filter-select-trigger { + border-color: var(--accent-color); + box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.1); +} + +.tasks-filter-select-ui.open { + z-index: 120; +} + +.tasks-filter-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.tasks-filter-select-caret { + flex-shrink: 0; + color: var(--text-secondary); + transition: transform 0.15s ease; +} + +.tasks-filter-select-ui.open .tasks-filter-select-caret { + transform: rotate(180deg); +} + +.tasks-filter-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: var(--shadow-lg); +} + +.tasks-filter-select-ui.open .tasks-filter-select-dropdown { + display: block; +} + +.tasks-filter-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + 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; + transition: background 0.12s ease, color 0.12s ease; +} + +.tasks-filter-select-option:hover { + background: var(--bg-secondary); +} + +.tasks-filter-select-option.is-selected { + background: rgba(59, 130, 246, 0.08); + color: #2563eb; + font-weight: 500; +} + +.tasks-filter-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: #2563eb; + font-size: 12px; + line-height: 1; +} + +.tasks-filter-select-option.is-selected .tasks-filter-select-check { + visibility: visible; +} + +.tasks-filter-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.tasks-filter-select-ui.is-disabled .tasks-filter-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + .batch-queues-filters.tasks-filters.batch-queues-filters--compact input[type="text"] { padding: 6px 10px; font-size: 0.8125rem; @@ -24832,6 +25122,147 @@ html[data-theme="dark"] .vulnerability-card:hover { margin-bottom: 16px; } +.chat-files-filter-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.chat-files-filters .chat-files-filter-select-ui { + position: relative; + min-width: 168px; +} + +.chat-files-filter-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-width: 0; + padding: 8px 12px; + border: 1px solid var(--border-color); + border-radius: 6px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 0.875rem; + line-height: 1.25; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.chat-files-filter-select-trigger:hover:not(:disabled) { + border-color: var(--accent-color); +} + +.chat-files-filter-select-ui.open .chat-files-filter-select-trigger { + border-color: var(--accent-color); + box-shadow: 0 0 0 2px rgba(0, 102, 255, 0.1); +} + +.chat-files-filter-select-ui.open { + z-index: 120; +} + +.chat-files-filter-select-ui.is-disabled .chat-files-filter-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + +.chat-files-filter-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.chat-files-filter-select-caret { + flex-shrink: 0; + color: var(--text-secondary); + transition: transform 0.15s ease; +} + +.chat-files-filter-select-ui.open .chat-files-filter-select-caret { + transform: rotate(180deg); +} + +.chat-files-filter-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: var(--shadow-lg); +} + +.chat-files-filter-select-ui.open .chat-files-filter-select-dropdown { + display: block; +} + +.chat-files-filter-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + 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; + transition: background 0.12s ease, color 0.12s ease; +} + +.chat-files-filter-select-option:hover { + background: var(--bg-secondary); +} + +.chat-files-filter-select-option.is-selected { + background: rgba(59, 130, 246, 0.08); + color: #2563eb; + font-weight: 500; +} + +.chat-files-filter-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: #2563eb; + font-size: 12px; + line-height: 1; +} + +.chat-files-filter-select-option.is-selected .chat-files-filter-select-check { + visibility: visible; +} + +.chat-files-filter-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .chat-files-table-wrap { overflow-x: auto; background: var(--bg-primary); @@ -26684,6 +27115,175 @@ html[data-theme="dark"] .chat-files-toast.chat-toast--error { border-radius: 8px; transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease; } + +.projects-filter-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.projects-fact-filter-field .projects-filter-select-ui { + width: 100%; + min-width: 120px; +} + +.projects-filter-select-ui { + position: relative; + width: 100%; + min-width: 0; +} + +.projects-filter-select-ui--compact { + display: inline-flex; + min-width: 64px; +} + +.projects-filter-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-width: 0; + height: 34px; + padding: 0 10px; + border: 1px solid var(--border-color, #e2e8f0); + border-radius: 8px; + background: var(--bg-primary, #f8fafc); + color: var(--text-primary, #0f172a); + font-size: 0.8125rem; + line-height: 1.25; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease; +} + +.projects-settings-card .projects-filter-select-trigger { + height: 38px; + font-size: 0.875rem; +} + +.projects-filter-select-trigger:hover:not(:disabled) { + border-color: rgba(59, 130, 246, 0.45); +} + +.projects-filter-select-ui.open .projects-filter-select-trigger { + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12); +} + +.projects-filter-select-ui.open { + z-index: 120; +} + +.projects-filter-select-ui.is-disabled .projects-filter-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + +.projects-filter-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +.projects-filter-select-caret { + flex-shrink: 0; + color: var(--text-secondary, #94a3b8); + transition: transform 0.15s ease; +} + +.projects-filter-select-ui.open .projects-filter-select-caret { + transform: rotate(180deg); +} + +.projects-filter-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--bg-primary, #fff); + border: 1px solid var(--border-color, #e2e8f0); + border-radius: 8px; + box-shadow: var(--shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1)); +} + +.projects-filter-select-ui.open .projects-filter-select-dropdown { + display: block; +} + +.projects-filter-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 8px 10px; + border: none; + border-radius: 6px; + background: transparent; + color: var(--text-primary, #0f172a); + font-size: 0.8125rem; + font-family: inherit; + cursor: pointer; + text-align: left; + transition: background 0.12s ease, color 0.12s ease; +} + +.projects-filter-select-option:hover { + background: var(--bg-secondary, #f1f5f9); +} + +.projects-filter-select-option.is-selected { + background: rgba(59, 130, 246, 0.08); + color: #2563eb; + font-weight: 500; +} + +.projects-filter-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: #2563eb; + font-size: 12px; + line-height: 1; +} + +.projects-filter-select-option.is-selected .projects-filter-select-check { + visibility: visible; +} + +.projects-filter-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.sidebar-list-pagination .pagination-page-size .projects-filter-select-ui--compact { + vertical-align: middle; +} + +.sidebar-list-pagination .pagination-page-size .projects-filter-select-trigger { + height: 28px; + padding: 0 8px; + font-size: 0.75rem; + border-radius: 6px; +} .projects-fact-filter-field--search input { padding-left: 34px; background: #fff; @@ -28556,7 +29156,13 @@ html[data-theme="dark"] .role-selection-item-main:hover, html[data-theme="dark"] .role-select-item:hover, html[data-theme="dark"] .custom-select-option:hover, html[data-theme="dark"] .audit-custom-select-option:hover, -html[data-theme="dark"] .vuln-filter-select-option:hover { +html[data-theme="dark"] .vuln-filter-select-option:hover, +html[data-theme="dark"] .hitl-filter-select-option:hover, +html[data-theme="dark"] .tasks-filter-select-option:hover, +html[data-theme="dark"] .workflow-form-select-option:hover, +html[data-theme="dark"] .projects-filter-select-option:hover, +html[data-theme="dark"] .chat-files-filter-select-option:hover, +html[data-theme="dark"] #role-modal .role-form-select-option:hover { background: rgba(96, 165, 250, 0.12); } @@ -28565,7 +29171,13 @@ html[data-theme="dark"] .role-selection-item-main.selected, html[data-theme="dark"] .role-select-item.selected, html[data-theme="dark"] .custom-select-option.selected, html[data-theme="dark"] .audit-custom-select-option.is-selected, -html[data-theme="dark"] .vuln-filter-select-option.is-selected { +html[data-theme="dark"] .vuln-filter-select-option.is-selected, +html[data-theme="dark"] .hitl-filter-select-option.is-selected, +html[data-theme="dark"] .tasks-filter-select-option.is-selected, +html[data-theme="dark"] .workflow-form-select-option.is-selected, +html[data-theme="dark"] .projects-filter-select-option.is-selected, +html[data-theme="dark"] .chat-files-filter-select-option.is-selected, +html[data-theme="dark"] #role-modal .role-form-select-option.is-selected { background: rgba(96, 165, 250, 0.18); color: var(--accent-hover); } @@ -28579,6 +29191,13 @@ html[data-theme="dark"] .model-pick-input, html[data-theme="dark"] .custom-select-trigger, html[data-theme="dark"] .audit-custom-select-trigger, html[data-theme="dark"] .vuln-filter-select-trigger, +html[data-theme="dark"] .hitl-filter-select-trigger, +html[data-theme="dark"] .tasks-filter-select-trigger, +html[data-theme="dark"] .workflow-form-select-trigger, +html[data-theme="dark"] .workflow-tool-select-trigger, +html[data-theme="dark"] .projects-filter-select-trigger, +html[data-theme="dark"] .chat-files-filter-select-trigger, +html[data-theme="dark"] .role-form-select-trigger, html[data-theme="dark"] .role-selector-btn, html[data-theme="dark"] .conversation-project-filter-trigger { background: #0f172a; @@ -28599,7 +29218,13 @@ html[data-theme="dark"] .form-control:focus, html[data-theme="dark"] .model-pick-input:focus, html[data-theme="dark"] .custom-select.open .custom-select-trigger, html[data-theme="dark"] .audit-custom-select.open .audit-custom-select-trigger, -html[data-theme="dark"] .vuln-filter-select.open .vuln-filter-select-trigger { +html[data-theme="dark"] .vuln-filter-select.open .vuln-filter-select-trigger, +html[data-theme="dark"] .hitl-filter-select-ui.open .hitl-filter-select-trigger, +html[data-theme="dark"] .tasks-filter-select-ui.open .tasks-filter-select-trigger, +html[data-theme="dark"] .workflow-form-select-ui.open .workflow-form-select-trigger, +html[data-theme="dark"] .projects-filter-select-ui.open .projects-filter-select-trigger, +html[data-theme="dark"] .chat-files-filter-select-ui.open .chat-files-filter-select-trigger, +html[data-theme="dark"] #role-modal .role-form-select-ui.open .role-form-select-trigger { border-color: var(--accent-color); box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.16); } @@ -30192,6 +30817,149 @@ html[data-theme="dark"] #agent-md-modal .modal-content, html[data-theme="dark"] #agent-md-modal .modal-header, html[data-theme="dark"] #agent-md-modal .modal-body, html[data-theme="dark"] #agent-md-modal .modal-footer, +.role-form-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +#role-modal .role-form-select-ui { + position: relative; + width: 100%; + min-width: 0; +} + +#role-modal .role-form-select-trigger { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + min-width: 0; + min-height: 40px; + padding: 10px 12px; + border: 1px solid var(--border-color); + border-radius: 8px; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 0.875rem; + line-height: 1.25; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +#role-modal .role-form-select-trigger:hover:not(:disabled) { + border-color: rgba(59, 130, 246, 0.45); +} + +#role-modal .role-form-select-ui.open .role-form-select-trigger { + border-color: var(--accent-color); + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12); +} + +#role-modal .role-form-select-ui.open { + z-index: 120; +} + +#role-modal .role-form-select-ui.is-disabled .role-form-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + +#role-modal .role-form-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; +} + +#role-modal .role-form-select-caret { + flex-shrink: 0; + color: var(--text-secondary); + transition: transform 0.15s ease; +} + +#role-modal .role-form-select-ui.open .role-form-select-caret { + transform: rotate(180deg); +} + +#role-modal .role-form-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: var(--shadow-lg); +} + +#role-modal .role-form-select-ui.open .role-form-select-dropdown { + display: block; +} + +#role-modal .role-form-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + 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; + transition: background 0.12s ease, color 0.12s ease; +} + +#role-modal .role-form-select-option:hover { + background: var(--bg-secondary); +} + +#role-modal .role-form-select-option.is-selected { + background: rgba(59, 130, 246, 0.08); + color: #2563eb; + font-weight: 500; +} + +#role-modal .role-form-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: #2563eb; + font-size: 12px; + line-height: 1; +} + +#role-modal .role-form-select-option.is-selected .role-form-select-check { + visibility: visible; +} + +#role-modal .role-form-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + html[data-theme="dark"] #role-modal .modal-content, html[data-theme="dark"] #role-modal .modal-header, html[data-theme="dark"] #role-modal .modal-body, @@ -31787,6 +32555,152 @@ html[data-theme="dark"] .form-group select { pointer-events: none; } +.workflow-form-native-select { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.workflow-form-select-ui { + position: relative; + width: 100%; + min-width: 0; +} + +.workflow-binding-row .workflow-form-select-ui { + flex: 1; + min-width: 0; +} + +.workflow-form-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-form-select-trigger:hover:not(:disabled) { + border-color: rgba(59, 130, 246, 0.45); +} + +.workflow-form-select-ui.open .workflow-form-select-trigger { + border-color: #3b82f6; + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12); +} + +.workflow-form-select-ui.open { + z-index: 120; +} + +.workflow-form-select-ui.is-disabled .workflow-form-select-trigger { + opacity: 0.6; + cursor: not-allowed; +} + +.workflow-form-select-value { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.workflow-form-select-caret { + flex-shrink: 0; + color: var(--text-secondary); + transition: transform 0.15s ease; +} + +.workflow-form-select-ui.open .workflow-form-select-caret { + transform: rotate(180deg); +} + +.workflow-form-select-dropdown { + display: none; + position: absolute; + top: calc(100% + 4px); + left: 0; + right: 0; + z-index: 200; + max-height: 280px; + overflow-y: auto; + padding: 4px; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 8px; + box-shadow: var(--shadow-lg); +} + +.workflow-form-select-ui.open .workflow-form-select-dropdown { + display: block; +} + +.workflow-form-select-option { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + 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; + transition: background 0.12s ease, color 0.12s ease; +} + +.workflow-form-select-option:hover { + background: var(--bg-secondary); +} + +.workflow-form-select-option.is-selected { + background: rgba(59, 130, 246, 0.08); + color: #2563eb; + font-weight: 500; +} + +.workflow-form-select-check { + width: 14px; + flex-shrink: 0; + visibility: hidden; + color: #2563eb; + font-size: 12px; + line-height: 1; +} + +.workflow-form-select-option.is-selected .workflow-form-select-check { + visibility: visible; +} + +.workflow-form-select-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .workflow-tool-select { position: relative; width: 100%; diff --git a/web/static/js/c2.js b/web/static/js/c2.js index 7e36b0a1..3b4a85fe 100644 --- a/web/static/js/c2.js +++ b/web/static/js/c2.js @@ -71,6 +71,162 @@ return key; } + const c2FormSelectMap = {}; + let c2FormSelectDocBound = false; + const C2_FORM_SELECT_CARET = ''; + const C2_FORM_SELECT_HANDLERS = { + 'c2-listener-type': function () { + C2.syncListenerProfileRowForType(); + } + }; + + function closeAllC2FormSelects() { + Object.keys(c2FormSelectMap).forEach(function (id) { + const reg = c2FormSelectMap[id]; + if (!reg || !reg.wrapper) return; + reg.wrapper.classList.remove('open'); + if (reg.trigger) reg.trigger.setAttribute('aria-expanded', 'false'); + }); + } + + function syncC2FormSelect(selectId) { + const reg = c2FormSelectMap[selectId]; + if (!reg) return; + const select = reg.select; + const dropdown = reg.dropdown; + const trigger = reg.trigger; + const valueSpan = trigger.querySelector('.c2-form-select-value'); + + dropdown.innerHTML = ''; + Array.prototype.forEach.call(select.options, function (opt) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = 'c2-form-select-option'; + item.setAttribute('role', 'option'); + item.setAttribute('data-value', opt.value); + if (opt.value === select.value) { + item.classList.add('is-selected'); + item.setAttribute('aria-selected', 'true'); + } else { + item.setAttribute('aria-selected', 'false'); + } + const check = document.createElement('span'); + check.className = 'c2-form-select-check'; + check.setAttribute('aria-hidden', 'true'); + check.textContent = '✓'; + const label = document.createElement('span'); + label.className = 'c2-form-select-label'; + label.textContent = opt.textContent; + item.appendChild(check); + item.appendChild(label); + dropdown.appendChild(item); + }); + + const selectedOpt = select.options[select.selectedIndex]; + if (valueSpan) { + valueSpan.textContent = selectedOpt ? selectedOpt.textContent : ''; + } + trigger.disabled = !!select.disabled; + reg.wrapper.classList.toggle('is-disabled', !!select.disabled); + } + + function enhanceC2FormSelect(select) { + if (!select || !select.id) return; + const existing = c2FormSelectMap[select.id]; + if (existing && existing.select !== select) { + delete c2FormSelectMap[select.id]; + } + if (select.dataset.c2FormCustom === '1') { + syncC2FormSelect(select.id); + return; + } + select.dataset.c2FormCustom = '1'; + select.classList.add('c2-form-native-select'); + select.tabIndex = -1; + select.setAttribute('aria-hidden', 'true'); + + const wrapper = document.createElement('div'); + wrapper.className = 'c2-form-select-ui'; + + const trigger = document.createElement('button'); + trigger.type = 'button'; + trigger.className = 'c2-form-select-trigger'; + trigger.setAttribute('aria-haspopup', 'listbox'); + trigger.setAttribute('aria-expanded', 'false'); + const valueSpan = document.createElement('span'); + valueSpan.className = 'c2-form-select-value'; + trigger.appendChild(valueSpan); + trigger.insertAdjacentHTML('beforeend', C2_FORM_SELECT_CARET); + + const dropdown = document.createElement('div'); + dropdown.className = 'c2-form-select-dropdown'; + dropdown.setAttribute('role', 'listbox'); + + const parent = select.parentNode; + parent.insertBefore(wrapper, select); + wrapper.appendChild(trigger); + wrapper.appendChild(dropdown); + wrapper.appendChild(select); + + c2FormSelectMap[select.id] = { wrapper: wrapper, trigger: trigger, dropdown: dropdown, select: select }; + + trigger.addEventListener('click', function (e) { + e.stopPropagation(); + if (select.disabled) return; + const open = wrapper.classList.contains('open'); + closeAllC2FormSelects(); + if (!open) { + wrapper.classList.add('open'); + trigger.setAttribute('aria-expanded', 'true'); + } + }); + + dropdown.addEventListener('click', function (e) { + const opt = e.target.closest('.c2-form-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 })); + } + wrapper.classList.remove('open'); + trigger.setAttribute('aria-expanded', 'false'); + syncC2FormSelect(select.id); + }); + + select.addEventListener('change', function () { + syncC2FormSelect(select.id); + }); + + if (!select.dataset.c2FormFilterBound) { + select.dataset.c2FormFilterBound = '1'; + const handler = C2_FORM_SELECT_HANDLERS[select.id]; + if (typeof handler === 'function') { + select.addEventListener('change', handler); + } + } + + syncC2FormSelect(select.id); + } + + C2.refreshFormSelects = function (root) { + const container = root || document.getElementById('c2-modal-content'); + if (!container) return; + Object.keys(c2FormSelectMap).forEach(function (id) { + if (!document.getElementById(id)) delete c2FormSelectMap[id]; + }); + container.querySelectorAll('select.c2-form-select-native').forEach(enhanceC2FormSelect); + if (!c2FormSelectDocBound) { + c2FormSelectDocBound = true; + document.addEventListener('click', closeAllC2FormSelects); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeAllC2FormSelects(); + }); + } + }; + function listenerTypeLabel(type) { if (!type) return ''; const k = 'c2.listeners.typeLabels.' + String(type).toLowerCase(); @@ -609,7 +765,7 @@
' + hint + '
' : ''} @@ -534,6 +534,7 @@ if (select.disabled) return; const open = wrapper.classList.contains('open'); closeWorkflowToolSelect(); + closeAllWorkflowFormSelects(); if (!open) { wrapper.classList.add('open'); trigger.setAttribute('aria-expanded', 'true'); @@ -573,6 +574,160 @@ syncWorkflowToolSelect(); } + const workflowFormSelectMap = {}; + let workflowFormSelectDocBound = false; + const WORKFLOW_FORM_SELECT_CARET = ''; + + function closeAllWorkflowFormSelects() { + Object.keys(workflowFormSelectMap).forEach(function (id) { + const reg = workflowFormSelectMap[id]; + if (!reg || !reg.wrapper) return; + reg.wrapper.classList.remove('open'); + if (reg.trigger) reg.trigger.setAttribute('aria-expanded', 'false'); + }); + } + + function pruneWorkflowFormSelectMap(root) { + Object.keys(workflowFormSelectMap).forEach(function (id) { + const select = document.getElementById(id); + if (!select || (root && !root.contains(select))) { + delete workflowFormSelectMap[id]; + } + }); + } + + function syncWorkflowFormSelect(select) { + const reg = workflowFormSelectMap[select.id]; + if (!reg) return; + const dropdown = reg.dropdown; + const trigger = reg.trigger; + const valueSpan = trigger.querySelector('.workflow-form-select-value'); + + dropdown.innerHTML = ''; + Array.prototype.forEach.call(select.options, function (opt) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = 'workflow-form-select-option'; + item.setAttribute('role', 'option'); + item.setAttribute('data-value', opt.value); + if (opt.value === select.value) { + item.classList.add('is-selected'); + item.setAttribute('aria-selected', 'true'); + } else { + item.setAttribute('aria-selected', 'false'); + } + const check = document.createElement('span'); + check.className = 'workflow-form-select-check'; + check.setAttribute('aria-hidden', 'true'); + check.textContent = '✓'; + const label = document.createElement('span'); + label.className = 'workflow-form-select-label'; + label.textContent = opt.textContent; + item.appendChild(check); + item.appendChild(label); + dropdown.appendChild(item); + }); + + const selectedOpt = select.options[select.selectedIndex]; + if (valueSpan) { + valueSpan.textContent = selectedOpt ? selectedOpt.textContent : ''; + } + trigger.disabled = !!select.disabled; + reg.wrapper.classList.toggle('is-disabled', !!select.disabled); + } + + function enhanceWorkflowFormSelect(select) { + if (!select || !select.id) return; + if (select.id === WORKFLOW_TOOL_SELECT_ID) return; + const existing = workflowFormSelectMap[select.id]; + if (existing && existing.select !== select) { + delete workflowFormSelectMap[select.id]; + } + if (select.dataset.workflowFormCustom === '1') { + syncWorkflowFormSelect(select); + return; + } + select.dataset.workflowFormCustom = '1'; + select.classList.add('workflow-form-native-select'); + select.tabIndex = -1; + select.setAttribute('aria-hidden', 'true'); + + const wrapper = document.createElement('div'); + wrapper.className = 'workflow-form-select-ui'; + + const trigger = document.createElement('button'); + trigger.type = 'button'; + trigger.className = 'workflow-form-select-trigger'; + trigger.setAttribute('aria-haspopup', 'listbox'); + trigger.setAttribute('aria-expanded', 'false'); + const valueSpan = document.createElement('span'); + valueSpan.className = 'workflow-form-select-value'; + trigger.appendChild(valueSpan); + trigger.insertAdjacentHTML('beforeend', WORKFLOW_FORM_SELECT_CARET); + + const dropdown = document.createElement('div'); + dropdown.className = 'workflow-form-select-dropdown'; + dropdown.setAttribute('role', 'listbox'); + + const parent = select.parentNode; + parent.insertBefore(wrapper, select); + wrapper.appendChild(trigger); + wrapper.appendChild(dropdown); + wrapper.appendChild(select); + + workflowFormSelectMap[select.id] = { wrapper: wrapper, trigger: trigger, dropdown: dropdown, select: select }; + + trigger.addEventListener('click', function (e) { + e.stopPropagation(); + if (select.disabled) return; + const open = wrapper.classList.contains('open'); + closeAllWorkflowFormSelects(); + closeWorkflowToolSelect(); + if (!open) { + wrapper.classList.add('open'); + trigger.setAttribute('aria-expanded', 'true'); + } + }); + + dropdown.addEventListener('click', function (e) { + const opt = e.target.closest('.workflow-form-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 })); + } + wrapper.classList.remove('open'); + trigger.setAttribute('aria-expanded', 'false'); + syncWorkflowFormSelect(select); + }); + + select.addEventListener('change', function () { + syncWorkflowFormSelect(select); + }); + + syncWorkflowFormSelect(select); + } + + function refreshWorkflowPropertySelects() { + const form = document.getElementById('workflow-property-form'); + if (!form || form.hidden) return; + pruneWorkflowFormSelectMap(form); + form.querySelectorAll('select').forEach(function (select) { + if (select.id === WORKFLOW_TOOL_SELECT_ID) return; + enhanceWorkflowFormSelect(select); + }); + if (!workflowFormSelectDocBound) { + workflowFormSelectDocBound = true; + document.addEventListener('click', closeAllWorkflowFormSelects); + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape') closeAllWorkflowFormSelects(); + }); + } + } + function readWorkflowMetaFromForm() { const idEl = document.getElementById('workflow-id'); const nameEl = document.getElementById('workflow-name'); @@ -794,7 +949,7 @@ return `${esc(_t('workflows.config.joinStrategyHint') || '多个上游进入同一节点时如何生成 previous。')}
@@ -838,7 +993,7 @@ ${sourceType === 'condition' ? `${esc(_t('workflows.config.edgeBranchHint'))}
` : ''} `; + refreshWorkflowPropertySelects(); return; } const type = ele.data('type') || 'tool'; @@ -879,7 +1035,7 @@ ${joinStrategyHtml(cfg)}${_t('workflows.config.hitlInteractiveHint')}