mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-06-27 16:29:59 +02:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d999792b8 | |||
| afae8970d1 | |||
| 4d7330c5c3 | |||
| 8884bfb0b4 | |||
| fb351c80b6 | |||
| 664834e338 | |||
| 95bf62db88 | |||
| 656242614d | |||
| a9d6d8c00e | |||
| 0d6a43c0a8 | |||
| 702f286eb1 |
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
# 前端显示的版本号(可选,不填则显示默认版本)
|
# 前端显示的版本号(可选,不填则显示默认版本)
|
||||||
version: "v1.6.37"
|
version: "v1.6.38"
|
||||||
# 服务器配置
|
# 服务器配置
|
||||||
server:
|
server:
|
||||||
host: 0.0.0.0 # 监听地址,0.0.0.0 表示监听所有网络接口
|
host: 0.0.0.0 # 监听地址,0.0.0.0 表示监听所有网络接口
|
||||||
|
|||||||
@@ -1185,6 +1185,8 @@ func (h *AgentHandler) createProgressCallback(runCtx context.Context, cancelRun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
flushResponsePlan()
|
flushResponsePlan()
|
||||||
|
// 助手正文开始前,推理流通常已结束;落库以便刷新后「渗透测试详情」可回放
|
||||||
|
flushThinkingStreams()
|
||||||
respPlan.meta = nil
|
respPlan.meta = nil
|
||||||
if dataMap, ok := data.(map[string]interface{}); ok {
|
if dataMap, ok := data.(map[string]interface{}); ok {
|
||||||
respPlan.meta = make(map[string]interface{}, len(dataMap))
|
respPlan.meta = make(map[string]interface{}, len(dataMap))
|
||||||
@@ -1220,6 +1222,19 @@ func (h *AgentHandler) createProgressCallback(runCtx context.Context, cancelRun
|
|||||||
}
|
}
|
||||||
if eventType == "response" {
|
if eventType == "response" {
|
||||||
flushResponsePlan()
|
flushResponsePlan()
|
||||||
|
flushThinkingStreams()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if eventType == "done" {
|
||||||
|
flushResponsePlan()
|
||||||
|
flushThinkingStreams()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 流式思考/推理结束:聚合落库(与 eino_agent_reply_stream_end 同理)
|
||||||
|
if eventType == "thinking_stream_end" || eventType == "reasoning_chain_stream_end" {
|
||||||
|
flushResponsePlan()
|
||||||
|
flushThinkingStreams()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"cyberstrike-ai/internal/config"
|
"cyberstrike-ai/internal/config"
|
||||||
|
"cyberstrike-ai/internal/database"
|
||||||
|
"cyberstrike-ai/internal/openai"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@@ -46,3 +50,50 @@ func TestCreateProgressCallback_ConcurrentToolEvents(t *testing.T) {
|
|||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCreateProgressCallback_FlushesReasoningOnDone 流式推理聚合须在 done/response 时落库,刷新后可回放。
|
||||||
|
func TestCreateProgressCallback_FlushesReasoningOnDone(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
db, err := database.NewDB(filepath.Join(tmp, "test.sqlite"), zap.NewNop())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewDB: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmp)
|
||||||
|
|
||||||
|
conv, err := db.CreateConversation("test", database.ConversationCreateMeta{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateConversation: %v", err)
|
||||||
|
}
|
||||||
|
asst, err := db.AddMessage(conv.ID, "assistant", "处理中...", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddMessage: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := &AgentHandler{logger: zap.NewNop(), db: db}
|
||||||
|
cb := h.createProgressCallback(context.Background(), nil, conv.ID, asst.ID, nil)
|
||||||
|
|
||||||
|
streamID := "eino-reasoning-test-1"
|
||||||
|
cb("reasoning_chain_stream_start", " ", map[string]interface{}{
|
||||||
|
"streamId": streamID,
|
||||||
|
"source": "eino",
|
||||||
|
})
|
||||||
|
cb("reasoning_chain_stream_delta", "step one", openai.WithSSEAccumulated(map[string]interface{}{
|
||||||
|
"streamId": streamID,
|
||||||
|
}, "step one"))
|
||||||
|
cb("done", "", map[string]interface{}{"conversationId": conv.ID})
|
||||||
|
|
||||||
|
details, err := db.GetProcessDetails(asst.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetProcessDetails: %v", err)
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, d := range details {
|
||||||
|
if d.EventType == "reasoning_chain" && d.Message == "step one" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("expected reasoning_chain persisted on done, got %+v", details)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -785,6 +785,16 @@ func runEinoADKAgentLoop(ctx context.Context, args *einoADKRunLoopArgs, baseMsgs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if progress != nil && reasoningStreamID != "" && strings.TrimSpace(reasoningBuf) != "" {
|
||||||
|
progress("reasoning_chain_stream_end", openai.DisplayReasoningContent(strings.TrimSpace(reasoningBuf)), map[string]interface{}{
|
||||||
|
"streamId": reasoningStreamID,
|
||||||
|
"conversationId": conversationID,
|
||||||
|
"source": "eino",
|
||||||
|
"einoAgent": ev.AgentName,
|
||||||
|
"einoRole": einoRoleTag(ev.AgentName),
|
||||||
|
"orchestration": orchMode,
|
||||||
|
})
|
||||||
|
}
|
||||||
if streamsMainAssistant(ev.AgentName) {
|
if streamsMainAssistant(ev.AgentName) {
|
||||||
s := strings.TrimSpace(mainAssistantBuf)
|
s := strings.TrimSpace(mainAssistantBuf)
|
||||||
if mainAssistDupTarget != "" {
|
if mainAssistDupTarget != "" {
|
||||||
|
|||||||
@@ -2419,6 +2419,9 @@ header {
|
|||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
border-top: 1px solid var(--border-color);
|
border-top: 1px solid var(--border-color);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mcp-call-label {
|
.mcp-call-label {
|
||||||
@@ -2473,10 +2476,15 @@ header {
|
|||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
border-top: 1px solid var(--border-color);
|
border-top: 1px solid var(--border-color);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.process-details-content {
|
.process-details-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.process-details-content .progress-timeline {
|
.process-details-content .progress-timeline {
|
||||||
@@ -2488,6 +2496,7 @@ header {
|
|||||||
|
|
||||||
.process-details-content .progress-timeline.expanded {
|
.process-details-content .progress-timeline.expanded {
|
||||||
max-height: 2000px;
|
max-height: 2000px;
|
||||||
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
@@ -3913,6 +3922,7 @@ header {
|
|||||||
|
|
||||||
.progress-timeline.expanded {
|
.progress-timeline.expanded {
|
||||||
max-height: 2000px;
|
max-height: 2000px;
|
||||||
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3920,7 +3930,8 @@ header {
|
|||||||
.progress-container.is-streaming .progress-timeline.expanded,
|
.progress-container.is-streaming .progress-timeline.expanded,
|
||||||
.process-details-container.is-streaming .process-details-content .progress-timeline.expanded {
|
.process-details-container.is-streaming .process-details-content .progress-timeline.expanded {
|
||||||
max-height: none;
|
max-height: none;
|
||||||
overflow: visible;
|
overflow-x: hidden;
|
||||||
|
overflow-y: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-item {
|
.timeline-item {
|
||||||
@@ -3931,6 +3942,10 @@ header {
|
|||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-item:hover {
|
.timeline-item:hover {
|
||||||
@@ -4088,6 +4103,12 @@ header {
|
|||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-x: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 流式增量阶段纯文本展示(避免半段 Markdown 反复解析) */
|
/* 流式增量阶段纯文本展示(避免半段 Markdown 反复解析) */
|
||||||
@@ -4096,6 +4117,37 @@ header {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 过程详情 Markdown:避免长 URL/代码/表格撑破紫/蓝时间线条目 */
|
||||||
|
.timeline-item-content p,
|
||||||
|
.timeline-item-content li,
|
||||||
|
.timeline-item-content td,
|
||||||
|
.timeline-item-content th {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item-content pre {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item-content code {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item-content .table-wrapper {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item-content table {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
/* 长过程详情:跳过视口外时间线条目的布局/绘制,减轻大段工具输出时的主线程压力 */
|
/* 长过程详情:跳过视口外时间线条目的布局/绘制,减轻大段工具输出时的主线程压力 */
|
||||||
.progress-timeline .timeline-item {
|
.progress-timeline .timeline-item {
|
||||||
content-visibility: auto;
|
content-visibility: auto;
|
||||||
@@ -14649,6 +14701,7 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
|
|||||||
}
|
}
|
||||||
.webshell-ai-process-block .process-details-content .progress-timeline.expanded {
|
.webshell-ai-process-block .process-details-content .progress-timeline.expanded {
|
||||||
max-height: 2000px;
|
max-height: 2000px;
|
||||||
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14880,6 +14933,10 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
|
|||||||
position: relative;
|
position: relative;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
.ws-project-selector-wrapper {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
.ws-agent-mode-wrapper {
|
.ws-agent-mode-wrapper {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -19255,6 +19312,12 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-mode-options > .role-selection-item-main {
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 选项为 <button>,浏览器默认 text-align:center 会继承到文案,强制左对齐与角色列表一致 */
|
/* 选项为 <button>,浏览器默认 text-align:center 会继承到文案,强制左对齐与角色列表一致 */
|
||||||
@@ -19286,7 +19349,7 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 340px;
|
width: 340px;
|
||||||
max-width: calc(100vw - 32px);
|
max-width: calc(100vw - 32px);
|
||||||
max-height: 60vh;
|
max-height: min(580px, 60vh);
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
@@ -19296,6 +19359,7 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
animation: slideUp 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
animation: slideUp 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19338,11 +19402,17 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
/* 限制显示8个角色:每个角色约70px高度 + gap,8个角色约580px */
|
/* 限制显示8个角色:每个角色约70px高度 + gap,8个角色约580px */
|
||||||
max-height: 580px;
|
max-height: 580px;
|
||||||
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 防止 flex 列容器在高度受限时把列表项纵向压扁(应滚动而非压缩) */
|
||||||
|
.role-selection-list-main > .role-selection-item-main {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.role-selection-list-main::-webkit-scrollbar {
|
.role-selection-list-main::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
}
|
}
|
||||||
@@ -19376,7 +19446,8 @@ tr.mcp-stats-tool-row[data-tool-name]:focus-visible {
|
|||||||
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
position: relative;
|
position: relative;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.role-selection-item-main:hover {
|
.role-selection-item-main:hover {
|
||||||
@@ -22760,6 +22831,7 @@ button.chat-files-dropdown-item:hover:not(:disabled) {
|
|||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
-webkit-line-clamp: 3;
|
-webkit-line-clamp: 3;
|
||||||
|
line-clamp: 3;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.projects-description-textarea {
|
.projects-description-textarea {
|
||||||
|
|||||||
+114
-5
@@ -2164,6 +2164,97 @@ function showCopySuccess(button) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Claude extended thinking 内部尾缀(与后端 DisplayReasoningContent 一致,UI 不展示) */
|
||||||
|
const CLAUDE_REASONING_UI_SUFFIX = '\n---CSAI_CLAUDE_THINKING_BLOCKS---\n';
|
||||||
|
|
||||||
|
function normalizeReasoningContentForDisplay(text) {
|
||||||
|
if (text == null) return '';
|
||||||
|
let s = String(text).trim();
|
||||||
|
if (!s) return '';
|
||||||
|
const idx = s.lastIndexOf(CLAUDE_REASONING_UI_SUFFIX);
|
||||||
|
if (idx >= 0) {
|
||||||
|
s = s.slice(0, idx).trim();
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMessageReasoningContent(messageIdOrEl, reasoningContent) {
|
||||||
|
const el = typeof messageIdOrEl === 'string' ? document.getElementById(messageIdOrEl) : messageIdOrEl;
|
||||||
|
if (!el || !el.dataset) return;
|
||||||
|
const rc = normalizeReasoningContentForDisplay(reasoningContent);
|
||||||
|
if (rc) {
|
||||||
|
el.dataset.reasoningContent = rc;
|
||||||
|
} else {
|
||||||
|
delete el.dataset.reasoningContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMessageReasoningContent(messageIdOrEl) {
|
||||||
|
const el = typeof messageIdOrEl === 'string' ? document.getElementById(messageIdOrEl) : messageIdOrEl;
|
||||||
|
if (!el || !el.dataset) return '';
|
||||||
|
return normalizeReasoningContentForDisplay(el.dataset.reasoningContent || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function reasoningTextAlreadyInProcessDetails(processDetails, rc) {
|
||||||
|
if (!rc) return true;
|
||||||
|
const list = Array.isArray(processDetails) ? processDetails : [];
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
const d = list[i];
|
||||||
|
if (!d) continue;
|
||||||
|
const et = d.eventType || '';
|
||||||
|
if (et !== 'reasoning_chain' && et !== 'thinking') continue;
|
||||||
|
const msg = normalizeReasoningContentForDisplay(d.message || '');
|
||||||
|
if (!msg) continue;
|
||||||
|
if (msg === rc || msg.includes(rc) || rc.includes(msg)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 合并 messages.reasoningContent 与 process_details 中的 reasoning_chain,两者都读、都展示(去重后) */
|
||||||
|
function mergeMessageReasoningContentIntoProcessDetails(processDetails, reasoningContent) {
|
||||||
|
const rc = normalizeReasoningContentForDisplay(reasoningContent);
|
||||||
|
const details = Array.isArray(processDetails) ? processDetails.slice() : [];
|
||||||
|
if (!rc || reasoningTextAlreadyInProcessDetails(details, rc)) {
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
details.push({
|
||||||
|
eventType: 'reasoning_chain',
|
||||||
|
message: rc,
|
||||||
|
data: { source: 'message.reasoningContent' }
|
||||||
|
});
|
||||||
|
return details;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncAssistantReasoningContentFromServer(backendMessageId, domAssistantId) {
|
||||||
|
if (!backendMessageId || !domAssistantId || !currentConversationId || typeof apiFetch !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const convRes = await apiFetch(`/api/conversations/${encodeURIComponent(currentConversationId)}?include_process_details=0`);
|
||||||
|
const conv = await convRes.json().catch(() => ({}));
|
||||||
|
if (!convRes.ok || !Array.isArray(conv.messages)) return;
|
||||||
|
const msg = conv.messages.find((m) => m && String(m.id) === String(backendMessageId));
|
||||||
|
if (!msg || !msg.reasoningContent) return;
|
||||||
|
setMessageReasoningContent(domAssistantId, msg.reasoningContent);
|
||||||
|
const pdRes = await apiFetch(`/api/messages/${encodeURIComponent(String(backendMessageId))}/process-details`);
|
||||||
|
const pdJson = await pdRes.json().catch(() => ({}));
|
||||||
|
const details = pdRes.ok && Array.isArray(pdJson.processDetails) ? pdJson.processDetails : [];
|
||||||
|
if (typeof renderProcessDetails === 'function') {
|
||||||
|
renderProcessDetails(domAssistantId, details);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('syncAssistantReasoningContentFromServer failed', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.normalizeReasoningContentForDisplay = normalizeReasoningContentForDisplay;
|
||||||
|
window.setMessageReasoningContent = setMessageReasoningContent;
|
||||||
|
window.getMessageReasoningContent = getMessageReasoningContent;
|
||||||
|
window.mergeMessageReasoningContentIntoProcessDetails = mergeMessageReasoningContentIntoProcessDetails;
|
||||||
|
window.syncAssistantReasoningContentFromServer = syncAssistantReasoningContentFromServer;
|
||||||
|
|
||||||
/** 相邻且类型/正文/data 完全一致的过程详情只保留一条(与后端去重一致,避免时间线叠多条相同块) */
|
/** 相邻且类型/正文/data 完全一致的过程详情只保留一条(与后端去重一致,避免时间线叠多条相同块) */
|
||||||
function dedupeConsecutiveProcessDetailRows(details) {
|
function dedupeConsecutiveProcessDetailRows(details) {
|
||||||
if (!Array.isArray(details) || details.length < 2) {
|
if (!Array.isArray(details) || details.length < 2) {
|
||||||
@@ -2282,20 +2373,27 @@ function renderProcessDetails(messageId, processDetails) {
|
|||||||
detailsContainer.appendChild(contentDiv);
|
detailsContainer.appendChild(contentDiv);
|
||||||
}
|
}
|
||||||
|
|
||||||
// processDetails === null 表示“尚未加载(懒加载)”
|
// processDetails === null 表示“尚未加载(懒加载)”;messages.reasoningContent 可先展示
|
||||||
const isLazyNotLoaded = (processDetails === null);
|
const isLazyNotLoaded = (processDetails === null);
|
||||||
if (isLazyNotLoaded) {
|
const reasoningFromMessage = getMessageReasoningContent(messageElement);
|
||||||
|
if (isLazyNotLoaded && !reasoningFromMessage) {
|
||||||
detailsContainer.dataset.lazyNotLoaded = '1';
|
detailsContainer.dataset.lazyNotLoaded = '1';
|
||||||
detailsContainer.dataset.loaded = '0';
|
detailsContainer.dataset.loaded = '0';
|
||||||
timeline.innerHTML = '<div class="progress-timeline-empty">' +
|
timeline.innerHTML = '<div class="progress-timeline-empty">' +
|
||||||
(typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情') +
|
(typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情') +
|
||||||
'(点击后加载)</div>';
|
'(点击后加载)</div>';
|
||||||
// 默认折叠
|
|
||||||
timeline.classList.remove('expanded');
|
timeline.classList.remove('expanded');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
detailsContainer.dataset.lazyNotLoaded = '0';
|
if (isLazyNotLoaded) {
|
||||||
detailsContainer.dataset.loaded = '1';
|
detailsContainer.dataset.lazyNotLoaded = '1';
|
||||||
|
detailsContainer.dataset.loaded = '0';
|
||||||
|
processDetails = [];
|
||||||
|
} else {
|
||||||
|
detailsContainer.dataset.lazyNotLoaded = '0';
|
||||||
|
detailsContainer.dataset.loaded = '1';
|
||||||
|
}
|
||||||
|
processDetails = mergeMessageReasoningContentIntoProcessDetails(processDetails, reasoningFromMessage);
|
||||||
processDetails = dedupeConsecutiveProcessDetailRows(processDetails);
|
processDetails = dedupeConsecutiveProcessDetailRows(processDetails);
|
||||||
if (typeof window.coalesceProcessDetailsToolPairs === 'function') {
|
if (typeof window.coalesceProcessDetailsToolPairs === 'function') {
|
||||||
processDetails = window.coalesceProcessDetailsToolPairs(processDetails);
|
processDetails = window.coalesceProcessDetailsToolPairs(processDetails);
|
||||||
@@ -2427,6 +2525,14 @@ function renderProcessDetails(messageId, processDetails) {
|
|||||||
addTimelineItem(timeline, eventType, timelineOpts);
|
addTimelineItem(timeline, eventType, timelineOpts);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isLazyNotLoaded && reasoningFromMessage) {
|
||||||
|
const lazyHint = document.createElement('div');
|
||||||
|
lazyHint.className = 'progress-timeline-empty progress-timeline-lazy-hint';
|
||||||
|
lazyHint.textContent = (typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情') +
|
||||||
|
'(点击后加载完整过程详情)';
|
||||||
|
timeline.appendChild(lazyHint);
|
||||||
|
}
|
||||||
|
|
||||||
// 检查是否有错误或取消事件,如果有,确保详情默认折叠(但仍有待审批 HITL 时保持展开,由 restoreHitlInlineForConversation 处理)
|
// 检查是否有错误或取消事件,如果有,确保详情默认折叠(但仍有待审批 HITL 时保持展开,由 restoreHitlInlineForConversation 处理)
|
||||||
const hasPendingHitlInDetails = processDetails.some(d => d && d.eventType === 'hitl_interrupt');
|
const hasPendingHitlInDetails = processDetails.some(d => d && d.eventType === 'hitl_interrupt');
|
||||||
const hasErrorOrCancelled = processDetails.some(d =>
|
const hasErrorOrCancelled = processDetails.some(d =>
|
||||||
@@ -3193,6 +3299,9 @@ async function loadConversation(conversationId) {
|
|||||||
attachDeleteTurnButton(messageEl);
|
attachDeleteTurnButton(messageEl);
|
||||||
}
|
}
|
||||||
if (msg.role === 'assistant') {
|
if (msg.role === 'assistant') {
|
||||||
|
if (messageEl && msg.reasoningContent) {
|
||||||
|
setMessageReasoningContent(messageEl, msg.reasoningContent);
|
||||||
|
}
|
||||||
const hasField = msg && Object.prototype.hasOwnProperty.call(msg, 'processDetails');
|
const hasField = msg && Object.prototype.hasOwnProperty.call(msg, 'processDetails');
|
||||||
renderProcessDetails(messageId, hasField ? (msg.processDetails || []) : null);
|
renderProcessDetails(messageId, hasField ? (msg.processDetails || []) : null);
|
||||||
if (msg.processDetails && msg.processDetails.length > 0) {
|
if (msg.processDetails && msg.processDetails.length > 0) {
|
||||||
|
|||||||
@@ -172,6 +172,59 @@ function einoMainStreamPlanningTitle(responseData) {
|
|||||||
return prefix + '📝 ' + plan;
|
return prefix + '📝 ' + plan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主通道 response 结束时:将流式占位条目固化为 planning(与后端 flushResponsePlan 落库类型一致),
|
||||||
|
* 避免 integrateProgressToMCPSection 快照前删除占位导致「助手输出」仅刷新后才出现。
|
||||||
|
*/
|
||||||
|
function finalizeMainResponseStreamItem(streamState, finalMessage, responseData) {
|
||||||
|
if (!streamState || !streamState.itemId) return false;
|
||||||
|
const item = document.getElementById(streamState.itemId);
|
||||||
|
if (!item || !item.parentNode) return false;
|
||||||
|
|
||||||
|
const fullText = (finalMessage != null && String(finalMessage).trim() !== '')
|
||||||
|
? String(finalMessage)
|
||||||
|
: (streamState.buffer || '');
|
||||||
|
if (!String(fullText).trim()) {
|
||||||
|
item.parentNode.removeChild(item);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = Object.assign({}, streamState.streamMeta || {}, responseData || {});
|
||||||
|
|
||||||
|
item.classList.remove('timeline-item-thinking');
|
||||||
|
item.classList.add('timeline-item-planning');
|
||||||
|
item.dataset.timelineType = 'planning';
|
||||||
|
delete item.dataset.responseStreamPlaceholder;
|
||||||
|
if (meta.orchestration != null && String(meta.orchestration).trim() !== '') {
|
||||||
|
item.dataset.orchestration = String(meta.orchestration).trim();
|
||||||
|
}
|
||||||
|
if (meta.einoAgent != null && String(meta.einoAgent).trim() !== '') {
|
||||||
|
item.dataset.einoAgent = String(meta.einoAgent).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleEl = item.querySelector('.timeline-item-title');
|
||||||
|
if (titleEl && typeof einoMainStreamPlanningTitle === 'function') {
|
||||||
|
titleEl.textContent = einoMainStreamPlanningTitle(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentEl = item.querySelector('.timeline-item-content');
|
||||||
|
if (!contentEl) {
|
||||||
|
contentEl = document.createElement('div');
|
||||||
|
contentEl.className = 'timeline-item-content';
|
||||||
|
item.appendChild(contentEl);
|
||||||
|
}
|
||||||
|
flushStreamPlainTextUpdate(contentEl);
|
||||||
|
const body = typeof formatTimelineStreamBody === 'function'
|
||||||
|
? formatTimelineStreamBody(fullText, meta)
|
||||||
|
: fullText;
|
||||||
|
if (typeof formatMarkdown === 'function') {
|
||||||
|
setTimelineItemContentStreamRich(contentEl, formatMarkdown(body, timelineMarkdownOpts));
|
||||||
|
} else {
|
||||||
|
setTimelineItemContentStreamPlain(contentEl, body);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function translateProgressMessage(message, data) {
|
function translateProgressMessage(message, data) {
|
||||||
if (!message || typeof message !== 'string') return message;
|
if (!message || typeof message !== 'string') return message;
|
||||||
if (typeof window.t !== 'function') return message;
|
if (typeof window.t !== 'function') return message;
|
||||||
@@ -224,6 +277,7 @@ if (typeof window !== 'undefined') {
|
|||||||
window.translateProgressMessage = translateProgressMessage;
|
window.translateProgressMessage = translateProgressMessage;
|
||||||
window.translatePlanExecuteAgentName = translatePlanExecuteAgentName;
|
window.translatePlanExecuteAgentName = translatePlanExecuteAgentName;
|
||||||
window.einoMainStreamPlanningTitle = einoMainStreamPlanningTitle;
|
window.einoMainStreamPlanningTitle = einoMainStreamPlanningTitle;
|
||||||
|
window.finalizeMainResponseStreamItem = finalizeMainResponseStreamItem;
|
||||||
window.formatTimelineStreamBody = formatTimelineStreamBody;
|
window.formatTimelineStreamBody = formatTimelineStreamBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2401,14 +2455,18 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
updateAssistantBubbleContent(assistantIdFinal, event.message, true);
|
updateAssistantBubbleContent(assistantIdFinal, event.message, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除 response_start/response_delta 阶段创建的「规划中」占位条目。
|
// 将 response_start/response_delta 占位固化为 planning,与后端落库一致后再快照过程详情
|
||||||
// 该条目属于 UI-only 的流式展示,不应被拷贝到最终的过程详情里;
|
|
||||||
// 否则会出现“不刷新页面仍显示规划中,刷新后消失”的不一致。
|
|
||||||
if (streamState && streamState.itemId) {
|
if (streamState && streamState.itemId) {
|
||||||
const planningItem = document.getElementById(streamState.itemId);
|
finalizeMainResponseStreamItem(streamState, event.message, responseData);
|
||||||
if (planningItem && planningItem.parentNode) {
|
} else if (event.message && String(event.message).trim()) {
|
||||||
planningItem.parentNode.removeChild(planningItem);
|
addTimelineItem(timeline, 'planning', {
|
||||||
}
|
title: typeof einoMainStreamPlanningTitle === 'function'
|
||||||
|
? einoMainStreamPlanningTitle(responseData)
|
||||||
|
: ('📝 ' + (typeof window.t === 'function' ? window.t('chat.planning') : '规划中')),
|
||||||
|
message: event.message,
|
||||||
|
data: responseData,
|
||||||
|
expanded: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最终回复时隐藏进度卡片(多代理模式下,迭代过程已完整展示)
|
// 最终回复时隐藏进度卡片(多代理模式下,迭代过程已完整展示)
|
||||||
@@ -2429,6 +2487,11 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
const respMid = responseData.messageId;
|
const respMid = responseData.messageId;
|
||||||
if (respMid) {
|
if (respMid) {
|
||||||
applyBackendMessageIdToAssistantDom(assistantIdFinal, respMid);
|
applyBackendMessageIdToAssistantDom(assistantIdFinal, respMid);
|
||||||
|
if (typeof window.syncAssistantReasoningContentFromServer === 'function') {
|
||||||
|
setTimeout(function () {
|
||||||
|
window.syncAssistantReasoningContentFromServer(respMid, assistantIdFinal);
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -1270,12 +1270,18 @@ async function saveProjectModal() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fromChat = !!window._projectModalFromChat;
|
const fromChat = !!window._projectModalFromChat;
|
||||||
|
const fromWebshellConnId = window._projectModalFromWebshellConnId || '';
|
||||||
window._projectModalFromChat = false;
|
window._projectModalFromChat = false;
|
||||||
|
window._projectModalFromWebshellConnId = '';
|
||||||
closeProjectModal();
|
closeProjectModal();
|
||||||
const saved = await res.json();
|
const saved = await res.json();
|
||||||
await loadProjectsList();
|
await loadProjectsList();
|
||||||
if (saved.id) {
|
if (saved.id) {
|
||||||
if (fromChat && !editId) {
|
if (fromWebshellConnId && !editId) {
|
||||||
|
if (typeof applyWebshellAiProjectSelection === 'function') {
|
||||||
|
await applyWebshellAiProjectSelection(saved.id);
|
||||||
|
}
|
||||||
|
} else if (fromChat && !editId) {
|
||||||
await applyChatProjectSelection(saved.id);
|
await applyChatProjectSelection(saved.id);
|
||||||
} else {
|
} else {
|
||||||
await selectProject(saved.id);
|
await selectProject(saved.id);
|
||||||
|
|||||||
+263
-8
@@ -27,6 +27,9 @@ const WEBSHELL_HISTORY_MAX = 100;
|
|||||||
let webshellClearInProgress = false;
|
let webshellClearInProgress = false;
|
||||||
// AI 助手:按连接 ID 保存对话 ID,便于多轮对话
|
// AI 助手:按连接 ID 保存对话 ID,便于多轮对话
|
||||||
let webshellAiConvMap = {};
|
let webshellAiConvMap = {};
|
||||||
|
// AI 助手:项目绑定(已有对话按 convId,新对话按 connId 草稿)
|
||||||
|
let webshellAiProjectByConvId = {};
|
||||||
|
let webshellAiDraftProjectByConn = {};
|
||||||
let webshellAiSending = false;
|
let webshellAiSending = false;
|
||||||
let webshellAiAbortController = null; // AbortController for current AI stream
|
let webshellAiAbortController = null; // AbortController for current AI stream
|
||||||
let webshellAiStreamReader = null; // Current ReadableStreamDefaultReader
|
let webshellAiStreamReader = null; // Current ReadableStreamDefaultReader
|
||||||
@@ -266,6 +269,7 @@ function wsToggleRolePanel() {
|
|||||||
var isOpen = panel.style.display === 'flex';
|
var isOpen = panel.style.display === 'flex';
|
||||||
if (isOpen) { wsCloseRolePanel(); return; }
|
if (isOpen) { wsCloseRolePanel(); return; }
|
||||||
wsCloseAgentModePanel();
|
wsCloseAgentModePanel();
|
||||||
|
wsCloseProjectPanel();
|
||||||
panel.style.display = 'flex';
|
panel.style.display = 'flex';
|
||||||
}
|
}
|
||||||
function wsCloseRolePanel() {
|
function wsCloseRolePanel() {
|
||||||
@@ -340,6 +344,7 @@ function wsToggleAgentModePanel() {
|
|||||||
var isOpen = panel.style.display === 'flex';
|
var isOpen = panel.style.display === 'flex';
|
||||||
if (isOpen) { wsCloseAgentModePanel(); return; }
|
if (isOpen) { wsCloseAgentModePanel(); return; }
|
||||||
wsCloseRolePanel();
|
wsCloseRolePanel();
|
||||||
|
wsCloseProjectPanel();
|
||||||
panel.style.display = 'flex';
|
panel.style.display = 'flex';
|
||||||
}
|
}
|
||||||
function wsCloseAgentModePanel() {
|
function wsCloseAgentModePanel() {
|
||||||
@@ -347,10 +352,204 @@ function wsCloseAgentModePanel() {
|
|||||||
if (panel) panel.style.display = 'none';
|
if (panel) panel.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── WebShell AI 项目选择器(与主「对话」页对齐) ───
|
||||||
|
|
||||||
|
function wsProjectT(key, fallback) {
|
||||||
|
if (typeof window.t === 'function') {
|
||||||
|
var v = window.t(key);
|
||||||
|
if (v && v !== key) return v;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWebshellAiConvId(conn) {
|
||||||
|
if (!conn || !conn.id) return '';
|
||||||
|
return webshellAiConvMap[conn.id] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWebshellAiProjectSelection(conn) {
|
||||||
|
if (!conn || !conn.id) return '';
|
||||||
|
var convId = getWebshellAiConvId(conn);
|
||||||
|
if (convId) return webshellAiProjectByConvId[convId] || '';
|
||||||
|
return webshellAiDraftProjectByConn[conn.id] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function wsSetWebshellAiProject(conn, projectId) {
|
||||||
|
if (!conn || !conn.id) return;
|
||||||
|
var pid = projectId || '';
|
||||||
|
var convId = getWebshellAiConvId(conn);
|
||||||
|
if (convId) {
|
||||||
|
if (pid) webshellAiProjectByConvId[convId] = pid;
|
||||||
|
else delete webshellAiProjectByConvId[convId];
|
||||||
|
} else if (pid) {
|
||||||
|
webshellAiDraftProjectByConn[conn.id] = pid;
|
||||||
|
} else {
|
||||||
|
delete webshellAiDraftProjectByConn[conn.id];
|
||||||
|
}
|
||||||
|
wsUpdateProjectButtonLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
function wsIsActiveProjectId(id) {
|
||||||
|
if (!id) return false;
|
||||||
|
var map = window.projectNameById || {};
|
||||||
|
return !!map[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
function wsResolveWebshellAiProjectSelection(conn) {
|
||||||
|
var raw = getWebshellAiProjectSelection(conn);
|
||||||
|
if (!raw) return '';
|
||||||
|
return wsIsActiveProjectId(raw) ? raw : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function wsUpdateProjectButtonLabel() {
|
||||||
|
var textEl = document.getElementById('ws-project-text');
|
||||||
|
if (!textEl || !webshellCurrentConn) return;
|
||||||
|
var id = wsResolveWebshellAiProjectSelection(webshellCurrentConn);
|
||||||
|
var nameMap = window.projectNameById || {};
|
||||||
|
textEl.textContent = id && nameMap[id] ? nameMap[id] : wsProjectT('projects.noProject', '无项目');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wsRenderProjectPanelList() {
|
||||||
|
var list = document.getElementById('ws-project-list');
|
||||||
|
if (!list || !webshellCurrentConn) return;
|
||||||
|
var conn = webshellCurrentConn;
|
||||||
|
var selected = wsResolveWebshellAiProjectSelection(conn);
|
||||||
|
var projects = [];
|
||||||
|
try {
|
||||||
|
if (typeof window.fetchAllProjects === 'function') {
|
||||||
|
projects = await window.fetchAllProjects(false);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
list.innerHTML = '<div class="chat-project-panel-empty">' + escapeHtml(wsProjectT('projects.loadFailedRetry', '加载失败,请重试')) + '</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof window.rebuildProjectNameMap === 'function') {
|
||||||
|
window.rebuildProjectNameMap(projects);
|
||||||
|
}
|
||||||
|
var activeProjects = projects.filter(function (p) { return p.status !== 'archived'; });
|
||||||
|
var items = [{ id: '', name: wsProjectT('projects.noProject', '无项目'), description: wsProjectT('projects.noProjectDescription', '不绑定项目') }].concat(activeProjects);
|
||||||
|
list.innerHTML = '';
|
||||||
|
items.forEach(function (p) {
|
||||||
|
var isNone = !p.id;
|
||||||
|
var isSelected = isNone ? !selected : selected === p.id;
|
||||||
|
var desc = isNone
|
||||||
|
? (p.description || '')
|
||||||
|
: ((p.description || '').trim().slice(0, 80) || wsProjectT('projects.sharedFactBoard', '共享事实黑板'));
|
||||||
|
var btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 'role-selection-item-main' + (isSelected ? ' selected' : '');
|
||||||
|
btn.setAttribute('role', 'option');
|
||||||
|
btn.onclick = function () { wsSelectProject(p.id || ''); };
|
||||||
|
btn.innerHTML = '<div class="role-selection-item-icon-main">' + (isNone ? '—' : '📁') + '</div>' +
|
||||||
|
'<div class="role-selection-item-content-main">' +
|
||||||
|
'<div class="role-selection-item-name-main">' + escapeHtml(p.name || '未命名') + '</div>' +
|
||||||
|
'<div class="role-selection-item-description-main">' + escapeHtml(desc) + '</div></div>' +
|
||||||
|
(isSelected ? '<div class="role-selection-checkmark-main">✓</div>' : '');
|
||||||
|
list.appendChild(btn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wsRenderProjectPanel() {
|
||||||
|
var list = document.getElementById('ws-project-list');
|
||||||
|
if (!list) return;
|
||||||
|
list.innerHTML = '<div class="chat-project-panel-loading">' + escapeHtml(wsProjectT('common.loading', '加载中...')) + '</div>';
|
||||||
|
await wsRenderProjectPanelList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function wsCloseProjectPanel() {
|
||||||
|
var panel = document.getElementById('ws-project-panel');
|
||||||
|
var btn = document.getElementById('ws-project-btn');
|
||||||
|
if (panel) panel.style.display = 'none';
|
||||||
|
if (btn) {
|
||||||
|
btn.classList.remove('active');
|
||||||
|
btn.setAttribute('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wsToggleProjectPanel() {
|
||||||
|
var panel = document.getElementById('ws-project-panel');
|
||||||
|
var btn = document.getElementById('ws-project-btn');
|
||||||
|
if (!panel) return;
|
||||||
|
var isHidden = panel.style.display === 'none' || !panel.style.display;
|
||||||
|
if (!isHidden) {
|
||||||
|
wsCloseProjectPanel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wsCloseRolePanel();
|
||||||
|
wsCloseAgentModePanel();
|
||||||
|
panel.style.display = 'flex';
|
||||||
|
if (btn) {
|
||||||
|
btn.classList.add('active');
|
||||||
|
btn.setAttribute('aria-expanded', 'true');
|
||||||
|
}
|
||||||
|
await wsRenderProjectPanel();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wsSelectProject(projectId) {
|
||||||
|
wsCloseProjectPanel();
|
||||||
|
await applyWebshellAiProjectSelection(projectId || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyWebshellAiProjectSelection(projectId) {
|
||||||
|
var conn = webshellCurrentConn;
|
||||||
|
if (!conn || !conn.id) return;
|
||||||
|
var prev = getWebshellAiProjectSelection(conn);
|
||||||
|
if (projectId === prev) {
|
||||||
|
wsUpdateProjectButtonLabel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var convId = getWebshellAiConvId(conn);
|
||||||
|
if (convId) {
|
||||||
|
try {
|
||||||
|
var res = await apiFetch('/api/conversations/' + encodeURIComponent(convId) + '/project', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ projectId: projectId }),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
var err = await res.json().catch(function () { return {}; });
|
||||||
|
throw new Error(err.error || res.statusText);
|
||||||
|
}
|
||||||
|
wsSetWebshellAiProject(conn, projectId);
|
||||||
|
if (typeof showNotification === 'function') {
|
||||||
|
showNotification(
|
||||||
|
projectId ? wsProjectT('projects.projectBound', '已绑定项目') : wsProjectT('projects.projectUnbound', '已解除项目绑定'),
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
alert(wsProjectT('projects.updateProjectBindingFailed', '更新项目绑定失败') + ': ' + (e.message || e));
|
||||||
|
wsUpdateProjectButtonLabel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wsSetWebshellAiProject(conn, projectId);
|
||||||
|
}
|
||||||
|
wsUpdateProjectButtonLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNewProjectModalFromWebshellAi() {
|
||||||
|
wsCloseProjectPanel();
|
||||||
|
if (webshellCurrentConn && webshellCurrentConn.id) {
|
||||||
|
window._projectModalFromWebshellConnId = webshellCurrentConn.id;
|
||||||
|
}
|
||||||
|
window._projectModalFromChat = false;
|
||||||
|
if (typeof showNewProjectModal === 'function') showNewProjectModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.applyWebshellAiProjectSelection = applyWebshellAiProjectSelection;
|
||||||
|
window.showNewProjectModalFromWebshellAi = showNewProjectModalFromWebshellAi;
|
||||||
|
window.wsToggleProjectPanel = wsToggleProjectPanel;
|
||||||
|
window.wsCloseProjectPanel = wsCloseProjectPanel;
|
||||||
|
|
||||||
|
// ─── end WebShell AI 项目选择器 ───
|
||||||
|
|
||||||
/** 当 WebShell AI Tab 可见时刷新选择器显示(同步主页可能的更改) */
|
/** 当 WebShell AI Tab 可见时刷新选择器显示(同步主页可能的更改) */
|
||||||
function wsRefreshSelectors() {
|
function wsRefreshSelectors() {
|
||||||
wsUpdateRoleSelectorDisplay();
|
wsUpdateRoleSelectorDisplay();
|
||||||
wsRenderRoleList();
|
wsRenderRoleList();
|
||||||
|
wsUpdateProjectButtonLabel();
|
||||||
var stored = localStorage.getItem('cyberstrike-chat-agent-mode') || 'eino_single';
|
var stored = localStorage.getItem('cyberstrike-chat-agent-mode') || 'eino_single';
|
||||||
if (stored !== 'eino_single' && stored !== 'deep' && stored !== 'plan_execute' && stored !== 'supervisor') {
|
if (stored !== 'eino_single' && stored !== 'deep' && stored !== 'plan_execute' && stored !== 'supervisor') {
|
||||||
stored = 'eino_single';
|
stored = 'eino_single';
|
||||||
@@ -370,6 +569,11 @@ document.addEventListener('click', function (e) {
|
|||||||
if (modePanel && modePanel.style.display !== 'none' && modeBtn && !modePanel.contains(e.target) && !modeBtn.contains(e.target)) {
|
if (modePanel && modePanel.style.display !== 'none' && modeBtn && !modePanel.contains(e.target) && !modeBtn.contains(e.target)) {
|
||||||
wsCloseAgentModePanel();
|
wsCloseAgentModePanel();
|
||||||
}
|
}
|
||||||
|
var projectPanel = document.getElementById('ws-project-panel');
|
||||||
|
var projectBtn = document.getElementById('ws-project-btn');
|
||||||
|
if (projectPanel && projectPanel.style.display !== 'none' && projectBtn && !projectPanel.contains(e.target) && !projectBtn.contains(e.target)) {
|
||||||
|
wsCloseProjectPanel();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── end WebShell AI 选择器 ───
|
// ─── end WebShell AI 选择器 ───
|
||||||
@@ -1873,6 +2077,7 @@ function webshellAiConvListSelect(conn, convId, messagesContainer, listEl) {
|
|||||||
apiFetch('/api/conversations/' + encodeURIComponent(convId) + '?include_process_details=1', { method: 'GET' })
|
apiFetch('/api/conversations/' + encodeURIComponent(convId) + '?include_process_details=1', { method: 'GET' })
|
||||||
.then(function (r) { return r.json(); })
|
.then(function (r) { return r.json(); })
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
|
wsSetWebshellAiProject(conn, data.projectId || data.project_id || '');
|
||||||
messagesContainer.innerHTML = '';
|
messagesContainer.innerHTML = '';
|
||||||
var list = data.messages || [];
|
var list = data.messages || [];
|
||||||
list.forEach(function (msg) {
|
list.forEach(function (msg) {
|
||||||
@@ -1893,9 +2098,14 @@ function webshellAiConvListSelect(conn, convId, messagesContainer, listEl) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
messagesContainer.appendChild(div);
|
messagesContainer.appendChild(div);
|
||||||
if (role === 'assistant' && msg.processDetails && msg.processDetails.length > 0) {
|
if (role === 'assistant') {
|
||||||
var block = renderWebshellProcessDetailsBlock(msg.processDetails, true);
|
var wsMergedDetails = (typeof window.mergeMessageReasoningContentIntoProcessDetails === 'function')
|
||||||
if (block) messagesContainer.appendChild(block);
|
? window.mergeMessageReasoningContentIntoProcessDetails(msg.processDetails || [], msg.reasoningContent)
|
||||||
|
: (msg.processDetails || []);
|
||||||
|
if (wsMergedDetails.length > 0) {
|
||||||
|
var block = renderWebshellProcessDetailsBlock(wsMergedDetails, true);
|
||||||
|
if (block) messagesContainer.appendChild(block);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (list.length === 0) {
|
if (list.length === 0) {
|
||||||
@@ -2003,6 +2213,25 @@ function selectWebshell(id, stateReady) {
|
|||||||
'<div id="webshell-ai-messages" class="webshell-ai-messages"></div>' +
|
'<div id="webshell-ai-messages" class="webshell-ai-messages"></div>' +
|
||||||
'<div class="webshell-ai-input-area">' +
|
'<div class="webshell-ai-input-area">' +
|
||||||
'<div class="webshell-ai-selectors-row">' +
|
'<div class="webshell-ai-selectors-row">' +
|
||||||
|
'<div class="ws-project-selector-wrapper project-selector-wrapper">' +
|
||||||
|
'<button type="button" id="ws-project-btn" class="role-selector-btn" onclick="wsToggleProjectPanel()" aria-label="' + escapeHtml(wsProjectT('projects.chatSelectorButton', '选择项目')) + '" aria-haspopup="listbox" aria-expanded="false" title="' + escapeHtml(wsProjectT('projects.chatSelectorButton', '绑定项目后共享事实黑板(跨对话)')) + '">' +
|
||||||
|
'<span class="role-selector-icon" aria-hidden="true">📁</span>' +
|
||||||
|
'<span id="ws-project-text" class="role-selector-text">' + escapeHtml(wsProjectT('projects.noProject', '无项目')) + '</span>' +
|
||||||
|
'<svg class="role-selector-arrow" width="10" height="10" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>' +
|
||||||
|
'</button>' +
|
||||||
|
'<div id="ws-project-panel" class="role-selection-panel chat-project-panel" style="display:none;" role="listbox">' +
|
||||||
|
'<div class="role-selection-panel-header">' +
|
||||||
|
'<h3 class="role-selection-panel-title">' + escapeHtml(wsProjectT('projects.selectProject', '选择项目')) + '</h3>' +
|
||||||
|
'<button type="button" class="role-selection-panel-close" onclick="wsCloseProjectPanel()" title="' + escapeHtml(wsProjectT('common.close', '关闭')) + '" aria-label="' + escapeHtml(wsProjectT('common.close', '关闭')) + '">' +
|
||||||
|
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="chat-project-panel-body">' +
|
||||||
|
'<div id="ws-project-list" class="role-selection-list-main"></div>' +
|
||||||
|
'<div class="chat-project-panel-footer">' +
|
||||||
|
'<button type="button" class="role-selection-item-main chat-project-panel-create-btn" onclick="showNewProjectModalFromWebshellAi()">' +
|
||||||
|
'<span class="chat-project-panel-create-icon" aria-hidden="true">+</span>' +
|
||||||
|
'<span class="chat-project-panel-create-label">' + escapeHtml(wsProjectT('projects.newProject', '新建项目')) + '</span>' +
|
||||||
|
'</button></div></div></div></div>' +
|
||||||
'<div class="ws-role-selector-wrapper">' +
|
'<div class="ws-role-selector-wrapper">' +
|
||||||
'<button type="button" class="role-selector-btn ws-role-selector-btn" id="ws-role-selector-btn" onclick="wsToggleRolePanel()">' +
|
'<button type="button" class="role-selector-btn ws-role-selector-btn" id="ws-role-selector-btn" onclick="wsToggleRolePanel()">' +
|
||||||
'<span id="ws-role-selector-icon" class="role-selector-icon">\ud83d\udd35</span>' +
|
'<span id="ws-role-selector-icon" class="role-selector-icon">\ud83d\udd35</span>' +
|
||||||
@@ -2174,9 +2403,11 @@ function selectWebshell(id, stateReady) {
|
|||||||
var aiNewConvBtn = document.getElementById('webshell-ai-new-conv');
|
var aiNewConvBtn = document.getElementById('webshell-ai-new-conv');
|
||||||
var aiConvListEl = document.getElementById('webshell-ai-conv-list');
|
var aiConvListEl = document.getElementById('webshell-ai-conv-list');
|
||||||
|
|
||||||
// 初始化角色 + 模式选择器
|
// 初始化角色 + 模式 + 项目选择器
|
||||||
wsLoadRoles();
|
wsLoadRoles();
|
||||||
wsInitAgentMode();
|
wsInitAgentMode();
|
||||||
|
if (typeof prefetchProjectsForChat === 'function') prefetchProjectsForChat();
|
||||||
|
wsUpdateProjectButtonLabel();
|
||||||
var aiMemoInput = document.getElementById('webshell-ai-memo-input');
|
var aiMemoInput = document.getElementById('webshell-ai-memo-input');
|
||||||
var aiMemoStatus = document.getElementById('webshell-ai-memo-status');
|
var aiMemoStatus = document.getElementById('webshell-ai-memo-status');
|
||||||
var aiMemoClearBtn = document.getElementById('webshell-ai-memo-clear');
|
var aiMemoClearBtn = document.getElementById('webshell-ai-memo-clear');
|
||||||
@@ -2225,6 +2456,8 @@ function selectWebshell(id, stateReady) {
|
|||||||
if (aiNewConvBtn) {
|
if (aiNewConvBtn) {
|
||||||
aiNewConvBtn.addEventListener('click', function () {
|
aiNewConvBtn.addEventListener('click', function () {
|
||||||
delete webshellAiConvMap[conn.id];
|
delete webshellAiConvMap[conn.id];
|
||||||
|
delete webshellAiDraftProjectByConn[conn.id];
|
||||||
|
wsUpdateProjectButtonLabel();
|
||||||
if (aiMessages) {
|
if (aiMessages) {
|
||||||
aiMessages.innerHTML = '';
|
aiMessages.innerHTML = '';
|
||||||
var readyMsg = wsT('webshell.aiSystemReadyMessage') || '系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。';
|
var readyMsg = wsT('webshell.aiSystemReadyMessage') || '系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。';
|
||||||
@@ -2767,7 +3000,15 @@ function loadWebshellAiHistory(conn, messagesContainer) {
|
|||||||
return apiFetch('/api/webshell/connections/' + encodeURIComponent(conn.id) + '/ai-history', { method: 'GET' })
|
return apiFetch('/api/webshell/connections/' + encodeURIComponent(conn.id) + '/ai-history', { method: 'GET' })
|
||||||
.then(function (r) { return r.json(); })
|
.then(function (r) { return r.json(); })
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
if (data.conversationId) webshellAiConvMap[conn.id] = data.conversationId;
|
if (data.conversationId) {
|
||||||
|
webshellAiConvMap[conn.id] = data.conversationId;
|
||||||
|
apiFetch('/api/conversations/' + encodeURIComponent(data.conversationId), { method: 'GET' })
|
||||||
|
.then(function (r) { return r.ok ? r.json() : null; })
|
||||||
|
.then(function (conv) {
|
||||||
|
if (conv) wsSetWebshellAiProject(conn, conv.projectId || conv.project_id || '');
|
||||||
|
})
|
||||||
|
.catch(function () { /* ignore */ });
|
||||||
|
}
|
||||||
var list = Array.isArray(data.messages) ? data.messages : [];
|
var list = Array.isArray(data.messages) ? data.messages : [];
|
||||||
list.forEach(function (msg) {
|
list.forEach(function (msg) {
|
||||||
var role = (msg.role || '').toLowerCase();
|
var role = (msg.role || '').toLowerCase();
|
||||||
@@ -2787,9 +3028,14 @@ function loadWebshellAiHistory(conn, messagesContainer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
messagesContainer.appendChild(div);
|
messagesContainer.appendChild(div);
|
||||||
if (role === 'assistant' && msg.processDetails && msg.processDetails.length > 0) {
|
if (role === 'assistant') {
|
||||||
var block = renderWebshellProcessDetailsBlock(msg.processDetails, true);
|
var wsHistMerged = (typeof window.mergeMessageReasoningContentIntoProcessDetails === 'function')
|
||||||
if (block) messagesContainer.appendChild(block);
|
? window.mergeMessageReasoningContentIntoProcessDetails(msg.processDetails || [], msg.reasoningContent)
|
||||||
|
: (msg.processDetails || []);
|
||||||
|
if (wsHistMerged.length > 0) {
|
||||||
|
var block = renderWebshellProcessDetailsBlock(wsHistMerged, true);
|
||||||
|
if (block) messagesContainer.appendChild(block);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (list.length === 0) {
|
if (list.length === 0) {
|
||||||
@@ -2922,6 +3168,10 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
|
|||||||
conversationId: convId,
|
conversationId: convId,
|
||||||
role: wsRole
|
role: wsRole
|
||||||
};
|
};
|
||||||
|
if (!convId) {
|
||||||
|
var wsPid = getWebshellAiProjectSelection(conn);
|
||||||
|
if (wsPid) body.projectId = wsPid;
|
||||||
|
}
|
||||||
|
|
||||||
// 流式输出:支持 progress 实时更新、response 打字机效果;若后端发送多段 response 则追加
|
// 流式输出:支持 progress 实时更新、response 打字机效果;若后端发送多段 response 则追加
|
||||||
var streamingTarget = ''; // 当前要打字显示的目标全文(用于打字机效果)
|
var streamingTarget = ''; // 当前要打字显示的目标全文(用于打字机效果)
|
||||||
@@ -2970,6 +3220,11 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
|
|||||||
|
|
||||||
if (_et === 'conversation' && _ed.conversationId) {
|
if (_et === 'conversation' && _ed.conversationId) {
|
||||||
var convId = _ed.conversationId;
|
var convId = _ed.conversationId;
|
||||||
|
var prevDraft = webshellAiDraftProjectByConn[conn.id];
|
||||||
|
if (prevDraft) {
|
||||||
|
webshellAiProjectByConvId[convId] = prevDraft;
|
||||||
|
delete webshellAiDraftProjectByConn[conn.id];
|
||||||
|
}
|
||||||
webshellAiConvMap[conn.id] = convId;
|
webshellAiConvMap[conn.id] = convId;
|
||||||
var listEl = document.getElementById('webshell-ai-conv-list');
|
var listEl = document.getElementById('webshell-ai-conv-list');
|
||||||
if (listEl) fetchAndRenderWebshellAiConvList(conn, listEl).then(function () {
|
if (listEl) fetchAndRenderWebshellAiConvList(conn, listEl).then(function () {
|
||||||
|
|||||||
Vendored
+32
File diff suppressed because one or more lines are too long
Vendored
+6696
File diff suppressed because one or more lines are too long
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+22
File diff suppressed because one or more lines are too long
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(self,(function(){return(()=>{"use strict";var e={775:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0;var r=function(){function e(){}return e.prototype.activate=function(e){this._terminal=e},e.prototype.dispose=function(){},e.prototype.fit=function(){var e=this.proposeDimensions();if(e&&this._terminal){var t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}},e.prototype.proposeDimensions=function(){if(this._terminal&&this._terminal.element&&this._terminal.element.parentElement){var e=this._terminal._core;if(0!==e._renderService.dimensions.actualCellWidth&&0!==e._renderService.dimensions.actualCellHeight){var t=window.getComputedStyle(this._terminal.element.parentElement),r=parseInt(t.getPropertyValue("height")),i=Math.max(0,parseInt(t.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),o=r-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=i-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-e.viewport.scrollBarWidth;return{cols:Math.max(2,Math.floor(a/e._renderService.dimensions.actualCellWidth)),rows:Math.max(1,Math.floor(o/e._renderService.dimensions.actualCellHeight))}}}},e}();t.FitAddon=r}},t={};return function r(i){if(t[i])return t[i].exports;var n=t[i]={exports:{}};return e[i](n,n.exports,r),n.exports}(775)})()}));
|
||||||
|
//# sourceMappingURL=xterm-addon-fit.js.map
|
||||||
Vendored
+190
@@ -0,0 +1,190 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
||||||
|
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||||
|
* https://github.com/chjj/term.js
|
||||||
|
* @license MIT
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* Originally forked from (with the author's permission):
|
||||||
|
* Fabrice Bellard's javascript vt100 for jslinux:
|
||||||
|
* http://bellard.org/jslinux/
|
||||||
|
* Copyright (c) 2011 Fabrice Bellard
|
||||||
|
* The original design remains. The terminal itself
|
||||||
|
* has been extended to include xterm CSI codes, among
|
||||||
|
* other features.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default styles for xterm.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
.xterm {
|
||||||
|
cursor: text;
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm.focus,
|
||||||
|
.xterm:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-helpers {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
/**
|
||||||
|
* The z-index of the helpers must be higher than the canvases in order for
|
||||||
|
* IMEs to appear on top.
|
||||||
|
*/
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-helper-textarea {
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
left: -9999em;
|
||||||
|
top: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
z-index: -5;
|
||||||
|
/** Prevent wrapping so the IME appears against the textarea at the correct position */
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .composition-view {
|
||||||
|
/* TODO: Composition position got messed up somewhere */
|
||||||
|
background: #000;
|
||||||
|
color: #FFF;
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .composition-view.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-viewport {
|
||||||
|
/* On OS X this is required in order for the scroll bar to appear fully opaque */
|
||||||
|
background-color: #000;
|
||||||
|
overflow-y: scroll;
|
||||||
|
cursor: default;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-screen {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-screen canvas {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-scroll-area {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-char-measure-element {
|
||||||
|
display: inline-block;
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -9999em;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm.enable-mouse-events {
|
||||||
|
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm.xterm-cursor-pointer,
|
||||||
|
.xterm .xterm-cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm.column-select.focus {
|
||||||
|
/* Column selection mode */
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .xterm-accessibility,
|
||||||
|
.xterm .xterm-message {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm .live-region {
|
||||||
|
position: absolute;
|
||||||
|
left: -9999px;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-dim {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-underline {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-strikethrough {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-screen .xterm-decoration-container .xterm-decoration {
|
||||||
|
z-index: 6;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-decoration-overview-ruler {
|
||||||
|
z-index: 7;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.xterm-decoration-top {
|
||||||
|
z-index: 2;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
Vendored
+2
File diff suppressed because one or more lines are too long
@@ -934,7 +934,7 @@ Content-Type: application/json
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/i18next@23.11.5/i18next.min.js"></script>
|
<script src="/static/vendor/i18next.min.js"></script>
|
||||||
<script src="/static/js/i18n.js"></script>
|
<script src="/static/js/i18n.js"></script>
|
||||||
<script src="/static/js/api-docs.js"></script>
|
<script src="/static/js/api-docs.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<link rel="shortcut icon" type="image/png" href="/static/favicon.ico">
|
<link rel="shortcut icon" type="image/png" href="/static/favicon.ico">
|
||||||
<link rel="stylesheet" href="/static/css/style.css">
|
<link rel="stylesheet" href="/static/css/style.css">
|
||||||
<link rel="stylesheet" href="/static/css/c2.css">
|
<link rel="stylesheet" href="/static/css/c2.css">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@4.19.0/css/xterm.css">
|
<link rel="stylesheet" href="/static/vendor/xterm.css">
|
||||||
<script src="/static/js/router.js"></script>
|
<script src="/static/js/router.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -3484,16 +3484,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Marked.js + DOMPurify:本地 vendor,避免 CDN 不可用导致 Markdown 降级为纯文本 -->
|
<!-- Marked.js + DOMPurify + 其他前端依赖:本地 vendor,内网/离线部署不依赖 CDN -->
|
||||||
<script src="/static/vendor/marked.min.js"></script>
|
<script src="/static/vendor/marked.min.js"></script>
|
||||||
<script src="/static/vendor/purify.min.js"></script>
|
<script src="/static/vendor/purify.min.js"></script>
|
||||||
<script src="/static/js/sanitize-markdown.js"></script>
|
<script src="/static/js/sanitize-markdown.js"></script>
|
||||||
<!-- Cytoscape.js for attack chain visualization -->
|
<!-- Cytoscape.js for attack chain visualization -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/cytoscape@3.27.0/dist/cytoscape.min.js"></script>
|
<script src="/static/vendor/cytoscape.min.js"></script>
|
||||||
<!-- ELK.js for high-quality DAG layout (reduces edge crossings) -->
|
<!-- ELK.js for high-quality DAG layout (reduces edge crossings) -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/elkjs@0.9.2/lib/elk.bundled.js"></script>
|
<script src="/static/vendor/elk.bundled.js"></script>
|
||||||
<!-- SheetJS for XLSX export (info-collect) -->
|
<!-- SheetJS for XLSX export (info-collect) -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
|
<script src="/static/vendor/xlsx.full.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// 确保ELK对象全局可用
|
// 确保ELK对象全局可用
|
||||||
if (typeof ELK === 'undefined' && typeof elk !== 'undefined') {
|
if (typeof ELK === 'undefined' && typeof elk !== 'undefined') {
|
||||||
@@ -4287,7 +4287,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/i18next@23.11.5/i18next.min.js"></script>
|
<script src="/static/vendor/i18next.min.js"></script>
|
||||||
<script src="/static/js/i18n.js"></script>
|
<script src="/static/js/i18n.js"></script>
|
||||||
<script src="/static/js/builtin-tools.js"></script>
|
<script src="/static/js/builtin-tools.js"></script>
|
||||||
<script src="/static/js/auth.js"></script>
|
<script src="/static/js/auth.js"></script>
|
||||||
@@ -4303,8 +4303,8 @@
|
|||||||
<script src="/static/js/settings.js"></script>
|
<script src="/static/js/settings.js"></script>
|
||||||
<script src="/static/js/audit.js"></script>
|
<script src="/static/js/audit.js"></script>
|
||||||
<script src="/static/js/wechat-robot.js"></script>
|
<script src="/static/js/wechat-robot.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/xterm@4.19.0/lib/xterm.js"></script>
|
<script src="/static/vendor/xterm.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.js"></script>
|
<script src="/static/vendor/xterm-addon-fit.js"></script>
|
||||||
<script src="/static/js/terminal.js"></script>
|
<script src="/static/js/terminal.js"></script>
|
||||||
<script src="/static/js/knowledge.js"></script>
|
<script src="/static/js/knowledge.js"></script>
|
||||||
<script src="/static/js/skills.js"></script>
|
<script src="/static/js/skills.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user