mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-10 22:33:48 +02:00
Add files via upload
This commit is contained in:
+710
-50
@@ -1272,32 +1272,59 @@ html[data-theme="dark"] .openapi-doc-btn:hover {
|
||||
|
||||
.new-chat-btn {
|
||||
width: 100%;
|
||||
padding: 10px 16px;
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
border: none;
|
||||
min-height: 40px;
|
||||
padding: 9px 16px;
|
||||
background: linear-gradient(180deg, #60a5fa 0%, #3b82f6 100%);
|
||||
color: #ffffff;
|
||||
border: 1px solid rgba(96, 165, 250, 0.72);
|
||||
border-radius: 8px;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
font-weight: 650;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: background 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
gap: 7px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
letter-spacing: 0;
|
||||
box-shadow: 0 6px 14px rgba(37, 99, 235, 0.18);
|
||||
}
|
||||
|
||||
.new-chat-btn:hover {
|
||||
background: var(--accent-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
background: linear-gradient(180deg, #74b2fb 0%, #4b8df7 100%);
|
||||
border-color: rgba(147, 197, 253, 0.9);
|
||||
box-shadow: 0 8px 16px rgba(37, 99, 235, 0.22);
|
||||
}
|
||||
|
||||
.new-chat-btn:active {
|
||||
background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%);
|
||||
box-shadow: 0 3px 8px rgba(37, 99, 235, 0.16);
|
||||
}
|
||||
|
||||
.new-chat-btn:focus-visible {
|
||||
outline: none;
|
||||
box-shadow:
|
||||
0 0 0 3px rgba(96, 165, 250, 0.22),
|
||||
0 6px 14px rgba(37, 99, 235, 0.18);
|
||||
}
|
||||
|
||||
.new-chat-btn span {
|
||||
font-size: 1.2em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.new-chat-btn span:first-child {
|
||||
flex-shrink: 0;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 700;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.new-chat-btn span:last-child {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
@@ -5027,6 +5054,12 @@ html[data-theme="dark"] .openapi-doc-btn:hover {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.timeline-item.timeline-item-focus-pulse {
|
||||
outline: 2px solid rgba(96, 165, 250, 0.85);
|
||||
outline-offset: 2px;
|
||||
animation: highlight-flash 1.1s ease-in-out 2;
|
||||
}
|
||||
|
||||
/* 迭代轮次:暖琥珀色条 + 极浅底,与紫(推理)/蓝(工具)区分但不抢视觉 */
|
||||
.timeline-item-iteration {
|
||||
border-left-color: #c4a574;
|
||||
@@ -5061,7 +5094,15 @@ html[data-theme="dark"] .openapi-doc-btn:hover {
|
||||
/* 迭代中主通道流式正文(标题常为「助手输出」等):中性底 + 主色条,表示对用户可见的答复流 */
|
||||
.timeline-item-thinking[data-response-stream-placeholder="1"] {
|
||||
border-left-color: var(--accent-color);
|
||||
background: rgba(0, 102, 255, 0.04);
|
||||
background: rgba(59, 130, 246, 0.055);
|
||||
border-color: rgba(59, 130, 246, 0.16);
|
||||
}
|
||||
|
||||
/* Eino single/deep/supervisor 的主通道是助手正文,不使用 planning 的青绿色卡片 */
|
||||
.timeline-item-planning[data-orchestration]:not([data-orchestration="plan_execute"]) {
|
||||
border-left-color: #3b82f6;
|
||||
background: rgba(59, 130, 246, 0.055);
|
||||
border-color: rgba(59, 130, 246, 0.16);
|
||||
}
|
||||
|
||||
.timeline-item-reasoning_chain {
|
||||
@@ -5559,49 +5600,12 @@ html[data-theme="dark"] .openapi-doc-btn:hover {
|
||||
.timeline-item.tool-detail-collapsible .timeline-item-header {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
padding-right: 104px;
|
||||
}
|
||||
|
||||
.timeline-item.tool-detail-collapsible .timeline-item-header:hover .timeline-item-title {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.timeline-item.tool-detail-collapsible .timeline-item-header::after {
|
||||
content: attr(data-tool-detail-label);
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
padding: 3px 8px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(96, 165, 250, 0.35);
|
||||
color: #93c5fd;
|
||||
background: rgba(37, 99, 235, 0.12);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.timeline-item.tool-detail-collapsible .timeline-item-header:hover::after {
|
||||
color: #dbeafe;
|
||||
border-color: rgba(147, 197, 253, 0.65);
|
||||
background: rgba(37, 99, 235, 0.22);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.timeline-item.tool-detail-collapsible .timeline-item-header {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.timeline-item.tool-detail-collapsible .timeline-item-header::after {
|
||||
position: static;
|
||||
transform: none;
|
||||
margin-left: auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* 参数块与卡片类型色弱对齐,扫读时一眼归到「调用」 */
|
||||
.timeline-item-tool_call .tool-args {
|
||||
background: rgba(21, 101, 192, 0.06);
|
||||
@@ -24978,6 +24982,313 @@ html[data-theme="dark"] .vulnerability-details code.vuln-detail-field-value {
|
||||
}
|
||||
}
|
||||
|
||||
.skills-management-page .page-content {
|
||||
padding: 18px 22px 0;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.skills-overview {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(160px, 1.2fr) repeat(3, minmax(110px, 0.55fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.skills-overview-card {
|
||||
min-height: 76px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.82), rgba(255, 255, 255, 0.58)),
|
||||
var(--bg-primary);
|
||||
box-shadow: 0 8px 22px rgba(15, 23, 42, 0.06);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.skills-overview-card-primary {
|
||||
border-color: rgba(0, 102, 255, 0.28);
|
||||
background:
|
||||
linear-gradient(135deg, rgba(0, 102, 255, 0.16), rgba(40, 167, 69, 0.10)),
|
||||
var(--bg-primary);
|
||||
}
|
||||
|
||||
.skills-overview-label {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.2;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.skills-overview-card strong {
|
||||
font-size: 1.55rem;
|
||||
line-height: 1;
|
||||
color: var(--text-primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.skills-management-page .skills-controls {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.skills-management-page .skills-search-box {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.skills-management-page .skills-search-box::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transform: translateY(-50%);
|
||||
opacity: 0.58;
|
||||
background: currentColor;
|
||||
color: var(--text-secondary);
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m21 21-4.34-4.34'/%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3C/svg%3E") center / contain no-repeat;
|
||||
}
|
||||
|
||||
.skills-management-page .skills-search-box input {
|
||||
height: 44px;
|
||||
padding: 10px 44px 10px 44px;
|
||||
border-radius: 10px;
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 8px 22px rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.skills-management-page .skills-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 2px 2px 18px;
|
||||
}
|
||||
|
||||
.skills-management-page .skill-card {
|
||||
min-height: 92px;
|
||||
display: grid;
|
||||
grid-template-columns: 46px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 14px 16px;
|
||||
padding: 14px 18px;
|
||||
border-radius: 10px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(248, 249, 250, 0.88)),
|
||||
var(--bg-primary);
|
||||
box-shadow: 0 10px 28px rgba(15, 23, 42, 0.06);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.skills-management-page .skill-card::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-top: 2px solid rgba(0, 102, 255, 0.38);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.skills-management-page .skill-card:hover {
|
||||
transform: translateY(-3px);
|
||||
border-color: rgba(0, 102, 255, 0.38);
|
||||
box-shadow: 0 16px 34px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.skills-management-page .skill-card:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.skill-card-mark {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 10px;
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, rgba(0, 102, 255, 0.18), rgba(40, 167, 69, 0.14));
|
||||
color: var(--accent-color);
|
||||
font-size: 1rem;
|
||||
font-weight: 800;
|
||||
border: 1px solid rgba(0, 102, 255, 0.18);
|
||||
}
|
||||
|
||||
.skill-card-body {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 12px 18px;
|
||||
}
|
||||
|
||||
.skills-management-page .skill-card-header {
|
||||
min-width: 0;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.skill-card-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.skills-management-page .skill-card-title {
|
||||
font-size: 1.02rem;
|
||||
line-height: 1.35;
|
||||
word-break: normal;
|
||||
overflow-wrap: anywhere;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.skill-card-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.skill-card-meta span {
|
||||
max-width: 100%;
|
||||
padding: 3px 8px;
|
||||
border-radius: 999px;
|
||||
background: rgba(0, 102, 255, 0.08);
|
||||
color: var(--accent-color);
|
||||
border: 1px solid rgba(0, 102, 255, 0.14);
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.25;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.skills-management-page .skill-card-description {
|
||||
color: var(--text-secondary);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
min-height: 1.4em;
|
||||
}
|
||||
|
||||
.skills-management-page .skill-card-actions {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
margin-top: 0;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: nowrap;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.skills-management-page .skill-card-actions .btn-secondary {
|
||||
padding: 6px 12px;
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.66);
|
||||
}
|
||||
|
||||
.skills-management-page .skill-card-actions .btn-danger {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-overview-card,
|
||||
html[data-theme="dark"] .skills-management-page .skill-card,
|
||||
html[data-theme="dark"] .skills-management-page .skills-search-box input {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(17, 24, 39, 0.96), rgba(15, 23, 42, 0.92)),
|
||||
#111827;
|
||||
border-color: #263244;
|
||||
box-shadow: 0 16px 34px rgba(0, 0, 0, 0.20);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-overview-card-primary {
|
||||
border-color: rgba(96, 165, 250, 0.30);
|
||||
background:
|
||||
linear-gradient(135deg, rgba(96, 165, 250, 0.14), rgba(52, 211, 153, 0.08)),
|
||||
#111827;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skill-card-mark {
|
||||
background: linear-gradient(135deg, rgba(96, 165, 250, 0.18), rgba(52, 211, 153, 0.12));
|
||||
border-color: rgba(96, 165, 250, 0.22);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skill-card-meta span {
|
||||
background: rgba(96, 165, 250, 0.12);
|
||||
border-color: rgba(96, 165, 250, 0.22);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-management-page .skill-card-actions .btn-secondary {
|
||||
background: rgba(15, 23, 42, 0.72);
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.skills-overview {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.skills-management-page .page-content {
|
||||
padding: 14px 14px 0;
|
||||
}
|
||||
|
||||
.skills-overview {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.skills-overview-card {
|
||||
min-height: 68px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.skills-overview-card strong {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.skills-management-page .skill-card {
|
||||
min-height: auto;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.skill-card-body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.skills-management-page .skill-card-actions {
|
||||
grid-column: 1;
|
||||
grid-row: auto;
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.skills-overview {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.skills-management-page .skill-card {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.skill-card-title-row {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.skill-card-mark {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.skill-item {
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
@@ -25135,6 +25446,323 @@ html[data-theme="dark"] .vulnerability-details code.vuln-detail-field-value {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Skills 状态监控:专属视觉增强 */
|
||||
.skills-monitor-page {
|
||||
--skills-blue: #2563eb;
|
||||
--skills-cyan: #06b6d4;
|
||||
--skills-success: #16a34a;
|
||||
--skills-danger: #e11d48;
|
||||
--skills-card-shadow: 0 10px 28px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.skills-monitor-page .page-content {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(37, 99, 235, 0.06) 0%, rgba(248, 250, 252, 0) 260px),
|
||||
#f5f7fb;
|
||||
padding: 20px 24px 28px;
|
||||
}
|
||||
|
||||
.skills-monitor-page .page-header {
|
||||
padding: 16px 24px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.skills-monitor-page .page-header h2 {
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.skills-monitor-page .monitor-sections {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.skills-monitor-page .monitor-section {
|
||||
border-color: rgba(37, 99, 235, 0.1);
|
||||
border-radius: 14px;
|
||||
box-shadow: var(--skills-card-shadow);
|
||||
}
|
||||
|
||||
.skills-monitor-page .monitor-section.monitor-overview {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.skills-monitor-page .monitor-section .section-header h3 {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.skills-monitor-page .monitor-section .section-header {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.skills-monitor-page .monitor-section .section-header h3::before {
|
||||
content: "";
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, var(--skills-blue), var(--skills-cyan));
|
||||
box-shadow: 0 0 0 5px rgba(37, 99, 235, 0.1);
|
||||
}
|
||||
|
||||
.skills-monitor-page .monitor-stats-grid {
|
||||
grid-template-columns: repeat(5, minmax(150px, 1fr));
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-stat-card {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
min-height: 124px;
|
||||
padding: 20px;
|
||||
border: 1px solid rgba(37, 99, 235, 0.12);
|
||||
border-radius: 16px;
|
||||
background:
|
||||
linear-gradient(145deg, rgba(255, 255, 255, 0.96), rgba(248, 250, 252, 0.92)),
|
||||
radial-gradient(circle at 100% 0%, rgba(37, 99, 235, 0.14), transparent 34%);
|
||||
box-shadow: var(--skills-card-shadow);
|
||||
overflow: hidden;
|
||||
transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-stat-card:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgba(37, 99, 235, 0.24);
|
||||
box-shadow: 0 14px 34px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.skills-monitor-page .monitor-stat-label {
|
||||
color: #64748b;
|
||||
font-size: 0.86rem;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.skills-monitor-page .monitor-stat-value {
|
||||
margin-top: 6px;
|
||||
font-size: 2.15rem;
|
||||
font-weight: 780;
|
||||
line-height: 1.05;
|
||||
letter-spacing: 0;
|
||||
color: #111827;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.skills-monitor-page .monitor-stat-value.is-success,
|
||||
.skills-monitor-page .skills-table-number.is-success {
|
||||
color: var(--skills-success);
|
||||
}
|
||||
|
||||
.skills-monitor-page .monitor-stat-value.is-danger,
|
||||
.skills-monitor-page .skills-table-number.is-danger {
|
||||
color: var(--skills-danger);
|
||||
}
|
||||
|
||||
.skills-monitor-page #skills-monitor-list {
|
||||
border-color: rgba(15, 23, 42, 0.08);
|
||||
border-radius: 14px;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-monitor-table {
|
||||
min-width: 900px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-monitor-table thead {
|
||||
background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-monitor-table th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
padding: 14px 16px;
|
||||
color: #64748b;
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-monitor-table th:first-child,
|
||||
.skills-monitor-page .skills-monitor-table td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-monitor-table th:not(:first-child),
|
||||
.skills-monitor-page .skills-monitor-table td:not(:first-child) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-monitor-table th:last-child,
|
||||
.skills-monitor-page .skills-monitor-table td:last-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-monitor-table td {
|
||||
padding: 14px 16px;
|
||||
border-color: rgba(15, 23, 42, 0.06);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-monitor-table td:first-child {
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-monitor-table tbody tr {
|
||||
transition: background 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-monitor-table tbody tr:hover {
|
||||
background: rgba(37, 99, 235, 0.055);
|
||||
box-shadow: inset 3px 0 0 rgba(37, 99, 235, 0.56);
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-table-pill,
|
||||
.skills-monitor-page .skills-table-number {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 34px;
|
||||
height: 26px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(15, 23, 42, 0.05);
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-table-number.is-success {
|
||||
background: rgba(22, 163, 74, 0.1);
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-table-number.is-danger {
|
||||
background: rgba(225, 29, 72, 0.1);
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-rate-cell {
|
||||
display: inline-grid;
|
||||
grid-template-columns: 52px 76px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--text-primary);
|
||||
font-weight: 650;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-rate-track {
|
||||
display: block;
|
||||
height: 7px;
|
||||
border-radius: 999px;
|
||||
background: rgba(15, 23, 42, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-rate-track span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
min-width: 3px;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(90deg, var(--skills-blue), var(--skills-success));
|
||||
}
|
||||
|
||||
.skills-monitor-page .skills-last-call {
|
||||
color: var(--text-secondary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.skills-monitor-page .monitor-empty {
|
||||
border: 1px dashed rgba(37, 99, 235, 0.18);
|
||||
border-radius: 14px;
|
||||
background: rgba(37, 99, 235, 0.035);
|
||||
}
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
.skills-monitor-page .monitor-stats-grid {
|
||||
grid-template-columns: repeat(3, minmax(180px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.skills-monitor-page .page-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.skills-monitor-page .monitor-stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-monitor-page {
|
||||
--skills-blue: #60a5fa;
|
||||
--skills-cyan: #22d3ee;
|
||||
--skills-success: #34d399;
|
||||
--skills-danger: #fb7185;
|
||||
--skills-card-shadow: 0 14px 34px rgba(0, 0, 0, 0.34);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-monitor-page .page-content {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(37, 99, 235, 0.16) 0%, rgba(11, 17, 32, 0) 280px),
|
||||
var(--bg-secondary);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-monitor-page .page-header {
|
||||
background: rgba(17, 24, 39, 0.88);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-monitor-page .monitor-section {
|
||||
border-color: rgba(96, 165, 250, 0.18);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-monitor-page .monitor-section .section-header {
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-monitor-page .skills-stat-card {
|
||||
border-color: rgba(96, 165, 250, 0.2);
|
||||
background:
|
||||
linear-gradient(145deg, rgba(17, 24, 39, 0.96), rgba(15, 23, 42, 0.94)),
|
||||
radial-gradient(circle at 100% 0%, rgba(96, 165, 250, 0.2), transparent 34%);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-monitor-page .monitor-stat-label,
|
||||
html[data-theme="dark"] .skills-monitor-page .skills-monitor-table th {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-monitor-page .monitor-stat-value,
|
||||
html[data-theme="dark"] .skills-monitor-page .skills-monitor-table td:first-child {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-monitor-page .skills-monitor-table thead {
|
||||
background: linear-gradient(180deg, #172033 0%, #111827 100%);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-monitor-page #skills-monitor-list,
|
||||
html[data-theme="dark"] .skills-monitor-page .skills-monitor-table td {
|
||||
border-color: rgba(148, 163, 184, 0.16);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-monitor-page .skills-table-pill {
|
||||
background: rgba(148, 163, 184, 0.12);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-monitor-page .skills-rate-track {
|
||||
background: rgba(148, 163, 184, 0.16);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .skills-monitor-page .monitor-empty {
|
||||
border-color: rgba(96, 165, 250, 0.22);
|
||||
background: rgba(96, 165, 250, 0.08);
|
||||
}
|
||||
|
||||
/* ==================== 信息收集(FOFA)页面 ==================== */
|
||||
.info-collect-panel {
|
||||
background: var(--bg-primary);
|
||||
@@ -29922,7 +30550,9 @@ html[data-theme="dark"] .timeline-item-thinking {
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .timeline-item-thinking[data-response-stream-placeholder="1"] {
|
||||
background: rgba(96, 165, 250, 0.075);
|
||||
border-left-color: #60a5fa;
|
||||
background: rgba(37, 99, 235, 0.09);
|
||||
border-color: rgba(96, 165, 250, 0.2);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .timeline-item-reasoning_chain {
|
||||
@@ -29933,6 +30563,12 @@ html[data-theme="dark"] .timeline-item-planning {
|
||||
background: rgba(45, 212, 191, 0.075);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .timeline-item-planning[data-orchestration]:not([data-orchestration="plan_execute"]) {
|
||||
border-left-color: #60a5fa;
|
||||
background: #101c2f;
|
||||
border-color: rgba(96, 165, 250, 0.2);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .timeline-item-tool_call,
|
||||
html[data-theme="dark"] .timeline-item-tool_calls_detected {
|
||||
background: rgba(96, 165, 250, 0.075);
|
||||
@@ -29989,6 +30625,30 @@ html[data-theme="dark"] .timeline-item-tool_call.tool-call-failed:hover {
|
||||
background: rgba(239, 68, 68, 0.16);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .timeline-item-tool_call .tool-args,
|
||||
html[data-theme="dark"] .timeline-item-tool_call .tool-result,
|
||||
html[data-theme="dark"] .timeline-item-tool_result .tool-result {
|
||||
background: rgba(51, 65, 85, 0.52);
|
||||
border-color: rgba(148, 163, 184, 0.28);
|
||||
color: #dbe4f0;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.035);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .timeline-item-tool_call:has(.tool-result-section.success) .tool-args,
|
||||
html[data-theme="dark"] .timeline-item-tool_call .tool-result-section.success .tool-result,
|
||||
html[data-theme="dark"] .timeline-item-tool_result:has(.tool-result-section.success) .tool-result {
|
||||
background: rgba(16, 185, 129, 0.13);
|
||||
border-color: rgba(52, 211, 153, 0.38);
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .timeline-item-tool_call:has(.tool-result-section.error) .tool-args,
|
||||
html[data-theme="dark"] .timeline-item-tool_call .tool-result-section.error .tool-result,
|
||||
html[data-theme="dark"] .timeline-item-tool_result:has(.tool-result-section.error) .tool-result {
|
||||
background: rgba(248, 113, 113, 0.13);
|
||||
border-color: rgba(248, 113, 113, 0.42);
|
||||
color: #dbe4f0;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .timeline-item-warning,
|
||||
html[data-theme="dark"] .timeline-item-user_interrupt_continue {
|
||||
background: rgba(251, 191, 36, 0.08);
|
||||
|
||||
@@ -2176,6 +2176,10 @@
|
||||
"clearStatsTitle": "Clear all statistics",
|
||||
"skillsCallStats": "Skills call stats",
|
||||
"searchPlaceholder": "Search Skills...",
|
||||
"overviewTotal": "Skills",
|
||||
"overviewCurrent": "This page",
|
||||
"overviewFiles": "Files",
|
||||
"overviewScripts": "Scripts",
|
||||
"loading": "Loading...",
|
||||
"paginationShow": "Show {{start}}-{{end}} of {{total}}",
|
||||
"perPageLabel": "Per page",
|
||||
|
||||
@@ -2164,6 +2164,10 @@
|
||||
"clearStatsTitle": "清空所有统计数据",
|
||||
"skillsCallStats": "Skills调用统计",
|
||||
"searchPlaceholder": "搜索Skills...",
|
||||
"overviewTotal": "Skills",
|
||||
"overviewCurrent": "当前页",
|
||||
"overviewFiles": "文件",
|
||||
"overviewScripts": "脚本",
|
||||
"loading": "加载中...",
|
||||
"paginationShow": "显示 {{start}}-{{end}} / 共 {{total}} 条",
|
||||
"perPageLabel": "每页显示",
|
||||
|
||||
+299
-13
@@ -2463,6 +2463,7 @@ function syncProcessDetailButtonLabels(messageId, expanded) {
|
||||
function renderProcessDetails(messageId, processDetails, options) {
|
||||
const renderOpts = options || {};
|
||||
const appendMode = !!renderOpts.append;
|
||||
const prependMode = !!renderOpts.prepend;
|
||||
const markLoaded = renderOpts.markLoaded !== false;
|
||||
const messageElement = document.getElementById(messageId);
|
||||
if (!messageElement) {
|
||||
@@ -2546,13 +2547,21 @@ function renderProcessDetails(messageId, processDetails, options) {
|
||||
processDetails = mergeMessageReasoningContentIntoProcessDetails(processDetails, reasoningFromMessage);
|
||||
processDetails = filterNoiseProcessDetails(processDetails);
|
||||
processDetails = dedupeConsecutiveProcessDetailRows(processDetails);
|
||||
const renderedMcpIds = collectMcpExecutionIdsFromProcessDetails(processDetails);
|
||||
if (renderedMcpIds.length > 0) {
|
||||
setPendingMcpExecutionIds(messageElement, renderedMcpIds);
|
||||
}
|
||||
const renderedToolCount = processDetails.filter((d) => d && d.eventType === 'tool_call').length;
|
||||
if (renderedToolCount > 0) {
|
||||
setMcpExecutionSummaryCount(messageElement, renderedToolCount);
|
||||
}
|
||||
if (typeof window.coalesceProcessDetailsToolPairs === 'function') {
|
||||
processDetails = window.coalesceProcessDetailsToolPairs(processDetails);
|
||||
}
|
||||
processDetails = compactWorkflowProcessDetails(processDetails);
|
||||
// 如果没有processDetails或为空,显示空状态
|
||||
if (!processDetails || processDetails.length === 0) {
|
||||
if (!appendMode) {
|
||||
if (!appendMode && !prependMode) {
|
||||
timeline.innerHTML = '<div class="progress-timeline-empty">' + (typeof window.t === 'function' ? window.t('chat.noProcessDetail') : '暂无过程详情(可能执行过快或未触发详细事件)') + '</div>';
|
||||
if (!isProcessDetailsUserExpanded(messageId)) {
|
||||
timeline.classList.remove('expanded');
|
||||
@@ -2561,7 +2570,13 @@ function renderProcessDetails(messageId, processDetails, options) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!appendMode) {
|
||||
const prependAnchor = prependMode ? timeline.firstChild : null;
|
||||
const prependScrollBox = prependMode ? document.getElementById('chat-messages') : null;
|
||||
const prependScrollHeight = prependScrollBox ? prependScrollBox.scrollHeight : 0;
|
||||
const prependScrollTop = prependScrollBox ? prependScrollBox.scrollTop : 0;
|
||||
const prependedIds = [];
|
||||
|
||||
if (!appendMode && !prependMode) {
|
||||
timeline.innerHTML = '';
|
||||
}
|
||||
|
||||
@@ -2713,7 +2728,24 @@ function renderProcessDetails(messageId, processDetails, options) {
|
||||
if (eventType === 'tool_call' && data._mergedResult) {
|
||||
timelineOpts.mergedResult = data._mergedResult;
|
||||
}
|
||||
addTimelineItem(timeline, eventType, timelineOpts);
|
||||
const itemId = addTimelineItem(timeline, eventType, timelineOpts);
|
||||
if (prependMode && itemId) {
|
||||
prependedIds.push(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
function finishPrependRender() {
|
||||
if (!prependMode || prependedIds.length === 0) return;
|
||||
const fragment = document.createDocumentFragment();
|
||||
prependedIds.forEach((id) => {
|
||||
const node = document.getElementById(id);
|
||||
if (node) fragment.appendChild(node);
|
||||
});
|
||||
timeline.insertBefore(fragment, prependAnchor || timeline.firstChild);
|
||||
if (prependScrollBox) {
|
||||
const delta = prependScrollBox.scrollHeight - prependScrollHeight;
|
||||
prependScrollBox.scrollTop = prependScrollTop + delta;
|
||||
}
|
||||
}
|
||||
|
||||
const TIMELINE_RENDER_BATCH = 40;
|
||||
@@ -2725,13 +2757,17 @@ function renderProcessDetails(messageId, processDetails, options) {
|
||||
if (endIdx < processDetails.length) {
|
||||
requestAnimationFrame(() => renderTimelineBatch(endIdx));
|
||||
} else if (markLoaded) {
|
||||
finishPrependRender();
|
||||
finishProcessDetailsRender(messageElement, processDetails, isLazyNotLoaded, timeline);
|
||||
} else {
|
||||
finishPrependRender();
|
||||
}
|
||||
};
|
||||
if (processDetails.length > TIMELINE_RENDER_BATCH) {
|
||||
renderTimelineBatch(0);
|
||||
} else {
|
||||
processDetails.forEach(renderOneProcessDetail);
|
||||
finishPrependRender();
|
||||
if (markLoaded) {
|
||||
finishProcessDetailsRender(messageElement, processDetails, isLazyNotLoaded, timeline);
|
||||
}
|
||||
@@ -2781,6 +2817,18 @@ function prefetchProcessDetailsSummaryHint(messageId, messageElement) {
|
||||
const j = await res.json().catch(() => ({}));
|
||||
if (!res.ok || !j.summary) return;
|
||||
const s = j.summary;
|
||||
const toolCount = parseInt(s.toolCount, 10) || 0;
|
||||
const summaryMcpIds = Array.isArray(s.mcpExecutionIds) ? s.mcpExecutionIds : [];
|
||||
const summaryTools = Array.isArray(s.toolExecutions) ? s.toolExecutions : [];
|
||||
if (summaryTools.length > 0) {
|
||||
setPendingToolExecutionSummaries(messageElement, summaryTools);
|
||||
}
|
||||
if (summaryMcpIds.length > 0) {
|
||||
setPendingMcpExecutionIds(messageElement, summaryMcpIds);
|
||||
}
|
||||
if (toolCount > 0) {
|
||||
setMcpExecutionSummaryCount(messageElement, toolCount);
|
||||
}
|
||||
const timeline = detailsContainer.querySelector('.progress-timeline');
|
||||
if (!timeline || detailsContainer.dataset.loaded === '1') return;
|
||||
const expandLabel = typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情';
|
||||
@@ -2871,16 +2919,114 @@ function getPendingMcpExecutionCount(messageElement) {
|
||||
}
|
||||
}
|
||||
|
||||
function getPendingToolExecutionSummaryCount(messageElement) {
|
||||
if (!messageElement || !messageElement.dataset || !messageElement.dataset.pendingToolExecutionSummaries) {
|
||||
return 0;
|
||||
}
|
||||
try {
|
||||
const tools = JSON.parse(messageElement.dataset.pendingToolExecutionSummaries);
|
||||
return Array.isArray(tools) ? tools.length : 0;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function getMcpExecutionCount(messageElement) {
|
||||
const pending = getPendingMcpExecutionCount(messageElement);
|
||||
if (pending > 0) return pending;
|
||||
const pendingSummaries = getPendingToolExecutionSummaryCount(messageElement);
|
||||
if (pendingSummaries > 0) return pendingSummaries;
|
||||
const toolList = messageElement && messageElement.querySelector('.mcp-tool-list');
|
||||
if (toolList) {
|
||||
return toolList.querySelectorAll('.mcp-detail-btn[data-exec-id]').length;
|
||||
const rendered = toolList.querySelectorAll('.mcp-detail-btn[data-exec-id], .mcp-detail-btn[data-tool-summary]').length;
|
||||
if (rendered > 0) return rendered;
|
||||
}
|
||||
if (messageElement && messageElement.dataset && messageElement.dataset.mcpExecutionCount) {
|
||||
const summaryCount = parseInt(messageElement.dataset.mcpExecutionCount, 10) || 0;
|
||||
if (summaryCount > 0) return summaryCount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function collectMcpExecutionIdsFromProcessDetails(processDetails) {
|
||||
if (!Array.isArray(processDetails)) return [];
|
||||
const seen = new Set();
|
||||
const ids = [];
|
||||
const add = (value) => {
|
||||
const id = value == null ? '' : String(value).trim();
|
||||
if (!id || seen.has(id)) return;
|
||||
seen.add(id);
|
||||
ids.push(id);
|
||||
};
|
||||
processDetails.forEach((detail) => {
|
||||
const data = detail && detail.data && typeof detail.data === 'object' ? detail.data : null;
|
||||
if (!data) return;
|
||||
add(data.executionId);
|
||||
const merged = data._mergedResult && typeof data._mergedResult === 'object' ? data._mergedResult : null;
|
||||
if (merged) add(merged.executionId);
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
function setPendingMcpExecutionIds(messageElement, executionIds) {
|
||||
if (!messageElement || !messageElement.dataset || !Array.isArray(executionIds)) return;
|
||||
const seen = new Set();
|
||||
const ids = [];
|
||||
executionIds.forEach((value) => {
|
||||
const id = value == null ? '' : String(value).trim();
|
||||
if (!id || seen.has(id)) return;
|
||||
seen.add(id);
|
||||
ids.push(id);
|
||||
});
|
||||
if (ids.length > 0) {
|
||||
messageElement.dataset.pendingMcpExecutionIds = JSON.stringify(ids);
|
||||
} else {
|
||||
delete messageElement.dataset.pendingMcpExecutionIds;
|
||||
}
|
||||
if (typeof syncMcpToolsToggleButton === 'function') {
|
||||
syncMcpToolsToggleButton(messageElement);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeToolExecutionSummaryForButton(raw) {
|
||||
const data = raw && typeof raw === 'object' ? raw : {};
|
||||
return {
|
||||
toolName: data.toolName || data.name || '',
|
||||
status: data.status || '',
|
||||
executionId: data.executionId || '',
|
||||
toolCallId: data.toolCallId || '',
|
||||
processDetailId: data.processDetailId || ''
|
||||
};
|
||||
}
|
||||
|
||||
function setPendingToolExecutionSummaries(messageElement, summaries) {
|
||||
if (!messageElement || !messageElement.dataset || !Array.isArray(summaries)) return;
|
||||
const normalized = summaries
|
||||
.map(normalizeToolExecutionSummaryForButton)
|
||||
.filter((item) => item.toolName || item.executionId || item.toolCallId);
|
||||
if (normalized.length > 0) {
|
||||
messageElement.dataset.pendingToolExecutionSummaries = JSON.stringify(normalized);
|
||||
} else {
|
||||
delete messageElement.dataset.pendingToolExecutionSummaries;
|
||||
}
|
||||
if (typeof syncMcpToolsToggleButton === 'function') {
|
||||
syncMcpToolsToggleButton(messageElement);
|
||||
}
|
||||
}
|
||||
|
||||
function setMcpExecutionSummaryCount(messageElement, count) {
|
||||
if (!messageElement || !messageElement.dataset) return;
|
||||
const n = parseInt(count, 10) || 0;
|
||||
if (n > 0) {
|
||||
messageElement.dataset.mcpExecutionCount = String(n);
|
||||
} else {
|
||||
delete messageElement.dataset.mcpExecutionCount;
|
||||
}
|
||||
if (typeof syncMcpToolsToggleButton === 'function') {
|
||||
syncMcpToolsToggleButton(messageElement);
|
||||
}
|
||||
}
|
||||
|
||||
function formatMcpToolsToggleLabel(count, expanded) {
|
||||
if (expanded) {
|
||||
if (typeof window.t === 'function') {
|
||||
@@ -2986,12 +3132,92 @@ function syncMcpToolsToggleButton(messageElement) {
|
||||
toolsToggle.innerHTML = '<span>' + formatMcpToolsToggleLabel(count, expanded) + '</span>';
|
||||
}
|
||||
|
||||
function cssEscapeValue(value) {
|
||||
const s = String(value || '');
|
||||
if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
|
||||
return CSS.escape(s);
|
||||
}
|
||||
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||
}
|
||||
|
||||
async function ensureProcessDetailsForToolFocus(messageElement, anchorId) {
|
||||
if (!messageElement || !messageElement.id) return null;
|
||||
const detailsId = 'process-details-' + messageElement.id;
|
||||
let detailsContainer = document.getElementById(detailsId);
|
||||
const backendId = messageElement.dataset ? String(messageElement.dataset.backendMessageId || '').trim() : '';
|
||||
if (detailsContainer && detailsContainer.dataset && detailsContainer.dataset.lazyNotLoaded === '1' && backendId && typeof window.loadProcessDetailsPaginated === 'function') {
|
||||
await window.loadProcessDetailsPaginated(messageElement.id, backendId, { autoLoadAll: false, anchorId: anchorId || '' });
|
||||
detailsContainer = document.getElementById(detailsId);
|
||||
} else if (!detailsContainer && backendId && typeof renderProcessDetails === 'function') {
|
||||
renderProcessDetails(messageElement.id, null);
|
||||
detailsContainer = document.getElementById(detailsId);
|
||||
if (detailsContainer && typeof window.loadProcessDetailsPaginated === 'function') {
|
||||
await window.loadProcessDetailsPaginated(messageElement.id, backendId, { autoLoadAll: false, anchorId: anchorId || '' });
|
||||
detailsContainer = document.getElementById(detailsId);
|
||||
}
|
||||
}
|
||||
if (typeof window.expandProcessDetailsTimeline === 'function') {
|
||||
window.expandProcessDetailsTimeline(messageElement.id);
|
||||
} else if (typeof toggleProcessDetails === 'function') {
|
||||
const timeline = detailsContainer && detailsContainer.querySelector('.progress-timeline');
|
||||
if (!timeline || !timeline.classList.contains('expanded')) {
|
||||
toggleProcessDetails(null, messageElement.id);
|
||||
}
|
||||
}
|
||||
return document.getElementById(detailsId);
|
||||
}
|
||||
|
||||
async function focusToolExecutionInProcessDetails(messageElement, summary, index) {
|
||||
const item = normalizeToolExecutionSummaryForButton(summary);
|
||||
let detailsContainer = await ensureProcessDetailsForToolFocus(messageElement, item.processDetailId || '');
|
||||
let timeline = detailsContainer && detailsContainer.querySelector('.progress-timeline');
|
||||
if (!timeline) return;
|
||||
let target = null;
|
||||
if (item.processDetailId) {
|
||||
target = timeline.querySelector('[data-process-detail-id="' + cssEscapeValue(item.processDetailId) + '"]');
|
||||
}
|
||||
if (!target && item.toolCallId) {
|
||||
target = timeline.querySelector('[data-tool-call-id="' + cssEscapeValue(item.toolCallId) + '"]');
|
||||
}
|
||||
if (!target) {
|
||||
const toolItems = timeline.querySelectorAll('.timeline-item-tool_call');
|
||||
target = toolItems[index] || null;
|
||||
}
|
||||
if (!target && item.processDetailId && messageElement.dataset && messageElement.dataset.backendMessageId && typeof window.loadProcessDetailsPaginated === 'function') {
|
||||
await window.loadProcessDetailsPaginated(messageElement.id, messageElement.dataset.backendMessageId, {
|
||||
autoLoadAll: false,
|
||||
anchorId: item.processDetailId
|
||||
});
|
||||
detailsContainer = document.getElementById('process-details-' + messageElement.id);
|
||||
timeline = detailsContainer && detailsContainer.querySelector('.progress-timeline');
|
||||
target = timeline ? timeline.querySelector('[data-process-detail-id="' + cssEscapeValue(item.processDetailId) + '"]') : null;
|
||||
}
|
||||
if (!target) return;
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
target.classList.remove('timeline-item-focus-pulse');
|
||||
void target.offsetWidth;
|
||||
target.classList.add('timeline-item-focus-pulse');
|
||||
setTimeout(() => {
|
||||
target.classList.remove('timeline-item-focus-pulse');
|
||||
}, 2200);
|
||||
}
|
||||
|
||||
function toggleMcpToolList(assistantMessageId) {
|
||||
const messageEl = document.getElementById(assistantMessageId);
|
||||
if (!messageEl) return;
|
||||
const chrome = ensureMcpCallSectionChrome(messageEl, assistantMessageId);
|
||||
if (!chrome) return;
|
||||
const { toolList } = chrome;
|
||||
if (
|
||||
!getPendingMcpExecutionCount(messageEl) &&
|
||||
!getPendingToolExecutionSummaryCount(messageEl) &&
|
||||
!toolList.querySelector('.mcp-detail-btn[data-exec-id], .mcp-detail-btn[data-tool-summary]')
|
||||
) {
|
||||
if (typeof toggleProcessDetails === 'function') {
|
||||
toggleProcessDetails(null, assistantMessageId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const willExpand = !toolList.classList.contains('expanded');
|
||||
if (willExpand) {
|
||||
ensureMcpCallButtons(messageEl);
|
||||
@@ -3007,6 +3233,9 @@ window.syncMcpToolsToggleButton = syncMcpToolsToggleButton;
|
||||
window.isProcessDetailsUserExpanded = isProcessDetailsUserExpanded;
|
||||
window.syncProcessDetailButtonLabels = syncProcessDetailButtonLabels;
|
||||
window.ensureMcpCallSectionChrome = ensureMcpCallSectionChrome;
|
||||
window.setMcpExecutionSummaryCount = setMcpExecutionSummaryCount;
|
||||
window.setPendingMcpExecutionIds = setPendingMcpExecutionIds;
|
||||
window.setPendingToolExecutionSummaries = setPendingToolExecutionSummaries;
|
||||
|
||||
/** 将 MCP 工具按钮挂到独立工具列表,并批量解析工具名 */
|
||||
function appendMcpCallButtons(messageElement, executionIds) {
|
||||
@@ -3033,28 +3262,85 @@ function appendMcpCallButtons(messageElement, executionIds) {
|
||||
syncMcpToolsToggleButton(messageElement);
|
||||
}
|
||||
|
||||
function appendMcpCallSummaryButtons(messageElement, summaries) {
|
||||
if (!messageElement || !Array.isArray(summaries) || summaries.length === 0) {
|
||||
return;
|
||||
}
|
||||
const chrome = ensureMcpCallSectionChrome(messageElement, messageElement.id);
|
||||
if (!chrome) return;
|
||||
const toolList = chrome.toolList;
|
||||
summaries.forEach((raw, index) => {
|
||||
const item = normalizeToolExecutionSummaryForButton(raw);
|
||||
const key = item.executionId || item.toolCallId || `${item.toolName || 'tool'}-${index + 1}`;
|
||||
const selector = '.mcp-detail-btn[data-tool-summary="' + CSS.escape(String(key)) + '"]';
|
||||
if (toolList.querySelector(selector) || (item.executionId && toolList.querySelector('.mcp-detail-btn[data-exec-id="' + CSS.escape(String(item.executionId)) + '"]'))) {
|
||||
return;
|
||||
}
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'mcp-detail-btn';
|
||||
btn.dataset.toolSummary = key;
|
||||
if (item.executionId) {
|
||||
btn.dataset.execId = item.executionId;
|
||||
}
|
||||
if (item.processDetailId || item.toolCallId) {
|
||||
btn.onclick = async () => {
|
||||
await focusToolExecutionInProcessDetails(messageElement, item, index);
|
||||
};
|
||||
} else if (item.executionId) {
|
||||
btn.onclick = () => showMCPDetail(item.executionId);
|
||||
} else {
|
||||
btn.onclick = async () => {
|
||||
await focusToolExecutionInProcessDetails(messageElement, item, index);
|
||||
};
|
||||
}
|
||||
renderToolExecutionButtonContent(btn, item.toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具'), String(index + 1), item.status);
|
||||
toolList.appendChild(btn);
|
||||
});
|
||||
syncMcpToolsToggleButton(messageElement);
|
||||
}
|
||||
|
||||
/** 历史会话懒加载:用户展开工具列表时再渲染工具按钮 */
|
||||
function ensureMcpCallButtons(messageElement) {
|
||||
if (!messageElement || !messageElement.dataset || !messageElement.dataset.pendingMcpExecutionIds) {
|
||||
if (!messageElement || !messageElement.dataset) {
|
||||
return;
|
||||
}
|
||||
let executionIds;
|
||||
try {
|
||||
executionIds = JSON.parse(messageElement.dataset.pendingMcpExecutionIds);
|
||||
} catch (e) {
|
||||
let renderedSummaryExecutions = false;
|
||||
if (messageElement.dataset.pendingToolExecutionSummaries) {
|
||||
let summaries;
|
||||
try {
|
||||
summaries = JSON.parse(messageElement.dataset.pendingToolExecutionSummaries);
|
||||
} catch (e) {
|
||||
delete messageElement.dataset.pendingToolExecutionSummaries;
|
||||
summaries = [];
|
||||
}
|
||||
if (Array.isArray(summaries) && summaries.length > 0) {
|
||||
appendMcpCallSummaryButtons(messageElement, summaries);
|
||||
renderedSummaryExecutions = true;
|
||||
}
|
||||
delete messageElement.dataset.pendingToolExecutionSummaries;
|
||||
}
|
||||
if (renderedSummaryExecutions) {
|
||||
delete messageElement.dataset.pendingMcpExecutionIds;
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(executionIds) || executionIds.length === 0) {
|
||||
if (messageElement.dataset.pendingMcpExecutionIds) {
|
||||
let executionIds;
|
||||
try {
|
||||
executionIds = JSON.parse(messageElement.dataset.pendingMcpExecutionIds);
|
||||
} catch (e) {
|
||||
delete messageElement.dataset.pendingMcpExecutionIds;
|
||||
executionIds = [];
|
||||
}
|
||||
if (Array.isArray(executionIds) && executionIds.length > 0) {
|
||||
appendMcpCallButtons(messageElement, executionIds);
|
||||
}
|
||||
delete messageElement.dataset.pendingMcpExecutionIds;
|
||||
return;
|
||||
}
|
||||
appendMcpCallButtons(messageElement, executionIds);
|
||||
delete messageElement.dataset.pendingMcpExecutionIds;
|
||||
}
|
||||
|
||||
window.ensureMcpCallButtons = ensureMcpCallButtons;
|
||||
window.appendMcpCallButtons = appendMcpCallButtons;
|
||||
window.appendMcpCallSummaryButtons = appendMcpCallSummaryButtons;
|
||||
|
||||
function normalizeToolExecutionSummary(raw) {
|
||||
if (typeof raw === 'string') {
|
||||
|
||||
+88
-19
@@ -1376,27 +1376,71 @@ function getProcessDetailsLoadMoreLabel(hasMore) {
|
||||
}
|
||||
|
||||
function updateProcessDetailsLoadMoreButton(assistantMessageId, backendMessageId, hasMore) {
|
||||
updateProcessDetailsPaginationButtons(assistantMessageId, backendMessageId, {
|
||||
hasPrev: false,
|
||||
hasNext: hasMore
|
||||
});
|
||||
}
|
||||
|
||||
function updateProcessDetailsPaginationButtons(assistantMessageId, backendMessageId, pageState) {
|
||||
const detailsContainer = document.getElementById('process-details-' + assistantMessageId);
|
||||
if (!detailsContainer) return;
|
||||
const state = pageState || {};
|
||||
|
||||
let btn = detailsContainer.querySelector('.process-details-load-more-btn');
|
||||
if (!hasMore) {
|
||||
if (btn) btn.remove();
|
||||
let prevBtn = detailsContainer.querySelector('.process-details-load-prev-btn');
|
||||
if (!state.hasPrev) {
|
||||
if (prevBtn) prevBtn.remove();
|
||||
} else {
|
||||
if (!prevBtn) {
|
||||
prevBtn = document.createElement('button');
|
||||
prevBtn.type = 'button';
|
||||
prevBtn.className = 'mcp-detail-btn process-details-load-prev-btn';
|
||||
const content = detailsContainer.querySelector('.process-details-content');
|
||||
if (content) {
|
||||
detailsContainer.insertBefore(prevBtn, content);
|
||||
} else {
|
||||
detailsContainer.prepend(prevBtn);
|
||||
}
|
||||
}
|
||||
const loadMoreText = typeof window.t === 'function' ? window.t('common.loadMore') : '加载更多';
|
||||
let prevPageText = typeof window.t === 'function' ? window.t('chat.previousPage') : '上一页';
|
||||
if (!prevPageText || prevPageText === 'chat.previousPage') prevPageText = '上一页';
|
||||
prevBtn.textContent = loadMoreText + ' · ' + prevPageText;
|
||||
prevBtn.disabled = false;
|
||||
prevBtn.onclick = async () => {
|
||||
if (detailsContainer.dataset.loadingPrev === '1') return;
|
||||
detailsContainer.dataset.loadingPrev = '1';
|
||||
prevBtn.disabled = true;
|
||||
prevBtn.textContent = typeof window.t === 'function' ? window.t('common.loading') : '加载中…';
|
||||
try {
|
||||
await loadProcessDetailsPaginated(assistantMessageId, backendMessageId, {
|
||||
prepend: true,
|
||||
autoLoadAll: false
|
||||
});
|
||||
} finally {
|
||||
detailsContainer.dataset.loadingPrev = '0';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let nextBtn = detailsContainer.querySelector('.process-details-load-more-btn');
|
||||
if (!state.hasNext) {
|
||||
if (nextBtn) nextBtn.remove();
|
||||
return;
|
||||
}
|
||||
if (!btn) {
|
||||
btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'mcp-detail-btn process-details-load-more-btn';
|
||||
detailsContainer.appendChild(btn);
|
||||
if (!nextBtn) {
|
||||
nextBtn = document.createElement('button');
|
||||
nextBtn.type = 'button';
|
||||
nextBtn.className = 'mcp-detail-btn process-details-load-more-btn';
|
||||
detailsContainer.appendChild(nextBtn);
|
||||
}
|
||||
btn.textContent = getProcessDetailsLoadMoreLabel(true);
|
||||
btn.disabled = false;
|
||||
btn.onclick = async () => {
|
||||
nextBtn.textContent = getProcessDetailsLoadMoreLabel(true);
|
||||
nextBtn.disabled = false;
|
||||
nextBtn.onclick = async () => {
|
||||
if (detailsContainer.dataset.loadingMore === '1') return;
|
||||
detailsContainer.dataset.loadingMore = '1';
|
||||
btn.disabled = true;
|
||||
btn.textContent = typeof window.t === 'function' ? window.t('common.loading') : '加载中…';
|
||||
nextBtn.disabled = true;
|
||||
nextBtn.textContent = typeof window.t === 'function' ? window.t('common.loading') : '加载中…';
|
||||
try {
|
||||
await loadProcessDetailsPaginated(assistantMessageId, backendMessageId, {
|
||||
append: true,
|
||||
@@ -1420,14 +1464,28 @@ async function loadProcessDetailsPaginated(assistantMessageId, backendMessageId,
|
||||
const autoLoadAll = opts.autoLoadAll !== false;
|
||||
const detailsContainer = document.getElementById('process-details-' + assistantMessageId);
|
||||
const PAGE = PROCESS_DETAILS_PAGE_SIZE;
|
||||
let offset = opts.append && detailsContainer && detailsContainer.dataset.nextOffset
|
||||
const existingNextOffset = detailsContainer && detailsContainer.dataset.nextOffset
|
||||
? parseInt(detailsContainer.dataset.nextOffset, 10) || 0
|
||||
: 0;
|
||||
const prepend = !!opts.prepend;
|
||||
let offset = prepend && detailsContainer && detailsContainer.dataset.prevOffset
|
||||
? parseInt(detailsContainer.dataset.prevOffset, 10) || 0
|
||||
: opts.append && detailsContainer && detailsContainer.dataset.nextOffset
|
||||
? parseInt(detailsContainer.dataset.nextOffset, 10) || 0
|
||||
: 0;
|
||||
const anchorId = opts.anchorId != null ? String(opts.anchorId).trim() : '';
|
||||
let isFirst = !opts.append;
|
||||
while (true) {
|
||||
const params = new URLSearchParams();
|
||||
params.set('limit', String(PAGE));
|
||||
if (anchorId && !opts.append && !prepend) {
|
||||
params.set('anchorId', anchorId);
|
||||
} else {
|
||||
params.set('offset', String(offset));
|
||||
}
|
||||
const res = await apiFetch(
|
||||
'/api/messages/' + encodeURIComponent(String(backendMessageId)) +
|
||||
'/process-details?limit=' + PAGE + '&offset=' + offset
|
||||
'/process-details?' + params.toString()
|
||||
);
|
||||
const j = await res.json().catch(() => ({}));
|
||||
if (!res.ok) {
|
||||
@@ -1436,16 +1494,27 @@ async function loadProcessDetailsPaginated(assistantMessageId, backendMessageId,
|
||||
const details = (j && Array.isArray(j.processDetails)) ? j.processDetails : [];
|
||||
const hasMore = !!(j && j.hasMore);
|
||||
renderProcessDetails(assistantMessageId, details, {
|
||||
append: !isFirst,
|
||||
append: !isFirst || opts.append,
|
||||
prepend: prepend,
|
||||
markLoaded: autoLoadAll ? !hasMore : true
|
||||
});
|
||||
offset += details.length;
|
||||
const responseOffset = j && typeof j.offset === 'number' ? j.offset : offset;
|
||||
const total = j && typeof j.total === 'number' ? j.total : responseOffset + details.length;
|
||||
const nextOffset = prepend && existingNextOffset > 0
|
||||
? existingNextOffset
|
||||
: responseOffset + details.length;
|
||||
const prevOffset = Math.max(0, responseOffset - PAGE);
|
||||
offset = nextOffset;
|
||||
if (detailsContainer) {
|
||||
detailsContainer.dataset.lazyNotLoaded = '0';
|
||||
detailsContainer.dataset.loaded = hasMore ? 'partial' : '1';
|
||||
detailsContainer.dataset.nextOffset = String(offset);
|
||||
detailsContainer.dataset.prevOffset = String(prevOffset);
|
||||
detailsContainer.dataset.nextOffset = String(nextOffset);
|
||||
}
|
||||
updateProcessDetailsLoadMoreButton(assistantMessageId, backendMessageId, hasMore && !autoLoadAll);
|
||||
updateProcessDetailsPaginationButtons(assistantMessageId, backendMessageId, {
|
||||
hasPrev: !autoLoadAll && responseOffset > 0,
|
||||
hasNext: !autoLoadAll && nextOffset < total
|
||||
});
|
||||
if (!hasMore || details.length === 0 || !autoLoadAll) {
|
||||
break;
|
||||
}
|
||||
|
||||
+63
-30
@@ -167,18 +167,24 @@ function renderSkillsList() {
|
||||
const fc = typeof skill.file_count === 'number' && skill.file_count > 0
|
||||
? _t('skills.cardFiles', { count: skill.file_count })
|
||||
: '';
|
||||
const meta = [ver, fc, sc].filter(Boolean).join(' · ');
|
||||
const metaItems = [ver, fc, sc].filter(Boolean);
|
||||
const initial = (skill.name || sid || 'S').trim().charAt(0).toUpperCase();
|
||||
return `
|
||||
<div class="skill-card">
|
||||
<div class="skill-card-header">
|
||||
<h3 class="skill-card-title">${escapeHtml(skill.name || sid)}</h3>
|
||||
${meta ? `<div class="skill-card-meta" style="opacity:0.85;font-size:12px;margin-top:4px;">${escapeHtml(meta)}</div>` : ''}
|
||||
<div class="skill-card-description">${escapeHtml(skill.description || _t('skills.noDescription'))}</div>
|
||||
</div>
|
||||
<div class="skill-card-actions">
|
||||
<button type="button" class="btn-secondary btn-small" data-skill-view="${escapeHtml(sid)}">${_t('common.view')}</button>
|
||||
<button type="button" class="btn-secondary btn-small" data-skill-edit="${escapeHtml(sid)}">${_t('common.edit')}</button>
|
||||
<button type="button" class="btn-secondary btn-small btn-danger" data-skill-delete="${escapeHtml(sid)}">${_t('common.delete')}</button>
|
||||
<div class="skill-card-mark" aria-hidden="true">${escapeHtml(initial)}</div>
|
||||
<div class="skill-card-body">
|
||||
<div class="skill-card-header">
|
||||
<div class="skill-card-title-row">
|
||||
<h3 class="skill-card-title">${escapeHtml(skill.name || sid)}</h3>
|
||||
${metaItems.length ? `<div class="skill-card-meta">${metaItems.map(item => `<span>${escapeHtml(item)}</span>`).join('')}</div>` : ''}
|
||||
</div>
|
||||
<div class="skill-card-description">${escapeHtml(skill.description || _t('skills.noDescription'))}</div>
|
||||
</div>
|
||||
<div class="skill-card-actions">
|
||||
<button type="button" class="btn-secondary btn-small" data-skill-view="${escapeHtml(sid)}">${_t('common.view')}</button>
|
||||
<button type="button" class="btn-secondary btn-small" data-skill-edit="${escapeHtml(sid)}">${_t('common.edit')}</button>
|
||||
<button type="button" class="btn-secondary btn-small btn-danger" data-skill-delete="${escapeHtml(sid)}">${_t('common.delete')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -347,6 +353,27 @@ function updateSkillsManagementStats() {
|
||||
if (totalEl) {
|
||||
totalEl.textContent = skillsPagination.total;
|
||||
}
|
||||
|
||||
const currentCountEl = document.getElementById('skills-current-count');
|
||||
if (currentCountEl) {
|
||||
currentCountEl.textContent = skillsList.length;
|
||||
}
|
||||
|
||||
const fileCountEl = document.getElementById('skills-file-count');
|
||||
if (fileCountEl) {
|
||||
const fileCount = skillsList.reduce((sum, skill) => {
|
||||
return sum + (typeof skill.file_count === 'number' ? skill.file_count : 0);
|
||||
}, 0);
|
||||
fileCountEl.textContent = fileCount;
|
||||
}
|
||||
|
||||
const scriptCountEl = document.getElementById('skills-script-count');
|
||||
if (scriptCountEl) {
|
||||
const scriptCount = skillsList.reduce((sum, skill) => {
|
||||
return sum + (typeof skill.script_count === 'number' ? skill.script_count : 0);
|
||||
}, 0);
|
||||
scriptCountEl.textContent = scriptCount;
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索skills
|
||||
@@ -920,23 +947,23 @@ function renderSkillsMonitor() {
|
||||
: '0.0';
|
||||
|
||||
statsEl.innerHTML = `
|
||||
<div class="monitor-stat-card">
|
||||
<div class="monitor-stat-card skills-stat-card skills-stat-card--total">
|
||||
<div class="monitor-stat-label">${_t('skills.totalSkillsCount')}</div>
|
||||
<div class="monitor-stat-value">${skillsStats.total}</div>
|
||||
</div>
|
||||
<div class="monitor-stat-card">
|
||||
<div class="monitor-stat-card skills-stat-card skills-stat-card--calls">
|
||||
<div class="monitor-stat-label">${_t('skills.totalCallsCount')}</div>
|
||||
<div class="monitor-stat-value">${skillsStats.totalCalls}</div>
|
||||
</div>
|
||||
<div class="monitor-stat-card">
|
||||
<div class="monitor-stat-card skills-stat-card skills-stat-card--success">
|
||||
<div class="monitor-stat-label">${_t('skills.successfulCalls')}</div>
|
||||
<div class="monitor-stat-value" style="color: #28a745;">${skillsStats.totalSuccess}</div>
|
||||
<div class="monitor-stat-value is-success">${skillsStats.totalSuccess}</div>
|
||||
</div>
|
||||
<div class="monitor-stat-card">
|
||||
<div class="monitor-stat-card skills-stat-card skills-stat-card--failed">
|
||||
<div class="monitor-stat-label">${_t('skills.failedCalls')}</div>
|
||||
<div class="monitor-stat-value" style="color: #dc3545;">${skillsStats.totalFailed}</div>
|
||||
<div class="monitor-stat-value is-danger">${skillsStats.totalFailed}</div>
|
||||
</div>
|
||||
<div class="monitor-stat-card">
|
||||
<div class="monitor-stat-card skills-stat-card skills-stat-card--rate">
|
||||
<div class="monitor-stat-label">${_t('skills.successRate')}</div>
|
||||
<div class="monitor-stat-value">${successRate}%</div>
|
||||
</div>
|
||||
@@ -966,15 +993,15 @@ function renderSkillsMonitor() {
|
||||
});
|
||||
|
||||
monitorListEl.innerHTML = `
|
||||
<table class="monitor-table">
|
||||
<table class="monitor-table skills-monitor-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: left !important;">${_t('skills.skillName')}</th>
|
||||
<th style="text-align: center;">${_t('skills.totalCalls')}</th>
|
||||
<th style="text-align: center;">${_t('skills.success')}</th>
|
||||
<th style="text-align: center;">${_t('skills.failure')}</th>
|
||||
<th style="text-align: center;">${_t('skills.successRate')}</th>
|
||||
<th style="text-align: left;">${_t('skills.lastCallTime')}</th>
|
||||
<th>${_t('skills.skillName')}</th>
|
||||
<th>${_t('skills.totalCalls')}</th>
|
||||
<th>${_t('skills.success')}</th>
|
||||
<th>${_t('skills.failure')}</th>
|
||||
<th>${_t('skills.successRate')}</th>
|
||||
<th>${_t('skills.lastCallTime')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -984,15 +1011,21 @@ function renderSkillsMonitor() {
|
||||
const failedCalls = stat.failed_calls || 0;
|
||||
const successRate = totalCalls > 0 ? ((successCalls / totalCalls) * 100).toFixed(1) : '0.0';
|
||||
const lastCallTime = stat.last_call_time && stat.last_call_time !== '-' ? stat.last_call_time : '-';
|
||||
const rateValue = Math.max(0, Math.min(100, Number(successRate) || 0));
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td style="text-align: left !important;"><strong>${escapeHtml(stat.skill_name || '')}</strong></td>
|
||||
<td style="text-align: center;">${totalCalls}</td>
|
||||
<td style="text-align: center; color: #28a745; font-weight: 500;">${successCalls}</td>
|
||||
<td style="text-align: center; color: #dc3545; font-weight: 500;">${failedCalls}</td>
|
||||
<td style="text-align: center;">${successRate}%</td>
|
||||
<td style="color: var(--text-secondary);">${escapeHtml(lastCallTime)}</td>
|
||||
<td><strong>${escapeHtml(stat.skill_name || '')}</strong></td>
|
||||
<td><span class="skills-table-pill">${totalCalls}</span></td>
|
||||
<td><span class="skills-table-number is-success">${successCalls}</span></td>
|
||||
<td><span class="skills-table-number is-danger">${failedCalls}</span></td>
|
||||
<td>
|
||||
<div class="skills-rate-cell">
|
||||
<span>${successRate}%</span>
|
||||
<span class="skills-rate-track"><span style="width: ${rateValue}%;"></span></span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="skills-last-call">${escapeHtml(lastCallTime)}</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('')}
|
||||
|
||||
Reference in New Issue
Block a user