Add files via upload

This commit is contained in:
公明
2026-07-09 15:55:55 +08:00
committed by GitHub
parent 56ee11a97a
commit 1c576db75d
7 changed files with 1188 additions and 114 deletions
+710 -50
View File
@@ -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);
+4
View File
@@ -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",
+4
View File
@@ -2164,6 +2164,10 @@
"clearStatsTitle": "清空所有统计数据",
"skillsCallStats": "Skills调用统计",
"searchPlaceholder": "搜索Skills...",
"overviewTotal": "Skills",
"overviewCurrent": "当前页",
"overviewFiles": "文件",
"overviewScripts": "脚本",
"loading": "加载中...",
"paginationShow": "显示 {{start}}-{{end}} / 共 {{total}} 条",
"perPageLabel": "每页显示",
+299 -13
View File
@@ -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
View File
@@ -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
View File
@@ -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('')}
+20 -2
View File
@@ -2677,7 +2677,7 @@
</div>
<!-- Skills状态监控页面 -->
<div id="page-skills-monitor" class="page">
<div id="page-skills-monitor" class="page skills-monitor-page">
<div class="page-header">
<h2 data-i18n="skills.monitorTitle">Skills状态监控</h2>
<div class="page-header-actions">
@@ -2710,7 +2710,7 @@
</div>
<!-- Skills管理页面 -->
<div id="page-skills-management" class="page">
<div id="page-skills-management" class="page skills-management-page">
<div class="page-header">
<h2 data-i18n="skills.title">Skills管理</h2>
<div class="page-header-actions">
@@ -2719,6 +2719,24 @@
</div>
</div>
<div class="page-content page-content-with-pagination">
<div class="skills-overview" id="skills-management-stats">
<div class="skills-overview-card skills-overview-card-primary">
<span class="skills-overview-label" data-i18n="skillsPage.overviewTotal">Skills</span>
<strong class="skill-stat-value">0</strong>
</div>
<div class="skills-overview-card">
<span class="skills-overview-label" data-i18n="skillsPage.overviewCurrent">当前页</span>
<strong id="skills-current-count">0</strong>
</div>
<div class="skills-overview-card">
<span class="skills-overview-label" data-i18n="skillsPage.overviewFiles">文件</span>
<strong id="skills-file-count">0</strong>
</div>
<div class="skills-overview-card">
<span class="skills-overview-label" data-i18n="skillsPage.overviewScripts">脚本</span>
<strong id="skills-script-count">0</strong>
</div>
</div>
<div class="skills-controls">
<div class="skills-search-box">
<input type="text" id="skills-search" data-i18n="skillsPage.searchPlaceholder" data-i18n-attr="placeholder" placeholder="搜索Skills..." oninput="handleSkillsSearchInput()" onkeydown="if(event.key==='Enter') searchSkills()" />