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:
+12
-3
@@ -213,12 +213,12 @@ html[data-theme="dark"] .c2-session-main-empty__icon {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
border: 1px solid var(--c2-border);
|
border: 1.5px solid var(--c2-border);
|
||||||
border-radius: var(--c2-radius-sm);
|
border-radius: var(--c2-radius-sm);
|
||||||
background: var(--c2-surface);
|
background: var(--c2-surface);
|
||||||
color: var(--c2-text);
|
color: var(--c2-text);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.25;
|
line-height: 1.5;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||||
@@ -230,7 +230,16 @@ html[data-theme="dark"] .c2-session-main-empty__icon {
|
|||||||
|
|
||||||
.c2-form-select-ui.open .c2-form-select-trigger {
|
.c2-form-select-ui.open .c2-form-select-trigger {
|
||||||
border-color: var(--c2-accent);
|
border-color: var(--c2-accent);
|
||||||
box-shadow: 0 0 0 2px var(--c2-accent-dim);
|
box-shadow: 0 0 0 3px var(--c2-accent-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .c2-modal .c2-form-select-trigger {
|
||||||
|
background: var(--c2-surface);
|
||||||
|
border-color: var(--c2-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .c2-modal .c2-form-select-ui.open .c2-form-select-trigger {
|
||||||
|
border-color: var(--c2-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.c2-form-select-ui.open {
|
.c2-form-select-ui.open {
|
||||||
|
|||||||
+280
-8
@@ -4190,6 +4190,41 @@ html[data-theme="dark"] .openapi-doc-btn:hover {
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mcp-detail-btn[data-exec-id] {
|
||||||
|
min-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mcp-detail-btn[data-status="completed"] {
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-color: rgba(40, 167, 69, 0.34);
|
||||||
|
background: rgba(40, 167, 69, 0.055);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mcp-detail-btn[data-status="failed"] {
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-color: rgba(220, 53, 69, 0.36);
|
||||||
|
background: rgba(220, 53, 69, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mcp-detail-btn[data-status="running"],
|
||||||
|
.mcp-detail-btn[data-status="pending"] {
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-color: rgba(0, 102, 255, 0.32);
|
||||||
|
background: rgba(0, 102, 255, 0.055);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mcp-detail-btn[data-status="cancelled"] {
|
||||||
|
color: var(--text-muted);
|
||||||
|
border-color: rgba(148, 163, 184, 0.32);
|
||||||
|
background: rgba(148, 163, 184, 0.065);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mcp-detail-btn .mcp-tool-name {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
/* 登录遮罩 */
|
/* 登录遮罩 */
|
||||||
.login-overlay {
|
.login-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -5163,7 +5198,14 @@ html[data-theme="dark"] .openapi-doc-btn:hover {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 过程详情 Markdown:避免长 URL/代码/表格撑破紫/蓝时间线条目 */
|
/* 过程详情纯文本:避免长 URL/代码/表格撑破时间线条目 */
|
||||||
|
.timeline-item-content .timeline-plain-text {
|
||||||
|
margin: 0;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.timeline-item-content p,
|
.timeline-item-content p,
|
||||||
.timeline-item-content li,
|
.timeline-item-content li,
|
||||||
.timeline-item-content td,
|
.timeline-item-content td,
|
||||||
@@ -5200,6 +5242,16 @@ html[data-theme="dark"] .openapi-doc-btn:hover {
|
|||||||
contain-intrinsic-size: auto 72px;
|
contain-intrinsic-size: auto 72px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeline-live-pruned-marker {
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px dashed rgba(148, 163, 184, 0.35);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
background: rgba(148, 163, 184, 0.08);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
.tool-details {
|
.tool-details {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -5481,20 +5533,73 @@ html[data-theme="dark"] .openapi-doc-btn:hover {
|
|||||||
border: 1px solid rgba(220, 53, 69, 0.3);
|
border: 1px solid rgba(220, 53, 69, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 工具调用项状态:全程保持「信息蓝」系,完成态不用绿色(避免与工具成功结果混淆) */
|
/* 工具调用项状态:摘要态直接表达成功/失败,避免必须展开才能判断状态 */
|
||||||
.timeline-item-tool_call.tool-call-running {
|
.timeline-item-tool_call.tool-call-running {
|
||||||
border-left-color: #42a5f5;
|
border: 1px solid rgba(66, 165, 245, 0.32);
|
||||||
background: rgba(66, 165, 245, 0.1);
|
border-left: 3px solid #42a5f5;
|
||||||
|
background: rgba(66, 165, 245, 0.075);
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-item-tool_call.tool-call-completed {
|
.timeline-item-tool_call.tool-call-completed {
|
||||||
border-left-color: #0d47a1;
|
border: 1px solid rgba(40, 167, 69, 0.34);
|
||||||
background: rgba(13, 71, 161, 0.08);
|
border-left: 3px solid var(--success-color);
|
||||||
|
background: rgba(40, 167, 69, 0.075);
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-item-tool_call.tool-call-failed {
|
.timeline-item-tool_call.tool-call-failed {
|
||||||
border-left-color: var(--error-color);
|
border: 1px solid rgba(220, 53, 69, 0.36);
|
||||||
background: rgba(220, 53, 69, 0.1);
|
border-left: 3px solid var(--error-color);
|
||||||
|
background: rgba(220, 53, 69, 0.085);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item.tool-detail-collapsible {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 参数块与卡片类型色弱对齐,扫读时一眼归到「调用」 */
|
/* 参数块与卡片类型色弱对齐,扫读时一眼归到「调用」 */
|
||||||
@@ -29722,6 +29827,173 @@ html[data-theme="dark"] #skill-view-body {
|
|||||||
border-color: #263244;
|
border-color: #263244;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-content .timeline-plain-text {
|
||||||
|
background: transparent !important;
|
||||||
|
border-color: transparent !important;
|
||||||
|
color: inherit;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-content.timeline-stream-plain {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .progress-container {
|
||||||
|
background: #0f172a;
|
||||||
|
border-color: #243044;
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .message.system .message-bubble.progress-container {
|
||||||
|
background: #0b1120 !important;
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-color: #203047 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .progress-container.completed {
|
||||||
|
background: #111827;
|
||||||
|
border-color: #263244;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .chat-scroll-to-bottom {
|
||||||
|
background: rgba(15, 23, 42, 0.94);
|
||||||
|
color: #dbeafe;
|
||||||
|
border-color: rgba(96, 165, 250, 0.36);
|
||||||
|
box-shadow:
|
||||||
|
0 10px 24px rgba(0, 0, 0, 0.34),
|
||||||
|
0 0 0 1px rgba(255, 255, 255, 0.03) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .chat-scroll-to-bottom:hover {
|
||||||
|
background: #172033;
|
||||||
|
color: #ffffff;
|
||||||
|
border-color: rgba(147, 197, 253, 0.62);
|
||||||
|
box-shadow:
|
||||||
|
0 12px 28px rgba(0, 0, 0, 0.42),
|
||||||
|
0 0 0 1px rgba(147, 197, 253, 0.08) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .chat-scroll-to-bottom:focus-visible {
|
||||||
|
outline-color: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .process-details-container {
|
||||||
|
border-top-color: #263244;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .progress-header,
|
||||||
|
html[data-theme="dark"] .progress-footer {
|
||||||
|
border-color: #243044;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .progress-toggle {
|
||||||
|
background: #111827;
|
||||||
|
border-color: #2b374b;
|
||||||
|
color: #a7b0c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .progress-toggle:hover {
|
||||||
|
background: #172033;
|
||||||
|
border-color: rgba(96, 165, 250, 0.38);
|
||||||
|
color: #dbeafe;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item {
|
||||||
|
background: #0f172a;
|
||||||
|
border-left-color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item:hover {
|
||||||
|
background: #132036;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-content {
|
||||||
|
border-top-color: rgba(148, 163, 184, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-iteration,
|
||||||
|
html[data-theme="dark"] .timeline-item.timeline-item-iteration.timeline-eino-scope-main,
|
||||||
|
html[data-theme="dark"] .timeline-item.timeline-item-iteration.timeline-eino-scope-sub {
|
||||||
|
background: #101a2b !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-thinking {
|
||||||
|
background: rgba(139, 92, 246, 0.09);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-thinking[data-response-stream-placeholder="1"] {
|
||||||
|
background: rgba(96, 165, 250, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-reasoning_chain {
|
||||||
|
background: rgba(124, 58, 237, 0.11);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-planning {
|
||||||
|
background: rgba(45, 212, 191, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-tool_call,
|
||||||
|
html[data-theme="dark"] .timeline-item-tool_calls_detected {
|
||||||
|
background: rgba(96, 165, 250, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-tool_result,
|
||||||
|
html[data-theme="dark"] .timeline-item-progress {
|
||||||
|
background: rgba(148, 163, 184, 0.075);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-eino_agent_reply {
|
||||||
|
background: rgba(168, 85, 247, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-tool_call:has(.tool-result-section.success),
|
||||||
|
html[data-theme="dark"] .timeline-item-tool_result:has(.tool-result-section.success) {
|
||||||
|
background: rgba(52, 211, 153, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-tool_call:has(.tool-result-section.error),
|
||||||
|
html[data-theme="dark"] .timeline-item-tool_result:has(.tool-result-section.error),
|
||||||
|
html[data-theme="dark"] .timeline-item-tool_result.error,
|
||||||
|
html[data-theme="dark"] .timeline-item-error {
|
||||||
|
background: rgba(248, 113, 113, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-tool_call.tool-call-running {
|
||||||
|
border: 1px solid rgba(96, 165, 250, 0.32);
|
||||||
|
border-left: 3px solid #60a5fa;
|
||||||
|
background: rgba(37, 99, 235, 0.105);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-tool_call.tool-call-completed {
|
||||||
|
border: 1px solid rgba(52, 211, 153, 0.34);
|
||||||
|
border-left: 3px solid #34d399;
|
||||||
|
background: rgba(16, 185, 129, 0.105);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-tool_call.tool-call-failed {
|
||||||
|
border: 1px solid rgba(248, 113, 113, 0.38);
|
||||||
|
border-left: 3px solid #f87171;
|
||||||
|
background: rgba(239, 68, 68, 0.115);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-tool_call.tool-call-running:hover {
|
||||||
|
background: rgba(37, 99, 235, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] .timeline-item-tool_call.tool-call-completed:hover {
|
||||||
|
background: rgba(16, 185, 129, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
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-warning,
|
||||||
|
html[data-theme="dark"] .timeline-item-user_interrupt_continue {
|
||||||
|
background: rgba(251, 191, 36, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
html[data-theme="dark"] .message.assistant .message-bubble {
|
html[data-theme="dark"] .message.assistant .message-bubble {
|
||||||
background: #111827;
|
background: #111827;
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"view": "View",
|
"view": "View",
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"loadFailed": "Load failed",
|
"loadFailed": "Load failed",
|
||||||
|
"loadMore": "Load more",
|
||||||
"untitled": "Untitled",
|
"untitled": "Untitled",
|
||||||
"justNow": "Just now",
|
"justNow": "Just now",
|
||||||
"minutesAgo": "{{n}} min ago",
|
"minutesAgo": "{{n}} min ago",
|
||||||
@@ -555,6 +556,10 @@
|
|||||||
"noMatchTools": "No matching tools",
|
"noMatchTools": "No matching tools",
|
||||||
"penetrationTestDetail": "Penetration test details",
|
"penetrationTestDetail": "Penetration test details",
|
||||||
"expandDetail": "Expand details",
|
"expandDetail": "Expand details",
|
||||||
|
"expandDetailLazyHint": "Expand details (loads iteration details on click)",
|
||||||
|
"viewToolDetail": "View details",
|
||||||
|
"collapseToolDetail": "Collapse",
|
||||||
|
"liveTimelinePruned": "Collapsed the first {{count}} live process details. View the full record page by page after the task completes.",
|
||||||
"toolExecutionsCount": "{{n}} tool runs",
|
"toolExecutionsCount": "{{n}} tool runs",
|
||||||
"collapseToolExecutions": "Collapse tool runs",
|
"collapseToolExecutions": "Collapse tool runs",
|
||||||
"noProcessDetail": "No process details (execution may be too fast or no detailed events)",
|
"noProcessDetail": "No process details (execution may be too fast or no detailed events)",
|
||||||
@@ -804,7 +809,9 @@
|
|||||||
"noResult": "No result",
|
"noResult": "No result",
|
||||||
"running": "Running...",
|
"running": "Running...",
|
||||||
"completed": "Completed",
|
"completed": "Completed",
|
||||||
"execFailed": "Execution failed"
|
"execFailed": "Execution failed",
|
||||||
|
"toolSucceeded": "Succeeded",
|
||||||
|
"toolFailed": "Failed"
|
||||||
},
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"title": "Task Management",
|
"title": "Task Management",
|
||||||
@@ -1813,6 +1820,7 @@
|
|||||||
"mcpMonitor": {
|
"mcpMonitor": {
|
||||||
"deselectAll": "Deselect all",
|
"deselectAll": "Deselect all",
|
||||||
"statusPending": "Pending",
|
"statusPending": "Pending",
|
||||||
|
"statusSuccess": "Succeeded",
|
||||||
"statusCompleted": "Completed",
|
"statusCompleted": "Completed",
|
||||||
"statusRunning": "Running",
|
"statusRunning": "Running",
|
||||||
"statusFailed": "Failed",
|
"statusFailed": "Failed",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"view": "查看",
|
"view": "查看",
|
||||||
"actions": "操作",
|
"actions": "操作",
|
||||||
"loadFailed": "加载失败",
|
"loadFailed": "加载失败",
|
||||||
|
"loadMore": "加载更多",
|
||||||
"untitled": "未命名",
|
"untitled": "未命名",
|
||||||
"justNow": "刚刚",
|
"justNow": "刚刚",
|
||||||
"minutesAgo": "{{n}} 分钟前",
|
"minutesAgo": "{{n}} 分钟前",
|
||||||
@@ -543,6 +544,10 @@
|
|||||||
"noMatchTools": "没有匹配的工具",
|
"noMatchTools": "没有匹配的工具",
|
||||||
"penetrationTestDetail": "渗透测试详情",
|
"penetrationTestDetail": "渗透测试详情",
|
||||||
"expandDetail": "展开详情",
|
"expandDetail": "展开详情",
|
||||||
|
"expandDetailLazyHint": "展开详情(点击后加载迭代详情)",
|
||||||
|
"viewToolDetail": "查看详情",
|
||||||
|
"collapseToolDetail": "收起",
|
||||||
|
"liveTimelinePruned": "已收起前 {{count}} 条实时过程详情,任务完成后可按页查看完整记录",
|
||||||
"toolExecutionsCount": "{{n}}次工具执行",
|
"toolExecutionsCount": "{{n}}次工具执行",
|
||||||
"collapseToolExecutions": "收起工具执行",
|
"collapseToolExecutions": "收起工具执行",
|
||||||
"noProcessDetail": "暂无过程详情(可能执行过快或未触发详细事件)",
|
"noProcessDetail": "暂无过程详情(可能执行过快或未触发详细事件)",
|
||||||
@@ -792,7 +797,9 @@
|
|||||||
"noResult": "无结果",
|
"noResult": "无结果",
|
||||||
"running": "执行中...",
|
"running": "执行中...",
|
||||||
"completed": "已完成",
|
"completed": "已完成",
|
||||||
"execFailed": "执行失败"
|
"execFailed": "执行失败",
|
||||||
|
"toolSucceeded": "成功",
|
||||||
|
"toolFailed": "失败"
|
||||||
},
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"title": "任务管理",
|
"title": "任务管理",
|
||||||
@@ -1801,6 +1808,7 @@
|
|||||||
"mcpMonitor": {
|
"mcpMonitor": {
|
||||||
"deselectAll": "取消全选",
|
"deselectAll": "取消全选",
|
||||||
"statusPending": "等待中",
|
"statusPending": "等待中",
|
||||||
|
"statusSuccess": "成功",
|
||||||
"statusCompleted": "已完成",
|
"statusCompleted": "已完成",
|
||||||
"statusRunning": "执行中",
|
"statusRunning": "执行中",
|
||||||
"statusFailed": "失败",
|
"statusFailed": "失败",
|
||||||
|
|||||||
+65
-6
@@ -2397,7 +2397,10 @@ function isEinoAgentHeartbeatProgress(detail) {
|
|||||||
|
|
||||||
function filterNoiseProcessDetails(details) {
|
function filterNoiseProcessDetails(details) {
|
||||||
if (!Array.isArray(details)) return details;
|
if (!Array.isArray(details)) return details;
|
||||||
return details.filter(function (d) { return !isEinoAgentHeartbeatProgress(d); });
|
return details.filter(function (d) {
|
||||||
|
if (isEinoAgentHeartbeatProgress(d)) return false;
|
||||||
|
return !(d && d.eventType === 'tool_calls_detected');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function dedupeConsecutiveProcessDetailRows(details) {
|
function dedupeConsecutiveProcessDetailRows(details) {
|
||||||
@@ -2704,6 +2707,7 @@ function renderProcessDetails(messageId, processDetails, options) {
|
|||||||
title: itemTitle,
|
title: itemTitle,
|
||||||
message: detail.message || '',
|
message: detail.message || '',
|
||||||
data: data,
|
data: data,
|
||||||
|
processDetailId: detail.id || '',
|
||||||
createdAt: detail.createdAt
|
createdAt: detail.createdAt
|
||||||
};
|
};
|
||||||
if (eventType === 'tool_call' && data._mergedResult) {
|
if (eventType === 'tool_call' && data._mergedResult) {
|
||||||
@@ -3052,7 +3056,62 @@ function ensureMcpCallButtons(messageElement) {
|
|||||||
window.ensureMcpCallButtons = ensureMcpCallButtons;
|
window.ensureMcpCallButtons = ensureMcpCallButtons;
|
||||||
window.appendMcpCallButtons = appendMcpCallButtons;
|
window.appendMcpCallButtons = appendMcpCallButtons;
|
||||||
|
|
||||||
// 批量获取工具名称并更新按钮(消除 N 次单独 API 请求,合并为 1 次)
|
function normalizeToolExecutionSummary(raw) {
|
||||||
|
if (typeof raw === 'string') {
|
||||||
|
return { toolName: raw, status: '' };
|
||||||
|
}
|
||||||
|
if (raw && typeof raw === 'object') {
|
||||||
|
return {
|
||||||
|
toolName: raw.toolName || raw.name || '',
|
||||||
|
status: raw.status || ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { toolName: '', status: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getToolExecutionStatusLabel(status) {
|
||||||
|
const normalized = String(status || '').toLowerCase();
|
||||||
|
if (typeof window.t === 'function') {
|
||||||
|
const keyMap = {
|
||||||
|
completed: 'mcpMonitor.statusSuccess',
|
||||||
|
failed: 'mcpMonitor.statusFailed',
|
||||||
|
running: 'mcpMonitor.statusRunning',
|
||||||
|
cancelled: 'mcpMonitor.statusCancelled',
|
||||||
|
pending: 'mcpMonitor.statusPending'
|
||||||
|
};
|
||||||
|
const key = keyMap[normalized];
|
||||||
|
if (key) {
|
||||||
|
const translated = window.t(key);
|
||||||
|
if (translated && translated !== key) return translated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const fallback = {
|
||||||
|
completed: '成功',
|
||||||
|
failed: '失败',
|
||||||
|
running: '运行中',
|
||||||
|
cancelled: '已取消',
|
||||||
|
pending: '等待中'
|
||||||
|
};
|
||||||
|
return fallback[normalized] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderToolExecutionButtonContent(btn, displayToolName, index, status) {
|
||||||
|
const safeToolName = escapeHtml(displayToolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具'));
|
||||||
|
const safeIndex = escapeHtml(index || '');
|
||||||
|
const statusText = getToolExecutionStatusLabel(status);
|
||||||
|
const normalizedStatus = String(status || '').toLowerCase();
|
||||||
|
const label = safeIndex ? `${safeToolName} #${safeIndex}` : safeToolName;
|
||||||
|
btn.innerHTML = '<span class="mcp-tool-name">' + label + '</span>';
|
||||||
|
if (!statusText) {
|
||||||
|
btn.removeAttribute('data-status');
|
||||||
|
btn.removeAttribute('title');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
btn.dataset.status = normalizedStatus;
|
||||||
|
btn.title = statusText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量获取工具摘要并更新按钮(消除 N 次单独 API 请求,合并为 1 次)
|
||||||
async function batchUpdateButtonToolNames(buttonsContainer, executionIds) {
|
async function batchUpdateButtonToolNames(buttonsContainer, executionIds) {
|
||||||
if (!executionIds || executionIds.length === 0) return;
|
if (!executionIds || executionIds.length === 0) return;
|
||||||
try {
|
try {
|
||||||
@@ -3062,17 +3121,17 @@ async function batchUpdateButtonToolNames(buttonsContainer, executionIds) {
|
|||||||
body: JSON.stringify({ ids: executionIds }),
|
body: JSON.stringify({ ids: executionIds }),
|
||||||
});
|
});
|
||||||
if (!response.ok) return;
|
if (!response.ok) return;
|
||||||
const nameMap = await response.json(); // { execId: toolName }
|
const nameMap = await response.json(); // { execId: toolName } 或 { execId: { toolName, status } }
|
||||||
// 更新对应按钮的文本
|
// 更新对应按钮的文本
|
||||||
const buttons = buttonsContainer.querySelectorAll('.mcp-detail-btn[data-exec-id]');
|
const buttons = buttonsContainer.querySelectorAll('.mcp-detail-btn[data-exec-id]');
|
||||||
buttons.forEach(btn => {
|
buttons.forEach(btn => {
|
||||||
const execId = btn.dataset.execId;
|
const execId = btn.dataset.execId;
|
||||||
const index = btn.dataset.execIndex;
|
const index = btn.dataset.execIndex;
|
||||||
const toolName = nameMap[execId];
|
const summary = normalizeToolExecutionSummary(nameMap[execId]);
|
||||||
|
const toolName = summary.toolName;
|
||||||
if (toolName) {
|
if (toolName) {
|
||||||
const displayToolName = toolName.includes('::') ? toolName.split('::')[1] : toolName;
|
const displayToolName = toolName.includes('::') ? toolName.split('::')[1] : toolName;
|
||||||
const span = btn.querySelector('span');
|
renderToolExecutionButtonContent(btn, displayToolName, index, summary.status);
|
||||||
if (span) span.textContent = `${displayToolName} #${index}`;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
+355
-171
@@ -236,11 +236,7 @@ function finalizeMainResponseStreamItem(streamState, finalMessage, responseData)
|
|||||||
const body = typeof formatTimelineStreamBody === 'function'
|
const body = typeof formatTimelineStreamBody === 'function'
|
||||||
? formatTimelineStreamBody(fullText, meta)
|
? formatTimelineStreamBody(fullText, meta)
|
||||||
: fullText;
|
: fullText;
|
||||||
if (typeof formatMarkdown === 'function') {
|
setTimelineItemContentStreamPlain(contentEl, body);
|
||||||
setTimelineItemContentStreamRich(contentEl, formatMarkdown(body, timelineMarkdownOpts));
|
|
||||||
} else {
|
|
||||||
setTimelineItemContentStreamPlain(contentEl, body);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,6 +435,9 @@ function toolResultStreamKey(progressId, toolCallId) {
|
|||||||
return String(progressId) + '::' + String(toolCallId);
|
return String(progressId) + '::' + String(toolCallId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LIVE_TIMELINE_MAX_ITEMS = 150;
|
||||||
|
const LIVE_TIMELINE_PRUNE_CHUNK = 50;
|
||||||
|
|
||||||
/** Eino 多代理:时间线标题前加 [agentId],标明哪一代理产生该工具调用/结果/回复 */
|
/** Eino 多代理:时间线标题前加 [agentId],标明哪一代理产生该工具调用/结果/回复 */
|
||||||
function timelineAgentBracketPrefix(data) {
|
function timelineAgentBracketPrefix(data) {
|
||||||
if (!data || data.einoAgent == null) return '';
|
if (!data || data.einoAgent == null) return '';
|
||||||
@@ -461,9 +460,6 @@ function applyEinoTimelineRole(item, data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 过程详情时间线:更严消毒(无 img;整页 HTML 见 sanitize-markdown.js) */
|
|
||||||
const timelineMarkdownOpts = { profile: 'timeline' };
|
|
||||||
|
|
||||||
function escapeHtmlLocal(text) {
|
function escapeHtmlLocal(text) {
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
@@ -471,6 +467,10 @@ function escapeHtmlLocal(text) {
|
|||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatTimelinePlainTextHtml(text) {
|
||||||
|
return '<pre class="timeline-plain-text">' + escapeHtml(text == null ? '' : String(text)) + '</pre>';
|
||||||
|
}
|
||||||
|
|
||||||
/** fenced 块占位(BMP 私用区,正文几乎不会出现) */
|
/** fenced 块占位(BMP 私用区,正文几乎不会出现) */
|
||||||
const _MD_FENCE_PRE = '\n\uE000CSAI_FENCE_';
|
const _MD_FENCE_PRE = '\n\uE000CSAI_FENCE_';
|
||||||
const _MD_FENCE_SUF = '_\uE000\n';
|
const _MD_FENCE_SUF = '_\uE000\n';
|
||||||
@@ -1280,23 +1280,14 @@ function integrateProgressToMCPSection(progressId, assistantMessageId, mcpExecut
|
|||||||
const progressElement = document.getElementById(progressId);
|
const progressElement = document.getElementById(progressId);
|
||||||
if (!progressElement) return;
|
if (!progressElement) return;
|
||||||
|
|
||||||
// 快照 innerHTML 前刷掉尚未执行的 rAF 流式更新,避免过程详情少最后几帧
|
// 只 flush 终态标题/正文;完成后的详情回看统一走后端分页,不再复制实时 DOM 快照。
|
||||||
flushAllPendingStreamPlainUpdates();
|
flushAllPendingStreamPlainUpdates();
|
||||||
|
|
||||||
// Ensure any "running" tool_call badges are closed before we snapshot timeline HTML.
|
// 进度 DOM 即将移除;先关闭仍处于 running 的工具摘要状态。
|
||||||
// Otherwise, once the progress element is removed, later 'done' events may not be able
|
|
||||||
// to update the original timeline DOM and the copied HTML would stay "执行中".
|
|
||||||
finalizeOutstandingToolCallsForProgress(progressId, 'failed');
|
finalizeOutstandingToolCallsForProgress(progressId, 'failed');
|
||||||
|
|
||||||
const mcpIds = Array.isArray(mcpExecutionIds) ? mcpExecutionIds : [];
|
const mcpIds = Array.isArray(mcpExecutionIds) ? mcpExecutionIds : [];
|
||||||
|
|
||||||
// 获取时间线内容
|
|
||||||
const timeline = document.getElementById(progressId + '-timeline');
|
|
||||||
let timelineHTML = '';
|
|
||||||
if (timeline) {
|
|
||||||
timelineHTML = timeline.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取助手消息元素
|
// 获取助手消息元素
|
||||||
const assistantElement = document.getElementById(assistantMessageId);
|
const assistantElement = document.getElementById(assistantMessageId);
|
||||||
if (!assistantElement) {
|
if (!assistantElement) {
|
||||||
@@ -1320,8 +1311,6 @@ function integrateProgressToMCPSection(progressId, assistantMessageId, mcpExecut
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasContent = timelineHTML.trim().length > 0;
|
|
||||||
|
|
||||||
if (mcpIds.length > 0 && typeof window.appendMcpCallButtons === 'function') {
|
if (mcpIds.length > 0 && typeof window.appendMcpCallButtons === 'function') {
|
||||||
window.appendMcpCallButtons(assistantElement, mcpIds);
|
window.appendMcpCallButtons(assistantElement, mcpIds);
|
||||||
const toolList = mcpSection.querySelector('.mcp-tool-list');
|
const toolList = mcpSection.querySelector('.mcp-tool-list');
|
||||||
@@ -1354,40 +1343,87 @@ function integrateProgressToMCPSection(progressId, assistantMessageId, mcpExecut
|
|||||||
mcpSection.appendChild(detailsContainer);
|
mcpSection.appendChild(detailsContainer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
detailsContainer.innerHTML = `
|
if (typeof renderProcessDetails === 'function') {
|
||||||
<div class="process-details-content">
|
renderProcessDetails(assistantMessageId, null);
|
||||||
${hasContent ? `<div class="progress-timeline" id="${detailsId}-timeline">${timelineHTML}</div>` : '<div class="progress-timeline-empty">' + (typeof window.t === 'function' ? window.t('chat.noProcessDetail') : '暂无过程详情(可能执行过快或未触发详细事件)') + '</div>'}
|
} else {
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (hasContent) {
|
|
||||||
const timeline = document.getElementById(detailsId + '-timeline');
|
|
||||||
if (timeline) {
|
|
||||||
timeline.classList.remove('expanded');
|
|
||||||
}
|
|
||||||
|
|
||||||
const expandLabel = typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情';
|
const expandLabel = typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情';
|
||||||
document.querySelectorAll(`#${assistantMessageId} .process-detail-btn`).forEach((btn) => {
|
detailsContainer.dataset.lazyNotLoaded = '1';
|
||||||
btn.innerHTML = '<span>' + expandLabel + '</span>';
|
detailsContainer.dataset.loaded = '0';
|
||||||
});
|
detailsContainer.innerHTML = `
|
||||||
|
<div class="process-details-content">
|
||||||
|
<div class="progress-timeline" id="${detailsId}-timeline">
|
||||||
|
<div class="progress-timeline-empty">${typeof window.t === 'function' ? window.t('chat.expandDetailLazyHint') : (expandLabel + '(点击后加载迭代详情)')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const expandLabel = typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情';
|
||||||
|
document.querySelectorAll(`#${assistantMessageId} .process-detail-btn`).forEach((btn) => {
|
||||||
|
btn.innerHTML = '<span>' + expandLabel + '</span>';
|
||||||
|
});
|
||||||
|
|
||||||
removeMessage(progressId);
|
removeMessage(progressId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PROCESS_DETAILS_PAGE_SIZE = 100;
|
const PROCESS_DETAILS_PAGE_SIZE = 50;
|
||||||
|
|
||||||
|
function getProcessDetailsLoadMoreLabel(hasMore) {
|
||||||
|
if (!hasMore) return '';
|
||||||
|
return (typeof window.t === 'function' ? window.t('common.loadMore') : '加载更多') + ' · ' +
|
||||||
|
(typeof window.t === 'function' ? window.t('chat.penetrationTestDetail') : '过程详情');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProcessDetailsLoadMoreButton(assistantMessageId, backendMessageId, hasMore) {
|
||||||
|
const detailsContainer = document.getElementById('process-details-' + assistantMessageId);
|
||||||
|
if (!detailsContainer) return;
|
||||||
|
|
||||||
|
let btn = detailsContainer.querySelector('.process-details-load-more-btn');
|
||||||
|
if (!hasMore) {
|
||||||
|
if (btn) btn.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!btn) {
|
||||||
|
btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 'mcp-detail-btn process-details-load-more-btn';
|
||||||
|
detailsContainer.appendChild(btn);
|
||||||
|
}
|
||||||
|
btn.textContent = getProcessDetailsLoadMoreLabel(true);
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.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') : '加载中…';
|
||||||
|
try {
|
||||||
|
await loadProcessDetailsPaginated(assistantMessageId, backendMessageId, {
|
||||||
|
append: true,
|
||||||
|
autoLoadAll: false
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
detailsContainer.dataset.loadingMore = '0';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页加载过程详情并增量渲染,避免数百轮迭代一次性阻塞主线程。
|
* 分页加载过程详情并增量渲染。默认全量加载供恢复流程使用;
|
||||||
|
* 用户手动展开时传 autoLoadAll=false,只加载一页并展示“加载更多”。
|
||||||
*/
|
*/
|
||||||
async function loadProcessDetailsPaginated(assistantMessageId, backendMessageId) {
|
async function loadProcessDetailsPaginated(assistantMessageId, backendMessageId, options) {
|
||||||
if (!assistantMessageId || !backendMessageId || typeof apiFetch !== 'function' || typeof renderProcessDetails !== 'function') {
|
if (!assistantMessageId || !backendMessageId || typeof apiFetch !== 'function' || typeof renderProcessDetails !== 'function') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const opts = options || {};
|
||||||
|
const autoLoadAll = opts.autoLoadAll !== false;
|
||||||
|
const detailsContainer = document.getElementById('process-details-' + assistantMessageId);
|
||||||
const PAGE = PROCESS_DETAILS_PAGE_SIZE;
|
const PAGE = PROCESS_DETAILS_PAGE_SIZE;
|
||||||
let offset = 0;
|
let offset = opts.append && detailsContainer && detailsContainer.dataset.nextOffset
|
||||||
let isFirst = true;
|
? parseInt(detailsContainer.dataset.nextOffset, 10) || 0
|
||||||
|
: 0;
|
||||||
|
let isFirst = !opts.append;
|
||||||
while (true) {
|
while (true) {
|
||||||
const res = await apiFetch(
|
const res = await apiFetch(
|
||||||
'/api/messages/' + encodeURIComponent(String(backendMessageId)) +
|
'/api/messages/' + encodeURIComponent(String(backendMessageId)) +
|
||||||
@@ -1401,12 +1437,18 @@ async function loadProcessDetailsPaginated(assistantMessageId, backendMessageId)
|
|||||||
const hasMore = !!(j && j.hasMore);
|
const hasMore = !!(j && j.hasMore);
|
||||||
renderProcessDetails(assistantMessageId, details, {
|
renderProcessDetails(assistantMessageId, details, {
|
||||||
append: !isFirst,
|
append: !isFirst,
|
||||||
markLoaded: !hasMore
|
markLoaded: autoLoadAll ? !hasMore : true
|
||||||
});
|
});
|
||||||
if (!hasMore || details.length === 0) {
|
offset += details.length;
|
||||||
|
if (detailsContainer) {
|
||||||
|
detailsContainer.dataset.lazyNotLoaded = '0';
|
||||||
|
detailsContainer.dataset.loaded = hasMore ? 'partial' : '1';
|
||||||
|
detailsContainer.dataset.nextOffset = String(offset);
|
||||||
|
}
|
||||||
|
updateProcessDetailsLoadMoreButton(assistantMessageId, backendMessageId, hasMore && !autoLoadAll);
|
||||||
|
if (!hasMore || details.length === 0 || !autoLoadAll) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
offset += details.length;
|
|
||||||
isFirst = false;
|
isFirst = false;
|
||||||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||||
}
|
}
|
||||||
@@ -1437,7 +1479,7 @@ function toggleProcessDetails(progressId, assistantMessageId) {
|
|||||||
if (timeline) {
|
if (timeline) {
|
||||||
timeline.innerHTML = '<div class="progress-timeline-empty">' + ((typeof window.t === 'function') ? window.t('common.loading') : '加载中…') + '</div>';
|
timeline.innerHTML = '<div class="progress-timeline-empty">' + ((typeof window.t === 'function') ? window.t('common.loading') : '加载中…') + '</div>';
|
||||||
}
|
}
|
||||||
loadProcessDetailsPaginated(assistantMessageId, backendMessageId)
|
loadProcessDetailsPaginated(assistantMessageId, backendMessageId, { autoLoadAll: false })
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error('加载过程详情失败:', e);
|
console.error('加载过程详情失败:', e);
|
||||||
const tl = detailsContainer.querySelector('.progress-timeline');
|
const tl = detailsContainer.querySelector('.progress-timeline');
|
||||||
@@ -1527,82 +1569,6 @@ async function cancelProgressTask(progressId) {
|
|||||||
openUserInterruptModal(progressId, state.conversationId);
|
openUserInterruptModal(progressId, state.conversationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将进度消息转换为可折叠的详情组件
|
|
||||||
function convertProgressToDetails(progressId, assistantMessageId) {
|
|
||||||
const progressElement = document.getElementById(progressId);
|
|
||||||
if (!progressElement) return;
|
|
||||||
|
|
||||||
// 获取时间线内容
|
|
||||||
const timeline = document.getElementById(progressId + '-timeline');
|
|
||||||
// 即使时间线不存在,也创建详情组件(显示空状态)
|
|
||||||
let timelineHTML = '';
|
|
||||||
if (timeline) {
|
|
||||||
timelineHTML = timeline.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取助手消息元素
|
|
||||||
const assistantElement = document.getElementById(assistantMessageId);
|
|
||||||
if (!assistantElement) {
|
|
||||||
removeMessage(progressId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建详情组件
|
|
||||||
const detailsId = 'details-' + Date.now() + '-' + messageCounter++;
|
|
||||||
const detailsDiv = document.createElement('div');
|
|
||||||
detailsDiv.id = detailsId;
|
|
||||||
detailsDiv.className = 'message system progress-details';
|
|
||||||
|
|
||||||
const contentWrapper = document.createElement('div');
|
|
||||||
contentWrapper.className = 'message-content';
|
|
||||||
|
|
||||||
const bubble = document.createElement('div');
|
|
||||||
bubble.className = 'message-bubble progress-container completed';
|
|
||||||
|
|
||||||
// 获取时间线HTML内容
|
|
||||||
const hasContent = timelineHTML.trim().length > 0;
|
|
||||||
|
|
||||||
// 检查时间线中是否有错误项
|
|
||||||
const hasError = timeline && timeline.querySelector('.timeline-item-error');
|
|
||||||
|
|
||||||
// 如果有错误,默认折叠;否则默认展开
|
|
||||||
const shouldExpand = !hasError;
|
|
||||||
const expandedClass = shouldExpand ? 'expanded' : '';
|
|
||||||
const collapseDetailText = typeof window.t === 'function' ? window.t('tasks.collapseDetail') : '收起详情';
|
|
||||||
const expandDetailText = typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情';
|
|
||||||
const toggleText = shouldExpand ? collapseDetailText : expandDetailText;
|
|
||||||
const penetrationDetailText = typeof window.t === 'function' ? window.t('chat.penetrationTestDetail') : '渗透测试详情';
|
|
||||||
const noProcessDetailText = typeof window.t === 'function' ? window.t('chat.noProcessDetail') : '暂无过程详情(可能执行过快或未触发详细事件)';
|
|
||||||
bubble.innerHTML = `
|
|
||||||
<div class="progress-header">
|
|
||||||
<span class="progress-title">📋 ${penetrationDetailText}</span>
|
|
||||||
${hasContent ? `<button class="progress-toggle" onclick="toggleProgressDetails('${detailsId}')">${toggleText}</button>` : ''}
|
|
||||||
</div>
|
|
||||||
${hasContent ? `<div class="progress-timeline ${expandedClass}" id="${detailsId}-timeline">${timelineHTML}</div><div class="progress-footer"><button type="button" class="progress-toggle progress-toggle-bottom" onclick="toggleProgressDetails('${detailsId}')">${toggleText}</button></div>` : '<div class="progress-timeline-empty">' + noProcessDetailText + '</div>'}
|
|
||||||
`;
|
|
||||||
|
|
||||||
contentWrapper.appendChild(bubble);
|
|
||||||
detailsDiv.appendChild(contentWrapper);
|
|
||||||
|
|
||||||
// 将详情组件插入到助手消息之后
|
|
||||||
const messagesDiv = document.getElementById('chat-messages');
|
|
||||||
const insertWasPinned = typeof window.captureScrollPinState === 'function'
|
|
||||||
? window.captureScrollPinState()
|
|
||||||
: (typeof window.isChatMessagesPinnedToBottom === 'function' ? window.isChatMessagesPinnedToBottom() : true);
|
|
||||||
// assistantElement 是消息div,需要插入到它的下一个兄弟节点之前
|
|
||||||
if (assistantElement.nextSibling) {
|
|
||||||
messagesDiv.insertBefore(detailsDiv, assistantElement.nextSibling);
|
|
||||||
} else {
|
|
||||||
// 如果没有下一个兄弟节点,直接追加
|
|
||||||
messagesDiv.appendChild(detailsDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除原来的进度消息
|
|
||||||
removeMessage(progressId);
|
|
||||||
|
|
||||||
scrollChatMessagesToBottomIfPinned(insertWasPinned);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 将后端消息 UUID 绑定到助手气泡,供删除本轮 / 过程详情懒加载(domId 为前端 msg-*) */
|
/** 将后端消息 UUID 绑定到助手气泡,供删除本轮 / 过程详情懒加载(domId 为前端 msg-*) */
|
||||||
function applyBackendMessageIdToAssistantDom(domAssistantId, backendMessageId) {
|
function applyBackendMessageIdToAssistantDom(domAssistantId, backendMessageId) {
|
||||||
if (!domAssistantId || !backendMessageId) return;
|
if (!domAssistantId || !backendMessageId) return;
|
||||||
@@ -2065,11 +2031,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
const contentEl = item.querySelector('.timeline-item-content');
|
const contentEl = item.querySelector('.timeline-item-content');
|
||||||
if (contentEl) {
|
if (contentEl) {
|
||||||
flushStreamPlainTextUpdate(contentEl);
|
flushStreamPlainTextUpdate(contentEl);
|
||||||
if (typeof formatMarkdown === 'function') {
|
setTimelineItemContentStreamPlain(contentEl, s.buffer);
|
||||||
setTimelineItemContentStreamRich(contentEl, formatMarkdown(s.buffer, timelineMarkdownOpts));
|
|
||||||
} else {
|
|
||||||
setTimelineItemContentStreamPlain(contentEl, s.buffer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -2091,11 +2053,6 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
case 'tool_calls_detected':
|
case 'tool_calls_detected':
|
||||||
// 助手正文段结束、进入工具调用:下一段 response_start 应新建时间线条目
|
// 助手正文段结束、进入工具调用:下一段 response_start 应新建时间线条目
|
||||||
responseStreamStateByProgressId.delete(progressId);
|
responseStreamStateByProgressId.delete(progressId);
|
||||||
addTimelineItem(timeline, 'tool_calls_detected', {
|
|
||||||
title: timelineAgentBracketPrefix(event.data) + '🔧 ' + (typeof window.t === 'function' ? window.t('chat.toolCallsDetected', { count: event.data?.count || 0 }) : '检测到 ' + (event.data?.count || 0) + ' 个工具调用'),
|
|
||||||
message: event.message,
|
|
||||||
data: event.data
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'warning':
|
case 'warning':
|
||||||
@@ -2240,6 +2197,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
title: timelineAgentBracketPrefix(toolInfo) + '🔧 ' + toolCallTitle,
|
title: timelineAgentBracketPrefix(toolInfo) + '🔧 ' + toolCallTitle,
|
||||||
message: event.message,
|
message: event.message,
|
||||||
data: toolInfo,
|
data: toolInfo,
|
||||||
|
processDetailId: toolInfo.processDetailId || '',
|
||||||
expanded: false
|
expanded: false
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2304,6 +2262,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
title: timelineAgentBracketPrefix(resultInfo) + statusIcon + ' ' + resultExecText,
|
title: timelineAgentBracketPrefix(resultInfo) + statusIcon + ' ' + resultExecText,
|
||||||
message: event.message,
|
message: event.message,
|
||||||
data: resultInfo,
|
data: resultInfo,
|
||||||
|
processDetailId: resultInfo.processDetailId || '',
|
||||||
expanded: false
|
expanded: false
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -2391,11 +2350,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
item.appendChild(contentEl);
|
item.appendChild(contentEl);
|
||||||
}
|
}
|
||||||
flushStreamPlainTextUpdate(contentEl);
|
flushStreamPlainTextUpdate(contentEl);
|
||||||
if (typeof formatMarkdown === 'function') {
|
setTimelineItemContentStreamPlain(contentEl, full);
|
||||||
setTimelineItemContentStreamRich(contentEl, formatMarkdown(full, timelineMarkdownOpts));
|
|
||||||
} else {
|
|
||||||
setTimelineItemContentStreamPlain(contentEl, full);
|
|
||||||
}
|
|
||||||
if (d.einoAgent != null && String(d.einoAgent).trim() !== '') {
|
if (d.einoAgent != null && String(d.einoAgent).trim() !== '') {
|
||||||
item.dataset.einoAgent = String(d.einoAgent).trim();
|
item.dataset.einoAgent = String(d.einoAgent).trim();
|
||||||
}
|
}
|
||||||
@@ -2640,17 +2595,20 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
// so the copied timeline HTML reflects the final status.
|
// so the copied timeline HTML reflects the final status.
|
||||||
finalizeOutstandingToolCallsForProgress(progressId, 'failed');
|
finalizeOutstandingToolCallsForProgress(progressId, 'failed');
|
||||||
|
|
||||||
|
const respMid = responseData.messageId;
|
||||||
|
if (respMid) {
|
||||||
|
applyBackendMessageIdToAssistantDom(assistantIdFinal, respMid);
|
||||||
|
}
|
||||||
|
|
||||||
const replayCtx = window.csTaskReplay;
|
const replayCtx = window.csTaskReplay;
|
||||||
const directReplay = replayCtx && replayCtx.progressId === progressId;
|
const directReplay = replayCtx && replayCtx.progressId === progressId;
|
||||||
if (!directReplay) {
|
if (!directReplay) {
|
||||||
// 将进度详情集成到工具调用区域(放在最终 response 之后,保证时间线已完整)
|
// 将详情入口集成到工具调用区域;完整过程从后端分页加载,不依赖实时 DOM 快照。
|
||||||
integrateProgressToMCPSection(progressId, assistantIdFinal, mcpIds);
|
integrateProgressToMCPSection(progressId, assistantIdFinal, mcpIds);
|
||||||
}
|
}
|
||||||
responseStreamStateByProgressId.delete(progressId);
|
responseStreamStateByProgressId.delete(progressId);
|
||||||
|
|
||||||
const respMid = responseData.messageId;
|
|
||||||
if (respMid) {
|
if (respMid) {
|
||||||
applyBackendMessageIdToAssistantDom(assistantIdFinal, respMid);
|
|
||||||
if (typeof window.syncAssistantReasoningContentFromServer === 'function') {
|
if (typeof window.syncAssistantReasoningContentFromServer === 'function') {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
window.syncAssistantReasoningContentFromServer(respMid, assistantIdFinal);
|
window.syncAssistantReasoningContentFromServer(respMid, assistantIdFinal);
|
||||||
@@ -3466,8 +3424,154 @@ function buildToolResultSectionHtml(data, opts) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toolCallDetailStateByItemId = new Map();
|
||||||
|
|
||||||
|
function setToolCallDetailState(item, state) {
|
||||||
|
if (!item || !item.id) return;
|
||||||
|
toolCallDetailStateByItemId.set(item.id, Object.assign({}, state || {}));
|
||||||
|
item.classList.add('tool-detail-collapsible');
|
||||||
|
item.classList.add('tool-call-collapsible');
|
||||||
|
updateToolDetailToggleLabel(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toolDetailToggleText(expanded) {
|
||||||
|
if (typeof window.t === 'function') {
|
||||||
|
return expanded
|
||||||
|
? window.t('chat.collapseToolDetail')
|
||||||
|
: window.t('chat.viewToolDetail');
|
||||||
|
}
|
||||||
|
return expanded ? '收起' : '查看详情';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateToolDetailToggleLabel(item) {
|
||||||
|
if (!item) return;
|
||||||
|
const header = item.querySelector('.timeline-item-header');
|
||||||
|
if (!header) return;
|
||||||
|
const expanded = item.classList.contains('tool-call-detail-expanded');
|
||||||
|
header.setAttribute('data-tool-detail-label', toolDetailToggleText(expanded) + (expanded ? ' ▴' : ' ▾'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchFullProcessDetailData(detailId) {
|
||||||
|
const id = detailId != null ? String(detailId).trim() : '';
|
||||||
|
if (!id || typeof apiFetch !== 'function') return null;
|
||||||
|
const res = await apiFetch('/api/process-details/' + encodeURIComponent(id));
|
||||||
|
const j = await res.json().catch(() => ({}));
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error((j && j.error) ? j.error : String(res.status));
|
||||||
|
}
|
||||||
|
const detail = j && j.processDetail ? j.processDetail : null;
|
||||||
|
return detail && detail.data ? detail.data : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function collapseOtherToolCallDetails(item) {
|
||||||
|
const timeline = item && item.closest ? item.closest('.progress-timeline') : null;
|
||||||
|
if (!timeline) return;
|
||||||
|
timeline.querySelectorAll('.timeline-item.tool-detail-collapsible.tool-call-detail-expanded').forEach(function (other) {
|
||||||
|
if (other === item) return;
|
||||||
|
other.classList.remove('tool-call-detail-expanded');
|
||||||
|
const content = other.querySelector('.timeline-item-content.tool-call-detail-content');
|
||||||
|
if (content) content.remove();
|
||||||
|
updateToolDetailToggleLabel(other);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderToolCallDetailContent(item) {
|
||||||
|
if (!item || !item.id) return;
|
||||||
|
const state = toolCallDetailStateByItemId.get(item.id) || {};
|
||||||
|
collapseOtherToolCallDetails(item);
|
||||||
|
|
||||||
|
let content = item.querySelector('.timeline-item-content.tool-call-detail-content');
|
||||||
|
if (content) {
|
||||||
|
content.remove();
|
||||||
|
item.classList.remove('tool-call-detail-expanded');
|
||||||
|
updateToolDetailToggleLabel(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
content = document.createElement('div');
|
||||||
|
content.className = 'timeline-item-content tool-call-detail-content';
|
||||||
|
if (state.payloadDeferred && !state.payloadLoaded && (state.processDetailId || state.resultDetailId)) {
|
||||||
|
content.innerHTML = '<div class="progress-timeline-empty">' +
|
||||||
|
escapeHtml(typeof window.t === 'function' ? window.t('common.loading') : '加载中…') +
|
||||||
|
'</div>';
|
||||||
|
item.appendChild(content);
|
||||||
|
item.classList.add('tool-call-detail-expanded');
|
||||||
|
updateToolDetailToggleLabel(item);
|
||||||
|
try {
|
||||||
|
if (state.processDetailId && !state.hideArgs) {
|
||||||
|
const fullCall = await fetchFullProcessDetailData(state.processDetailId);
|
||||||
|
if (fullCall) {
|
||||||
|
state.args = parseToolCallArgsFromData(fullCall);
|
||||||
|
state.payloadDeferred = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.resultDetailId) {
|
||||||
|
const fullResult = await fetchFullProcessDetailData(state.resultDetailId);
|
||||||
|
if (fullResult) {
|
||||||
|
state.resultData = fullResult;
|
||||||
|
const noResultText = typeof window.t === 'function' ? window.t('timeline.noResult') : '无结果';
|
||||||
|
const result = fullResult.result != null ? fullResult.result : (fullResult.error != null ? fullResult.error : noResultText);
|
||||||
|
state.rawText = typeof result === 'string' ? result : JSON.stringify(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.payloadLoaded = true;
|
||||||
|
setToolCallDetailState(item, state);
|
||||||
|
content.remove();
|
||||||
|
item.classList.remove('tool-call-detail-expanded');
|
||||||
|
renderToolCallDetailContent(item);
|
||||||
|
} catch (e) {
|
||||||
|
content.innerHTML = '<div class="progress-timeline-empty">' + escapeHtml(e && e.message ? e.message : '加载失败') + '</div>';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = state.args != null ? state.args : {};
|
||||||
|
let resultBlock = '';
|
||||||
|
if (state.resultData) {
|
||||||
|
resultBlock = '<div class="tool-details tool-result-slot">' +
|
||||||
|
buildToolResultSectionHtml(state.resultData, { rawText: state.rawText }) +
|
||||||
|
'</div>';
|
||||||
|
} else if (state.pending !== false) {
|
||||||
|
resultBlock = '<div class="tool-details tool-result-slot">' +
|
||||||
|
buildToolResultSectionHtml({}, { pending: true }) +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramsLabel = typeof window.t === 'function' ? window.t('timeline.params') : '参数:';
|
||||||
|
const argsBlock = state.hideArgs ? '' :
|
||||||
|
'<div class="tool-arg-section">' +
|
||||||
|
'<strong data-i18n="timeline.params">' + escapeHtml(paramsLabel) + '</strong>' +
|
||||||
|
'<pre class="tool-args">' + escapeHtml(JSON.stringify(args, null, 2)) + '</pre>' +
|
||||||
|
'</div>';
|
||||||
|
content.innerHTML = '<div class="tool-details">' + argsBlock + resultBlock + '</div>';
|
||||||
|
item.appendChild(content);
|
||||||
|
item.classList.add('tool-call-detail-expanded');
|
||||||
|
updateToolDetailToggleLabel(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof document !== 'undefined' && !document.__cyberStrikeToolCallDetailToggleBound) {
|
||||||
|
document.__cyberStrikeToolCallDetailToggleBound = true;
|
||||||
|
document.addEventListener('click', function (event) {
|
||||||
|
const target = event.target;
|
||||||
|
if (target && target.closest && target.closest('button, a, input, textarea, select, pre, code, .timeline-item-content')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const item = target && target.closest
|
||||||
|
? target.closest('.timeline-item.tool-detail-collapsible')
|
||||||
|
: null;
|
||||||
|
if (!item) return;
|
||||||
|
renderToolCallDetailContent(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function ensureToolCallResultSlot(item) {
|
function ensureToolCallResultSlot(item) {
|
||||||
if (!item) return null;
|
if (!item) return null;
|
||||||
|
if (item.classList.contains('tool-call-collapsible')) {
|
||||||
|
const state = toolCallDetailStateByItemId.get(item.id) || {};
|
||||||
|
state.pending = true;
|
||||||
|
setToolCallDetailState(item, state);
|
||||||
|
if (!item.classList.contains('tool-call-detail-expanded')) return null;
|
||||||
|
}
|
||||||
let section = item.querySelector('.tool-result-section');
|
let section = item.querySelector('.tool-result-section');
|
||||||
if (section) return section;
|
if (section) return section;
|
||||||
const content = item.querySelector('.timeline-item-content');
|
const content = item.querySelector('.timeline-item-content');
|
||||||
@@ -3488,6 +3592,27 @@ function mergeToolResultIntoCallItem(item, data, options) {
|
|||||||
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
||||||
const text = options.rawText != null ? String(options.rawText) : resultStr;
|
const text = options.rawText != null ? String(options.rawText) : resultStr;
|
||||||
|
|
||||||
|
if (item.classList.contains('tool-call-collapsible')) {
|
||||||
|
const state = toolCallDetailStateByItemId.get(item.id) || {};
|
||||||
|
state.resultData = data;
|
||||||
|
state.rawText = text;
|
||||||
|
state.resultDetailId = data.processDetailId || state.resultDetailId || '';
|
||||||
|
state.pending = false;
|
||||||
|
setToolCallDetailState(item, state);
|
||||||
|
const expanded = item.classList.contains('tool-call-detail-expanded');
|
||||||
|
const content = item.querySelector('.timeline-item-content.tool-call-detail-content');
|
||||||
|
if (content) content.remove();
|
||||||
|
if (expanded) {
|
||||||
|
item.classList.remove('tool-call-detail-expanded');
|
||||||
|
renderToolCallDetailContent(item);
|
||||||
|
}
|
||||||
|
item.dataset.toolResultMerged = '1';
|
||||||
|
item.dataset.toolSuccess = data.success !== false ? '1' : '0';
|
||||||
|
item.classList.remove('tool-call-running');
|
||||||
|
item.classList.add(data.success !== false ? 'tool-call-completed' : 'tool-call-failed');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
let section = item.querySelector('.tool-result-section');
|
let section = item.querySelector('.tool-result-section');
|
||||||
if (!section) {
|
if (!section) {
|
||||||
ensureToolCallResultSlot(item);
|
ensureToolCallResultSlot(item);
|
||||||
@@ -3568,6 +3693,9 @@ function coalesceProcessDetailsToolPairs(details) {
|
|||||||
const rd = resultDetail.data || {};
|
const rd = resultDetail.data || {};
|
||||||
targetDetail.data = targetDetail.data || {};
|
targetDetail.data = targetDetail.data || {};
|
||||||
targetDetail.data._mergedResult = Object.assign({}, rd);
|
targetDetail.data._mergedResult = Object.assign({}, rd);
|
||||||
|
if (resultDetail.id) {
|
||||||
|
targetDetail.data._mergedResultDetailId = resultDetail.id;
|
||||||
|
}
|
||||||
if (resultDetail.createdAt) {
|
if (resultDetail.createdAt) {
|
||||||
targetDetail.data._mergedResultAt = resultDetail.createdAt;
|
targetDetail.data._mergedResultAt = resultDetail.createdAt;
|
||||||
}
|
}
|
||||||
@@ -3581,6 +3709,7 @@ function coalesceProcessDetailsToolPairs(details) {
|
|||||||
|
|
||||||
if (et === 'tool_call') {
|
if (et === 'tool_call') {
|
||||||
const copy = {
|
const copy = {
|
||||||
|
id: detail.id,
|
||||||
eventType: detail.eventType,
|
eventType: detail.eventType,
|
||||||
message: detail.message,
|
message: detail.message,
|
||||||
createdAt: detail.createdAt,
|
createdAt: detail.createdAt,
|
||||||
@@ -3692,6 +3821,44 @@ function buildWorkflowBranchDetailHtml(data) {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isLiveProgressTimeline(timeline) {
|
||||||
|
return !!(timeline && timeline.id && /^progress-\d+-\d+-timeline$/.test(timeline.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function pruneLiveTimelineIfNeeded(timeline) {
|
||||||
|
if (!isLiveProgressTimeline(timeline)) return;
|
||||||
|
const items = Array.from(timeline.children).filter(function (el) {
|
||||||
|
return el && el.classList && el.classList.contains('timeline-item');
|
||||||
|
});
|
||||||
|
if (items.length <= LIVE_TIMELINE_MAX_ITEMS) return;
|
||||||
|
|
||||||
|
let marker = timeline.querySelector('.timeline-live-pruned-marker');
|
||||||
|
let pruned = marker ? (parseInt(marker.dataset.prunedCount || '0', 10) || 0) : 0;
|
||||||
|
let removable = items.filter(function (el) {
|
||||||
|
return !el.dataset.hitlInterruptId &&
|
||||||
|
!el.dataset.workflowRunId &&
|
||||||
|
!el.classList.contains('timeline-item-workflow_hitl_waiting') &&
|
||||||
|
!el.classList.contains('timeline-item-hitl_interrupt');
|
||||||
|
});
|
||||||
|
const overflow = items.length - LIVE_TIMELINE_MAX_ITEMS;
|
||||||
|
const removeCount = Math.min(removable.length, Math.max(overflow, LIVE_TIMELINE_PRUNE_CHUNK));
|
||||||
|
if (removeCount <= 0) return;
|
||||||
|
|
||||||
|
if (!marker) {
|
||||||
|
marker = document.createElement('div');
|
||||||
|
marker.className = 'timeline-live-pruned-marker';
|
||||||
|
timeline.insertBefore(marker, timeline.firstChild);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < removeCount; i++) {
|
||||||
|
removable[i].remove();
|
||||||
|
}
|
||||||
|
pruned += removeCount;
|
||||||
|
marker.dataset.prunedCount = String(pruned);
|
||||||
|
marker.textContent = typeof window.t === 'function'
|
||||||
|
? window.t('chat.liveTimelinePruned', { count: pruned })
|
||||||
|
: ('已收起前 ' + pruned + ' 条实时过程详情,任务完成后可按页查看完整记录');
|
||||||
|
}
|
||||||
|
|
||||||
function addTimelineItem(timeline, type, options) {
|
function addTimelineItem(timeline, type, options) {
|
||||||
const item = document.createElement('div');
|
const item = document.createElement('div');
|
||||||
// 生成唯一ID
|
// 生成唯一ID
|
||||||
@@ -3715,6 +3882,9 @@ function addTimelineItem(timeline, type, options) {
|
|||||||
}
|
}
|
||||||
if (type === 'tool_call' && options.data) {
|
if (type === 'tool_call' && options.data) {
|
||||||
const d = options.data;
|
const d = options.data;
|
||||||
|
if (options.processDetailId) {
|
||||||
|
item.dataset.processDetailId = String(options.processDetailId);
|
||||||
|
}
|
||||||
item.dataset.toolName = (d.toolName != null && d.toolName !== '') ? String(d.toolName) : '';
|
item.dataset.toolName = (d.toolName != null && d.toolName !== '') ? String(d.toolName) : '';
|
||||||
item.dataset.toolIndex = (d.index != null) ? String(d.index) : '0';
|
item.dataset.toolIndex = (d.index != null) ? String(d.index) : '0';
|
||||||
item.dataset.toolTotal = (d.total != null) ? String(d.total) : '0';
|
item.dataset.toolTotal = (d.total != null) ? String(d.total) : '0';
|
||||||
@@ -3725,6 +3895,10 @@ function addTimelineItem(timeline, type, options) {
|
|||||||
if (merged) {
|
if (merged) {
|
||||||
item.dataset.toolResultMerged = '1';
|
item.dataset.toolResultMerged = '1';
|
||||||
item.dataset.toolSuccess = merged.success !== false ? '1' : '0';
|
item.dataset.toolSuccess = merged.success !== false ? '1' : '0';
|
||||||
|
item.classList.add(merged.success !== false ? 'tool-call-completed' : 'tool-call-failed');
|
||||||
|
if (d._mergedResultDetailId) {
|
||||||
|
item.dataset.toolResultDetailId = String(d._mergedResultDetailId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'hitl_interrupt' && options.data && options.data.interruptId != null && String(options.data.interruptId).trim() !== '') {
|
if (type === 'hitl_interrupt' && options.data && options.data.interruptId != null && String(options.data.interruptId).trim() !== '') {
|
||||||
@@ -3738,6 +3912,9 @@ function addTimelineItem(timeline, type, options) {
|
|||||||
}
|
}
|
||||||
if (type === 'tool_result' && options.data) {
|
if (type === 'tool_result' && options.data) {
|
||||||
const d = options.data;
|
const d = options.data;
|
||||||
|
if (options.processDetailId) {
|
||||||
|
item.dataset.processDetailId = String(options.processDetailId);
|
||||||
|
}
|
||||||
item.dataset.toolName = (d.toolName != null && d.toolName !== '') ? String(d.toolName) : '';
|
item.dataset.toolName = (d.toolName != null && d.toolName !== '') ? String(d.toolName) : '';
|
||||||
item.dataset.toolSuccess = d.success !== false ? '1' : '0';
|
item.dataset.toolSuccess = d.success !== false ? '1' : '0';
|
||||||
}
|
}
|
||||||
@@ -3790,34 +3967,29 @@ function addTimelineItem(timeline, type, options) {
|
|||||||
const streamBody = typeof formatTimelineStreamBody === 'function'
|
const streamBody = typeof formatTimelineStreamBody === 'function'
|
||||||
? formatTimelineStreamBody(options.message, options.data)
|
? formatTimelineStreamBody(options.message, options.data)
|
||||||
: options.message;
|
: options.message;
|
||||||
content += `<div class="timeline-item-content">${formatMarkdown(streamBody, timelineMarkdownOpts)}</div>`;
|
content += `<div class="timeline-item-content timeline-stream-plain">${formatTimelinePlainTextHtml(streamBody)}</div>`;
|
||||||
} else if (type === 'tool_call' && options.data) {
|
} else if (type === 'tool_call' && options.data) {
|
||||||
const data = options.data;
|
const data = options.data;
|
||||||
const args = parseToolCallArgsFromData(data);
|
const args = parseToolCallArgsFromData(data);
|
||||||
const merged = options.mergedResult || data._mergedResult;
|
const merged = options.mergedResult || data._mergedResult;
|
||||||
const paramsLabel = typeof window.t === 'function' ? window.t('timeline.params') : '参数:';
|
|
||||||
let resultBlock = '';
|
|
||||||
if (merged) {
|
if (merged) {
|
||||||
resultBlock = '<div class="tool-details tool-result-slot">' + buildToolResultSectionHtml(merged) + '</div>';
|
|
||||||
if (merged.success !== false) {
|
if (merged.success !== false) {
|
||||||
item.classList.add('tool-call-completed');
|
item.classList.add('tool-call-completed');
|
||||||
} else {
|
} else {
|
||||||
item.classList.add('tool-call-failed');
|
item.classList.add('tool-call-failed');
|
||||||
}
|
}
|
||||||
} else if (!options.skipPendingResult) {
|
} else if (!options.skipPendingResult) {
|
||||||
resultBlock = '<div class="tool-details tool-result-slot">' + buildToolResultSectionHtml({}, { pending: true }) + '</div>';
|
item.classList.add('tool-call-running');
|
||||||
}
|
}
|
||||||
content += `
|
setToolCallDetailState(item, {
|
||||||
<div class="timeline-item-content">
|
args: args,
|
||||||
<div class="tool-details">
|
resultData: merged || null,
|
||||||
<div class="tool-arg-section">
|
pending: !merged && !options.skipPendingResult,
|
||||||
<strong data-i18n="timeline.params">${escapeHtml(paramsLabel)}</strong>
|
processDetailId: options.processDetailId || '',
|
||||||
<pre class="tool-args">${escapeHtml(JSON.stringify(args, null, 2))}</pre>
|
resultDetailId: data._mergedResultDetailId || '',
|
||||||
</div>
|
payloadDeferred: data._payloadDeferred === true || (merged && merged._payloadDeferred === true),
|
||||||
${resultBlock}
|
payloadLoaded: !(data._payloadDeferred === true || (merged && merged._payloadDeferred === true))
|
||||||
</div>
|
});
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
} else if ((type === 'eino_agent_reply' || type === 'workflow_agent_output') && options.message) {
|
} else if ((type === 'eino_agent_reply' || type === 'workflow_agent_output') && options.message) {
|
||||||
let prefix = '';
|
let prefix = '';
|
||||||
if (type === 'workflow_agent_output' && options.data) {
|
if (type === 'workflow_agent_output' && options.data) {
|
||||||
@@ -3839,9 +4011,9 @@ function addTimelineItem(timeline, type, options) {
|
|||||||
const body = type === 'workflow_agent_output'
|
const body = type === 'workflow_agent_output'
|
||||||
? `<div class="workflow-agent-output">
|
? `<div class="workflow-agent-output">
|
||||||
<div class="workflow-agent-io-label">输出</div>
|
<div class="workflow-agent-io-label">输出</div>
|
||||||
<div class="workflow-agent-output-body">${formatMarkdown(options.message, timelineMarkdownOpts)}</div>
|
<div class="workflow-agent-output-body">${formatTimelinePlainTextHtml(options.message)}</div>
|
||||||
</div>`
|
</div>`
|
||||||
: formatMarkdown(options.message, timelineMarkdownOpts);
|
: formatTimelinePlainTextHtml(options.message);
|
||||||
content += `<div class="timeline-item-content workflow-agent-io">${prefix}${body}</div>`;
|
content += `<div class="timeline-item-content workflow-agent-io">${prefix}${body}</div>`;
|
||||||
} else if (type === 'workflow_node_result' && options.data && String(options.data.nodeType || '').toLowerCase() === 'condition') {
|
} else if (type === 'workflow_node_result' && options.data && String(options.data.nodeType || '').toLowerCase() === 'condition') {
|
||||||
content += buildWorkflowConditionResultHtml(options.data);
|
content += buildWorkflowConditionResultHtml(options.data);
|
||||||
@@ -3853,17 +4025,17 @@ function addTimelineItem(timeline, type, options) {
|
|||||||
const noResultText = typeof window.t === 'function' ? window.t('timeline.noResult') : '无结果';
|
const noResultText = typeof window.t === 'function' ? window.t('timeline.noResult') : '无结果';
|
||||||
const result = data.result || data.error || noResultText;
|
const result = data.result || data.error || noResultText;
|
||||||
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
||||||
const execResultLabel = typeof window.t === 'function' ? window.t('timeline.executionResult') : '执行结果:';
|
setToolCallDetailState(item, {
|
||||||
const execIdLabel = typeof window.t === 'function' ? window.t('timeline.executionId') : '执行ID:';
|
resultData: data,
|
||||||
content += `
|
rawText: resultStr,
|
||||||
<div class="timeline-item-content">
|
pending: false,
|
||||||
<div class="tool-result-section ${isError ? 'error' : 'success'}">
|
hideArgs: true,
|
||||||
<strong data-i18n="timeline.executionResult">${escapeHtml(execResultLabel)}</strong>
|
processDetailId: options.processDetailId || '',
|
||||||
<pre class="tool-result">${escapeHtml(resultStr)}</pre>
|
resultDetailId: options.processDetailId || '',
|
||||||
${data.executionId ? `<div class="tool-execution-id"><span data-i18n="timeline.executionId">${escapeHtml(execIdLabel)}</span> <code>${escapeHtml(data.executionId)}</code></div>` : ''}
|
payloadDeferred: data._payloadDeferred === true,
|
||||||
</div>
|
payloadLoaded: data._payloadDeferred !== true
|
||||||
</div>
|
});
|
||||||
`;
|
item.classList.add(isError ? 'tool-call-failed' : 'tool-call-completed');
|
||||||
} else if (type === 'cancelled') {
|
} else if (type === 'cancelled') {
|
||||||
const taskCancelledLabel = typeof window.t === 'function' ? window.t('chat.taskCancelled') : '任务已取消';
|
const taskCancelledLabel = typeof window.t === 'function' ? window.t('chat.taskCancelled') : '任务已取消';
|
||||||
content += `
|
content += `
|
||||||
@@ -3875,21 +4047,25 @@ function addTimelineItem(timeline, type, options) {
|
|||||||
const streamBody = typeof formatTimelineStreamBody === 'function'
|
const streamBody = typeof formatTimelineStreamBody === 'function'
|
||||||
? formatTimelineStreamBody(options.message, options.data)
|
? formatTimelineStreamBody(options.message, options.data)
|
||||||
: options.message;
|
: options.message;
|
||||||
content += `<div class="timeline-item-content">${formatMarkdown(streamBody, timelineMarkdownOpts)}</div>`;
|
content += `<div class="timeline-item-content timeline-stream-plain">${formatTimelinePlainTextHtml(streamBody)}</div>`;
|
||||||
} else if (type === 'progress' && options.message) {
|
} else if (type === 'progress' && options.message) {
|
||||||
content += `<div class="timeline-item-content timeline-eino-trace"><pre class="tool-result">${escapeHtml(options.message)}</pre></div>`;
|
content += `<div class="timeline-item-content timeline-eino-trace"><pre class="tool-result">${escapeHtml(options.message)}</pre></div>`;
|
||||||
} else if (type === 'user_interrupt_continue' && options.message) {
|
} else if (type === 'user_interrupt_continue' && options.message) {
|
||||||
const streamBody = typeof formatTimelineStreamBody === 'function'
|
const streamBody = typeof formatTimelineStreamBody === 'function'
|
||||||
? formatTimelineStreamBody(options.message, options.data)
|
? formatTimelineStreamBody(options.message, options.data)
|
||||||
: options.message;
|
: options.message;
|
||||||
content += `<div class="timeline-item-content">${formatMarkdown(streamBody, timelineMarkdownOpts)}</div>`;
|
content += `<div class="timeline-item-content timeline-stream-plain">${formatTimelinePlainTextHtml(streamBody)}</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.innerHTML = content;
|
item.innerHTML = content;
|
||||||
|
if (item.classList.contains('tool-detail-collapsible')) {
|
||||||
|
updateToolDetailToggleLabel(item);
|
||||||
|
}
|
||||||
if (options.data) {
|
if (options.data) {
|
||||||
applyEinoTimelineRole(item, options.data);
|
applyEinoTimelineRole(item, options.data);
|
||||||
}
|
}
|
||||||
timeline.appendChild(item);
|
timeline.appendChild(item);
|
||||||
|
pruneLiveTimelineIfNeeded(timeline);
|
||||||
|
|
||||||
// 自动展开详情
|
// 自动展开详情
|
||||||
const expanded = timeline.classList.contains('expanded');
|
const expanded = timeline.classList.contains('expanded');
|
||||||
@@ -6245,6 +6421,14 @@ function refreshProgressAndTimelineI18n() {
|
|||||||
timeSpan.textContent = d.toLocaleTimeString(timeLocale, timeOpts);
|
timeSpan.textContent = d.toLocaleTimeString(timeLocale, timeOpts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (item.classList.contains('tool-detail-collapsible') && typeof updateToolDetailToggleLabel === 'function') {
|
||||||
|
updateToolDetailToggleLabel(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.timeline-live-pruned-marker').forEach(function (marker) {
|
||||||
|
const count = parseInt(marker.dataset.prunedCount || '0', 10) || 0;
|
||||||
|
marker.textContent = _t('chat.liveTimelinePruned', { count: count });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 详情区「展开/收起」按钮
|
// 详情区「展开/收起」按钮
|
||||||
|
|||||||
@@ -2311,9 +2311,15 @@ function initModelListControls() {
|
|||||||
hitlAuditProv.dataset.modelListBound = '1';
|
hitlAuditProv.dataset.modelListBound = '1';
|
||||||
hitlAuditProv.addEventListener('change', syncModelListFetchButtons);
|
hitlAuditProv.addEventListener('change', syncModelListFetchButtons);
|
||||||
}
|
}
|
||||||
|
const knowledgeEmbeddingProv = document.getElementById('knowledge-embedding-provider');
|
||||||
|
if (knowledgeEmbeddingProv && !knowledgeEmbeddingProv.dataset.modelListBound) {
|
||||||
|
knowledgeEmbeddingProv.dataset.modelListBound = '1';
|
||||||
|
knowledgeEmbeddingProv.addEventListener('change', syncModelListFetchButtons);
|
||||||
|
}
|
||||||
bindModelSelect('openai');
|
bindModelSelect('openai');
|
||||||
bindModelSelect('vision');
|
bindModelSelect('vision');
|
||||||
bindModelSelect('hitlAudit');
|
bindModelSelect('hitlAudit');
|
||||||
|
bindModelSelect('knowledgeEmbedding');
|
||||||
syncModelListFetchButtons();
|
syncModelListFetchButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2324,6 +2330,9 @@ function modelSelectIds(scope) {
|
|||||||
if (scope === 'hitlAudit') {
|
if (scope === 'hitlAudit') {
|
||||||
return { selectId: 'hitl-audit-model-select', inputId: 'hitl-audit-model-name' };
|
return { selectId: 'hitl-audit-model-select', inputId: 'hitl-audit-model-name' };
|
||||||
}
|
}
|
||||||
|
if (scope === 'knowledgeEmbedding') {
|
||||||
|
return { selectId: 'knowledge-embedding-model-select', inputId: 'knowledge-embedding-model' };
|
||||||
|
}
|
||||||
return { selectId: 'openai-model-select', inputId: 'openai-model' };
|
return { selectId: 'openai-model-select', inputId: 'openai-model' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2359,6 +2368,15 @@ function resolveModelListCredentials(scope) {
|
|||||||
|| (document.getElementById('openai-api-key')?.value || '').trim();
|
|| (document.getElementById('openai-api-key')?.value || '').trim();
|
||||||
return { provider, base_url: baseUrl, api_key: apiKey };
|
return { provider, base_url: baseUrl, api_key: apiKey };
|
||||||
}
|
}
|
||||||
|
if (scope === 'knowledgeEmbedding') {
|
||||||
|
const kp = (document.getElementById('knowledge-embedding-provider')?.value || '').trim();
|
||||||
|
const provider = kp || document.getElementById('openai-provider')?.value || 'openai';
|
||||||
|
const baseUrl = (document.getElementById('knowledge-embedding-base-url')?.value || '').trim()
|
||||||
|
|| (document.getElementById('openai-base-url')?.value || '').trim();
|
||||||
|
const apiKey = (document.getElementById('knowledge-embedding-api-key')?.value || '').trim()
|
||||||
|
|| (document.getElementById('openai-api-key')?.value || '').trim();
|
||||||
|
return { provider, base_url: baseUrl, api_key: apiKey };
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
provider: document.getElementById('openai-provider')?.value || 'openai',
|
provider: document.getElementById('openai-provider')?.value || 'openai',
|
||||||
base_url: (document.getElementById('openai-base-url')?.value || '').trim(),
|
base_url: (document.getElementById('openai-base-url')?.value || '').trim(),
|
||||||
@@ -2444,6 +2462,32 @@ function syncModelListFetchButtons() {
|
|||||||
hitlAuditHint.style.display = 'none';
|
hitlAuditHint.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const kp = (document.getElementById('knowledge-embedding-provider')?.value || '').trim();
|
||||||
|
const knowledgeEmbeddingEffectiveProv = kp || openaiProv;
|
||||||
|
const knowledgeEmbeddingBtn = document.getElementById('fetch-knowledge-embedding-models-btn');
|
||||||
|
const knowledgeEmbeddingHint = document.getElementById('fetch-knowledge-embedding-models-hint');
|
||||||
|
const knowledgeEmbeddingSelect = document.getElementById('knowledge-embedding-model-select');
|
||||||
|
const isClaudeKnowledgeEmbedding = knowledgeEmbeddingEffectiveProv === 'claude';
|
||||||
|
if (knowledgeEmbeddingBtn) {
|
||||||
|
knowledgeEmbeddingBtn.style.display = isClaudeKnowledgeEmbedding ? 'none' : '';
|
||||||
|
}
|
||||||
|
if (knowledgeEmbeddingSelect && isClaudeKnowledgeEmbedding) {
|
||||||
|
knowledgeEmbeddingSelect.style.display = 'none';
|
||||||
|
const knowledgeEmbeddingWrap = modelPickSelectMap['knowledge-embedding-model-select'];
|
||||||
|
if (knowledgeEmbeddingWrap) knowledgeEmbeddingWrap.wrapper.style.display = 'none';
|
||||||
|
} else if (knowledgeEmbeddingSelect && !isClaudeKnowledgeEmbedding) {
|
||||||
|
syncModelPickDropdown('knowledge-embedding-model-select');
|
||||||
|
}
|
||||||
|
if (knowledgeEmbeddingHint) {
|
||||||
|
if (isClaudeKnowledgeEmbedding) {
|
||||||
|
knowledgeEmbeddingHint.textContent = tFn('settingsBasic.modelsListClaudeHint');
|
||||||
|
knowledgeEmbeddingHint.style.display = '';
|
||||||
|
} else {
|
||||||
|
knowledgeEmbeddingHint.textContent = '';
|
||||||
|
knowledgeEmbeddingHint.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateModelSelect(scope, models, currentValue) {
|
function populateModelSelect(scope, models, currentValue) {
|
||||||
@@ -2483,12 +2527,27 @@ function populateModelSelect(scope, models, currentValue) {
|
|||||||
async function fetchModelList(scope) {
|
async function fetchModelList(scope) {
|
||||||
const tFn = typeof window.t === 'function' ? window.t : (k) => k;
|
const tFn = typeof window.t === 'function' ? window.t : (k) => k;
|
||||||
const creds = resolveModelListCredentials(scope);
|
const creds = resolveModelListCredentials(scope);
|
||||||
const btnId = scope === 'vision'
|
const modelListUiIds = {
|
||||||
? 'fetch-vision-models-btn'
|
openai: {
|
||||||
: (scope === 'hitlAudit' ? 'fetch-hitl-audit-models-btn' : 'fetch-openai-models-btn');
|
btnId: 'fetch-openai-models-btn',
|
||||||
const resultId = scope === 'vision'
|
resultId: 'fetch-openai-models-result'
|
||||||
? 'fetch-vision-models-result'
|
},
|
||||||
: (scope === 'hitlAudit' ? 'fetch-hitl-audit-models-result' : 'fetch-openai-models-result');
|
vision: {
|
||||||
|
btnId: 'fetch-vision-models-btn',
|
||||||
|
resultId: 'fetch-vision-models-result'
|
||||||
|
},
|
||||||
|
hitlAudit: {
|
||||||
|
btnId: 'fetch-hitl-audit-models-btn',
|
||||||
|
resultId: 'fetch-hitl-audit-models-result'
|
||||||
|
},
|
||||||
|
knowledgeEmbedding: {
|
||||||
|
btnId: 'fetch-knowledge-embedding-models-btn',
|
||||||
|
resultId: 'fetch-knowledge-embedding-models-result'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const uiIds = modelListUiIds[scope] || modelListUiIds.openai;
|
||||||
|
const btnId = uiIds.btnId;
|
||||||
|
const resultId = uiIds.resultId;
|
||||||
const inputId = modelSelectIds(scope).inputId;
|
const inputId = modelSelectIds(scope).inputId;
|
||||||
const btn = document.getElementById(btnId);
|
const btn = document.getElementById(btnId);
|
||||||
const resultEl = document.getElementById(resultId);
|
const resultEl = document.getElementById(resultId);
|
||||||
|
|||||||
@@ -3253,7 +3253,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="knowledge-embedding-model" data-i18n="settingsBasic.modelName">模型名称</label>
|
<label for="knowledge-embedding-model" data-i18n="settingsBasic.modelName">模型名称</label>
|
||||||
<input type="text" id="knowledge-embedding-model" data-i18n="settingsBasic.embeddingModelPlaceholder" data-i18n-attr="placeholder" placeholder="text-embedding-v4" />
|
<div class="model-pick-row">
|
||||||
|
<input type="text" id="knowledge-embedding-model" class="model-pick-input" data-i18n="settingsBasic.embeddingModelPlaceholder" data-i18n-attr="placeholder" placeholder="text-embedding-v4" />
|
||||||
|
<select id="knowledge-embedding-model-select" class="model-pick-native" style="display: none;" aria-hidden="true" tabindex="-1">
|
||||||
|
<option value="" disabled data-i18n="settingsBasic.modelsListSelectPlaceholder">请选择模型</option>
|
||||||
|
</select>
|
||||||
|
<a href="javascript:void(0)" id="fetch-knowledge-embedding-models-btn" class="model-pick-fetch-link" onclick="fetchModelList('knowledgeEmbedding')" data-i18n="settingsBasic.fetchModels">获取列表</a>
|
||||||
|
</div>
|
||||||
|
<small id="fetch-knowledge-embedding-models-hint" class="form-hint" style="display: none; font-size: 0.75rem; margin-top: 4px;"></small>
|
||||||
|
<span id="fetch-knowledge-embedding-models-result" style="font-size: 0.75rem; margin-top: 2px; display: block;"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-subsection-header">
|
<div class="settings-subsection-header">
|
||||||
|
|||||||
Reference in New Issue
Block a user