diff --git a/style.css b/style.css index 0260fb9..b7348e8 100644 --- a/style.css +++ b/style.css @@ -618,6 +618,910 @@ display: flex; } +/* ==================== Task-Mode (对称 config-mode) ==================== */ + +#st-bme-panel.task-mode .bme-tab-content { + display: none; +} + +#st-bme-panel.task-mode .bme-task-sidebar { + display: flex; +} + +#st-bme-panel.task-mode .bme-graph-workspace { + display: none; +} + +#st-bme-panel.task-mode .bme-task-workspace { + display: flex; +} + +.bme-task-sidebar { + display: none; + flex-direction: column; + gap: 2px; + padding: 12px 8px; + overflow-y: auto; +} + +.bme-task-sidebar-header { + padding: 8px 10px 12px; + margin-bottom: 4px; +} + +.bme-task-sidebar-kicker { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1.2px; + color: var(--bme-primary); + margin-bottom: 4px; +} + +.bme-task-sidebar-title { + font-size: 14px; + font-weight: 700; + color: var(--bme-on-surface); +} + +.bme-task-sidebar-help { + font-size: 11px; + color: var(--bme-on-surface-dim); + margin-top: 4px; + line-height: 1.4; +} + +.bme-task-nav-btn { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + border-radius: 8px; + border: none; + border-left: 3px solid transparent; + background: transparent; + color: var(--bme-on-surface-dim); + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; + white-space: nowrap; +} + +.bme-task-nav-btn:hover { + background: rgba(255, 255, 255, 0.04); + color: var(--bme-on-surface); +} + +.bme-task-nav-btn.active { + border-left-color: var(--bme-primary); + background: var(--bme-primary-dim); + color: var(--bme-primary); + font-weight: 600; +} + +.bme-task-nav-btn i { + width: 16px; + text-align: center; + font-size: 13px; +} + +.bme-task-nav-btn .bme-task-nav-badge { + margin-left: auto; + font-size: 10px; + padding: 2px 6px; + border-radius: 10px; + background: var(--bme-primary-dim); + color: var(--bme-primary); + font-weight: 600; +} + +.bme-task-workspace { + display: none; + flex: 1; + flex-direction: column; + min-height: 0; + overflow-y: auto; + background: + radial-gradient( + circle at top right, + var(--bme-primary-dim, rgba(233, 69, 96, 0.15)), + transparent 32% + ), + linear-gradient(180deg, rgba(255, 255, 255, 0.02), transparent 20%), + var(--bme-surface-lowest, #0e0e11); +} + +.bme-task-workspace-header { + padding: 20px 24px 14px; + border-bottom: 1px solid var(--bme-border); + flex-shrink: 0; +} + +.bme-task-workspace-kicker { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1.2px; + color: var(--bme-primary); + margin-bottom: 4px; +} + +.bme-task-workspace-title { + font-size: 20px; + font-weight: 700; + color: var(--bme-on-surface); + margin-bottom: 4px; +} + +.bme-task-workspace-desc { + font-size: 12px; + color: var(--bme-on-surface-dim); + line-height: 1.5; +} + +.bme-task-workspace-body { + padding: 16px 24px 32px; + flex: 1; + min-height: 0; + overflow-y: auto; +} + +.bme-task-section { + display: none; +} + +.bme-task-section.active { + display: block; +} + +/* --- Task nav mobile pill selector (visible only on mobile) --- */ +.bme-task-nav-mobile { + display: none; + gap: 6px; + padding: 8px 16px; + overflow-x: auto; + flex-shrink: 0; + border-bottom: 1px solid var(--bme-border); + -webkit-overflow-scrolling: touch; +} + +.bme-task-nav-mobile .bme-task-nav-pill { + display: flex; + align-items: center; + gap: 4px; + padding: 6px 12px; + border-radius: 20px; + border: 1px solid var(--bme-border); + background: transparent; + color: var(--bme-on-surface-dim); + font-size: 11px; + font-weight: 500; + cursor: pointer; + white-space: nowrap; + transition: all 0.15s ease; +} + +.bme-task-nav-mobile .bme-task-nav-pill:hover { + border-color: var(--bme-primary); + color: var(--bme-on-surface); +} + +.bme-task-nav-mobile .bme-task-nav-pill.active { + background: var(--bme-primary); + border-color: var(--bme-primary); + color: #fff; + font-weight: 600; +} + +/* ==================== Pipeline Overview ==================== */ + +.bme-pipeline-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-bottom: 16px; +} + +.bme-pipeline-card { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 12px 14px; + background: var(--bme-surface, #131316); + border: 1px solid var(--bme-border); + border-radius: 8px; + border-left: 3px solid var(--bme-border); + transition: border-color 0.2s; +} + +.bme-pipeline-card[data-status="idle"], +.bme-pipeline-card[data-status="ready"] { + border-left-color: #2ecc71; +} +.bme-pipeline-card[data-status="running"], +.bme-pipeline-card[data-status="building"] { + border-left-color: #00d4ff; +} +.bme-pipeline-card[data-status="queued"], +.bme-pipeline-card[data-status="warning"] { + border-left-color: #f39c12; +} +.bme-pipeline-card[data-status="error"], +.bme-pipeline-card[data-status="blocked"] { + border-left-color: #e74c3c; +} + +.bme-pipeline-dot { + width: 10px; + height: 10px; + border-radius: 50%; + margin-top: 3px; + flex-shrink: 0; +} + +.bme-pipeline-dot.green { background: #2ecc71; box-shadow: 0 0 6px rgba(46,204,113,.4); } +.bme-pipeline-dot.cyan { background: #00d4ff; box-shadow: 0 0 6px rgba(0,212,255,.4); animation: bme-pulse-dot 1.5s ease-in-out infinite; } +.bme-pipeline-dot.amber { background: #f39c12; box-shadow: 0 0 6px rgba(243,156,18,.4); } +.bme-pipeline-dot.red { background: #e74c3c; box-shadow: 0 0 6px rgba(231,76,60,.4); } + +@keyframes bme-pulse-dot { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +.bme-pipeline-info { + flex: 1; + min-width: 0; +} + +.bme-pipeline-name { + font-size: 12px; + font-weight: 700; + color: var(--bme-on-surface); + margin-bottom: 2px; +} + +.bme-pipeline-status { + font-size: 11px; + font-weight: 600; + margin-bottom: 2px; +} + +.bme-pipeline-status.green { color: #2ecc71; } +.bme-pipeline-status.cyan { color: #00d4ff; } +.bme-pipeline-status.amber { color: #f39c12; } +.bme-pipeline-status.red { color: #e74c3c; } + +.bme-pipeline-detail { + font-size: 10px; + color: var(--bme-on-surface-dim); +} + +/* --- Batch Progress --- */ + +.bme-batch-progress { + background: var(--bme-surface, #131316); + border: 1px solid var(--bme-border); + border-radius: 8px; + padding: 16px; + margin-bottom: 16px; +} + +.bme-batch-stages { + display: flex; + align-items: flex-start; + gap: 0; + margin-bottom: 12px; +} + +.bme-batch-stage { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + position: relative; +} + +.bme-batch-stage-dot { + width: 28px; + height: 28px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + z-index: 1; + border: 2px solid var(--bme-border); + background: var(--bme-surface-lowest, #0e0e11); + color: var(--bme-on-surface-dim); +} + +.bme-batch-stage-dot.done { + background: #2ecc71; + border-color: #2ecc71; + color: #fff; +} + +.bme-batch-stage-dot.running { + background: #00d4ff; + border-color: #00d4ff; + color: #fff; + animation: bme-pulse-dot 1.5s ease-in-out infinite; +} + +.bme-batch-stage-label { + font-size: 11px; + font-weight: 600; + color: var(--bme-on-surface-dim); + text-align: center; +} + +.bme-batch-stage-detail { + font-size: 10px; + color: var(--bme-on-surface-dim); + text-align: center; + max-width: 100px; + opacity: 0.7; +} + +.bme-batch-stage-line { + position: absolute; + top: 14px; + left: calc(50% + 18px); + width: calc(100% - 36px); + height: 2px; + background: var(--bme-border); + z-index: 0; +} + +.bme-batch-stage-line.done { background: #2ecc71; } +.bme-batch-stage-line.running { background: linear-gradient(90deg, #2ecc71, #00d4ff); } + +.bme-batch-stage:last-child .bme-batch-stage-line { display: none; } + +.bme-batch-meta { + display: flex; + gap: 16px; + font-size: 11px; + color: var(--bme-on-surface-dim); + padding-top: 10px; + border-top: 1px solid var(--bme-border); +} + +.bme-batch-meta i { + font-size: 10px; + color: var(--bme-primary); + opacity: 0.6; + margin-right: 4px; +} + +/* --- Status Summary --- */ + +.bme-status-summary { + background: var(--bme-surface, #131316); + border: 1px solid var(--bme-border); + border-radius: 8px; + padding: 12px 16px; +} + +.bme-status-summary-title { + font-size: 12px; + font-weight: 700; + color: var(--bme-on-surface); + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 6px; +} + +.bme-status-summary-title i { + color: var(--bme-primary); + font-size: 11px; +} + +.bme-status-row { + display: flex; + align-items: baseline; + gap: 10px; + padding: 6px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.03); +} + +.bme-status-row:last-child { border-bottom: none; } + +.bme-status-row-label { + font-size: 11px; + font-weight: 600; + width: 56px; + flex-shrink: 0; + display: flex; + align-items: center; + gap: 6px; + color: var(--bme-on-surface); +} + +.bme-status-row-label .bme-sdot { + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; +} + +.bme-status-row-value { + font-size: 11px; + color: var(--bme-on-surface-dim); + flex: 1; + min-width: 0; +} + +/* ==================== Task Timeline ==================== */ + +.bme-timeline-toolbar { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: var(--bme-surface, #131316); + border: 1px solid var(--bme-border); + border-radius: 8px; + margin-bottom: 12px; + flex-wrap: wrap; + position: sticky; + top: 0; + z-index: 5; +} + +.bme-timeline-filter { + padding: 4px 10px; + border-radius: 6px; + border: 1px solid var(--bme-border); + background: transparent; + color: var(--bme-on-surface-dim); + font-size: 11px; + cursor: pointer; +} + +.bme-timeline-filter:hover { + border-color: var(--bme-primary); + color: var(--bme-on-surface); +} + +.bme-timeline-search { + flex: 1; + min-width: 100px; + border: none; + background: transparent; + color: var(--bme-on-surface); + font-size: 12px; + outline: none; +} + +.bme-timeline-search::placeholder { + color: var(--bme-on-surface-dim); + opacity: 0.5; +} + +.bme-timeline-divider { + width: 1px; + height: 16px; + background: var(--bme-border); +} + +.bme-timeline-action { + padding: 4px 8px; + border-radius: 6px; + border: none; + background: transparent; + color: var(--bme-on-surface-dim); + cursor: pointer; + font-size: 11px; + display: flex; + align-items: center; + gap: 4px; +} + +.bme-timeline-action:hover { color: var(--bme-on-surface); } +.bme-timeline-action.active { color: var(--bme-primary); } + +.bme-timeline-stack { + display: flex; + flex-direction: column; + gap: 0; +} + +.bme-timeline-entry { + position: relative; + padding: 12px 14px; + background: var(--bme-surface, #131316); + border: 1px solid var(--bme-border); + border-radius: 8px; + margin-bottom: 2px; + transition: border-color 0.15s; +} + +.bme-timeline-entry:hover { + border-color: rgba(255, 255, 255, 0.1); +} + +.bme-timeline-entry__head { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; +} + +.bme-timeline-entry__title { + font-size: 12px; + font-weight: 600; + color: var(--bme-on-surface); + flex: 1; + min-width: 0; +} + +.bme-timeline-entry__meta { + font-size: 10px; + color: var(--bme-on-surface-dim); +} + +.bme-timeline-entry__toggle { + background: transparent; + border: none; + color: var(--bme-on-surface-dim); + cursor: pointer; + padding: 2px; + font-size: 11px; + transition: transform 0.2s; +} + +.bme-timeline-entry.is-collapsed .bme-timeline-entry__toggle { + transform: rotate(-90deg); +} + +.bme-timeline-entry__detail { + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid var(--bme-border); +} + +.bme-timeline-entry.is-collapsed .bme-timeline-entry__detail { + display: none; +} + +.bme-timeline-substage { + display: flex; + align-items: baseline; + gap: 8px; + padding: 4px 0; + font-size: 11px; + color: var(--bme-on-surface-dim); +} + +.bme-timeline-substage i { + font-size: 10px; + width: 14px; + text-align: center; +} + +.bme-timeline-connector { + width: 2px; + height: 6px; + background: var(--bme-border); + margin: 0 auto; +} + +.bme-timeline-bottom-bar { + padding: 8px 14px; + font-size: 11px; + color: var(--bme-on-surface-dim); + text-align: center; +} + +/* ==================== Memory Browser Master-Detail ==================== */ + +.bme-memory-master-detail { + display: flex; + height: 100%; + min-height: 400px; + gap: 0; +} + +.bme-memory-list-panel { + width: 40%; + min-width: 200px; + max-width: 360px; + display: flex; + flex-direction: column; + border-right: 1px solid var(--bme-border); + overflow: hidden; +} + +.bme-memory-list-filters { + padding: 10px; + display: flex; + flex-direction: column; + gap: 6px; + border-bottom: 1px solid var(--bme-border); + flex-shrink: 0; +} + +.bme-memory-list-scroll { + flex: 1; + overflow-y: auto; +} + +.bme-memory-node-item { + padding: 10px 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.03); + border-left: 3px solid transparent; + cursor: pointer; + transition: all 0.12s; +} + +.bme-memory-node-item:hover { + background: rgba(255, 255, 255, 0.03); +} + +.bme-memory-node-item.selected { + border-left-color: var(--bme-primary); + background: var(--bme-primary-dim); +} + +.bme-memory-node-item__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 4px; +} + +.bme-memory-node-item__type { + font-size: 10px; + font-weight: 700; + padding: 2px 6px; + border-radius: 4px; + text-transform: uppercase; +} + +.bme-memory-node-item__type.type-character { background: rgba(155,89,182,.2); color: #c084fc; } +.bme-memory-node-item__type.type-event { background: rgba(0,212,255,.15); color: #00d4ff; } +.bme-memory-node-item__type.type-location { background: rgba(46,204,113,.15); color: #2ecc71; } +.bme-memory-node-item__type.type-rule { background: rgba(243,156,18,.15); color: #f39c12; } +.bme-memory-node-item__type.type-thread { background: rgba(59,130,246,.15); color: #3b82f6; } +.bme-memory-node-item__type.type-default { background: rgba(255,255,255,.08); color: var(--bme-on-surface-dim); } + +.bme-memory-node-item__imp { + font-size: 10px; + font-family: monospace; + color: var(--bme-primary); +} + +.bme-memory-node-item__title { + font-size: 12px; + font-weight: 600; + color: var(--bme-on-surface); + margin-bottom: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.bme-memory-node-item__preview { + font-size: 11px; + color: var(--bme-on-surface-dim); + line-height: 1.4; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + margin-bottom: 4px; +} + +.bme-memory-node-item__meta { + display: flex; + gap: 8px; + font-size: 10px; + color: var(--bme-on-surface-dim); + opacity: 0.6; +} + +.bme-memory-detail-panel { + flex: 1; + overflow-y: auto; + padding: 20px; + min-width: 0; +} + +.bme-memory-detail-empty { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: var(--bme-on-surface-dim); + font-size: 12px; +} + +.bme-memory-detail__title { + font-size: 18px; + font-weight: 700; + color: var(--bme-on-surface); + margin-bottom: 8px; +} + +.bme-memory-detail__badges { + display: flex; + gap: 6px; + flex-wrap: wrap; + margin-bottom: 14px; +} + +.bme-memory-detail__desc { + font-size: 12px; + line-height: 1.6; + color: var(--bme-on-surface); + margin-bottom: 16px; + white-space: pre-wrap; +} + +.bme-memory-detail__fields { + margin-bottom: 16px; +} + +.bme-memory-detail__field-row { + display: flex; + gap: 8px; + padding: 4px 0; + font-size: 11px; + border-bottom: 1px solid rgba(255, 255, 255, 0.03); +} + +.bme-memory-detail__field-key { + color: var(--bme-on-surface-dim); + min-width: 80px; + font-weight: 600; +} + +.bme-memory-detail__field-val { + color: var(--bme-on-surface); +} + +.bme-memory-detail__stats { + display: flex; + gap: 14px; + flex-wrap: wrap; + font-size: 11px; + color: var(--bme-on-surface-dim); + margin-bottom: 16px; + padding: 10px 0; + border-top: 1px solid var(--bme-border); + border-bottom: 1px solid var(--bme-border); +} + +.bme-memory-detail__actions { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +/* ==================== Injection Preview ==================== */ + +.bme-injection-token-bar { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 14px; + background: var(--bme-surface, #131316); + border: 1px solid var(--bme-border); + border-radius: 8px; + margin-bottom: 12px; +} + +.bme-injection-token-bar__label { + font-size: 13px; + font-weight: 700; + color: var(--bme-on-surface); + white-space: nowrap; +} + +.bme-injection-token-bar__track { + flex: 1; + height: 6px; + border-radius: 3px; + background: var(--bme-border); + overflow: hidden; +} + +.bme-injection-token-bar__fill { + height: 100%; + border-radius: 3px; + background: var(--bme-primary); + transition: width 0.3s; +} + +.bme-injection-token-bar__breakdown { + font-size: 10px; + color: var(--bme-on-surface-dim); + white-space: nowrap; +} + +.bme-injection-card-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-bottom: 16px; +} + +.bme-injection-card { + background: var(--bme-surface, #131316); + border: 1px solid var(--bme-border); + border-radius: 8px; + padding: 14px; + border-top: 2px solid var(--bme-border); +} + +.bme-injection-card__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} + +.bme-injection-card__type { + font-size: 10px; + font-weight: 700; + padding: 2px 6px; + border-radius: 4px; +} + +.bme-injection-card__tokens { + font-size: 10px; + color: var(--bme-on-surface-dim); +} + +.bme-injection-card__body { + font-size: 11px; + line-height: 1.5; + color: var(--bme-on-surface-dim); + max-height: 120px; + overflow-y: auto; + white-space: pre-wrap; +} + +/* ==================== Persistence Status ==================== */ + +.bme-persist-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; + margin-bottom: 16px; +} + +.bme-persist-kv { + background: var(--bme-surface, #131316); + border: 1px solid var(--bme-border); + border-radius: 8px; + padding: 14px 16px; +} + +.bme-persist-kv__row { + display: flex; + justify-content: space-between; + align-items: baseline; + padding: 5px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.03); + font-size: 11px; +} + +.bme-persist-kv__row:last-child { border-bottom: none; } +.bme-persist-kv__row span { color: var(--bme-on-surface-dim); } +.bme-persist-kv__row strong { color: var(--bme-on-surface); font-weight: 600; } + +.bme-persist-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + .bme-graph-toolbar { display: flex; align-items: center; @@ -5985,3 +6889,52 @@ flex-direction: column; } } + +/* ═══════════════════════════════════════════════════════════ + Task Monitor Mobile Responsive + ═══════════════════════════════════════════════════════════ */ + +@media (max-width: 768px) { + #st-bme-panel.task-mode .bme-task-sidebar { + display: none; + } + .bme-task-nav-mobile { + display: flex; + } + .bme-task-workspace-header { + padding: 14px 16px 10px; + } + .bme-task-workspace-body { + padding: 12px 12px 24px; + } + .bme-pipeline-grid { + grid-template-columns: 1fr; + } + .bme-injection-card-grid { + grid-template-columns: 1fr; + } + .bme-persist-grid { + grid-template-columns: 1fr; + } + .bme-memory-master-detail { + flex-direction: column; + min-height: 0; + } + .bme-memory-list-panel { + width: 100%; + max-width: none; + border-right: none; + border-bottom: 1px solid var(--bme-border); + max-height: 40vh; + } + .bme-memory-detail-panel { + padding: 14px; + } + .bme-batch-stages { + flex-wrap: wrap; + gap: 4px; + } + .bme-batch-stage-line { + display: none; + } +} diff --git a/ui/panel.html b/ui/panel.html index ea83d8a..1ddb734 100644 --- a/ui/panel.html +++ b/ui/panel.html @@ -69,13 +69,9 @@ 总览 - - - 记忆 - - - - 注入 + + + 任务 @@ -128,14 +124,6 @@ 任务预设 - - - 消息追踪 - + + + 任务监控 + ST-BME 任务流 + + 左侧切换监控视图,右侧查看实时任务状态。 + + + + + + 管线总览 + + + + 任务流水 + + + + 记忆浏览 + + + + 注入预览 + + + + 消息追踪 + + + + 持久化 + + + + @@ -271,45 +319,6 @@ - - - - - - 全部 - 客观 - 角色 POV - 用户 POV - 主观记忆 - 事件 - 地点 - 线索 - 规则 - 全局概要(旧) - 反思 - - - - - - - - - - @@ -2638,20 +2647,6 @@ /> - - - 消息追踪 - 这一轮到底发了什么? - - 用更白话的方式展示最近一次注入主 AI 的内容,以及送去提取模型的实际请求。 - - - - - + + + + + 任务监控 + ST-BME 任务流工作区 + + 实时查看所有任务管线的运行状态与当前批次进度。 + + + + + + 管线 + 流水 + 记忆 + 注入 + 追踪 + 持久化 + + + + + + + + + + + 总览 - - - 记忆 - - - - 注入 + + + 任务 diff --git a/ui/panel.js b/ui/panel.js index 40987d5..779053c 100644 --- a/ui/panel.js +++ b/ui/panel.js @@ -322,6 +322,8 @@ let graphRenderer = null; let mobileGraphRenderer = null; let currentTabId = "dashboard"; let currentConfigSectionId = "toggles"; +let currentTaskSectionId = "pipeline"; +let currentSelectedMemoryNodeId = ""; let currentTaskProfileTaskType = "extract"; let currentTaskProfileTabId = "generation"; let currentTaskProfileBlockId = ""; @@ -893,11 +895,13 @@ export async function initPanel({ _bindActions(); _bindDashboardControls(); _bindConfigControls(); + _bindTaskNavigation(); _bindPlannerLauncher(); currentTabId = panelEl?.querySelector(".bme-tab-btn.active")?.dataset.tab || "dashboard"; _applyWorkspaceMode(); _syncConfigSectionState(); + _syncTaskSectionState(); _refreshRuntimeStatus(); _initFloatingBall(); _bindFabToggle(); @@ -1159,11 +1163,8 @@ export function refreshLiveState() { case "dashboard": _refreshDashboard(); break; - case "memory": - _refreshMemoryBrowser(); - break; - case "injection": - void _refreshInjectionPreview(); + case "task": + _refreshTaskMonitor(); break; default: break; @@ -1176,9 +1177,6 @@ export function refreshLiveState() { ) { _refreshTaskProfileWorkspace(); } - if (currentTabId === "config" && currentConfigSectionId === "trace") { - _refreshMessageTraceWorkspace(); - } _scheduleVisibleGraphWorkspaceRefresh(); } @@ -1217,11 +1215,8 @@ function _switchTab(tabId) { case "dashboard": _refreshDashboard(); break; - case "memory": - _refreshMemoryBrowser(); - break; - case "injection": - void _refreshInjectionPreview(); + case "task": + _refreshTaskMonitor(); break; case "config": _refreshConfigTab(); @@ -1281,7 +1276,501 @@ function _bindPlannerLauncher() { function _applyWorkspaceMode() { if (!panelEl) return; const isConfig = currentTabId === "config"; + const isTask = currentTabId === "task"; panelEl.classList.toggle("config-mode", isConfig); + panelEl.classList.toggle("task-mode", isTask); +} + +// ==================== 任务监控工作区 ==================== + +const TASK_SECTION_META = { + pipeline: { kicker: "管线总览", title: "管线总览", desc: "实时查看所有任务管线的运行状态与当前批次进度。" }, + timeline: { kicker: "任务流水", title: "任务流水", desc: "按时间轴追踪每次提取、召回、向量索引等任务的执行记录。" }, + memory: { kicker: "记忆浏览", title: "记忆浏览", desc: "浏览和检索图谱中的所有记忆节点。" }, + injection: { kicker: "注入预览", title: "注入预览", desc: "查看最近一次注入到主 AI 的内容预览与 token 用量。" }, + trace: { kicker: "消息追踪", title: "消息追踪", desc: "这一轮到底发了什么?查看召回注入快照和提取请求详情。" }, + persistence: { kicker: "持久化", title: "持久化状态", desc: "图谱加载状态、存储层级、commit marker 与修复操作。" }, +}; + +function _bindTaskNavigation() { + panelEl?.querySelectorAll(".bme-task-nav-btn").forEach((btn) => { + btn.addEventListener("click", () => { + _switchTaskSection(btn.dataset.taskSection); + }); + }); + panelEl?.querySelectorAll(".bme-task-nav-pill").forEach((btn) => { + btn.addEventListener("click", () => { + _switchTaskSection(btn.dataset.taskSection); + }); + }); +} + +function _switchTaskSection(sectionId) { + currentTaskSectionId = sectionId || "pipeline"; + _syncTaskSectionState(); + _refreshTaskMonitor(); +} + +function _syncTaskSectionState() { + panelEl?.querySelectorAll(".bme-task-nav-btn").forEach((btn) => { + btn.classList.toggle("active", btn.dataset.taskSection === currentTaskSectionId); + }); + panelEl?.querySelectorAll(".bme-task-nav-pill").forEach((btn) => { + btn.classList.toggle("active", btn.dataset.taskSection === currentTaskSectionId); + }); + panelEl?.querySelectorAll(".bme-task-section").forEach((section) => { + section.classList.toggle("active", section.dataset.taskSection === currentTaskSectionId); + }); + const meta = TASK_SECTION_META[currentTaskSectionId] || TASK_SECTION_META.pipeline; + const kicker = document.getElementById("bme-task-ws-kicker"); + const title = document.getElementById("bme-task-ws-title"); + const desc = document.getElementById("bme-task-ws-desc"); + if (kicker) kicker.textContent = meta.kicker; + if (title) title.textContent = meta.title; + if (desc) desc.textContent = meta.desc; +} + +function _refreshTaskMonitor() { + switch (currentTaskSectionId) { + case "pipeline": + _refreshTaskPipelineOverview(); + break; + case "timeline": + _refreshTaskTimeline(); + break; + case "memory": + _refreshTaskMemoryBrowser(); + break; + case "injection": + _refreshTaskInjectionPreview(); + break; + case "trace": + _refreshTaskMessageTrace(); + break; + case "persistence": + _refreshTaskPersistence(); + break; + } +} + +// ---------- Pipeline Overview ---------- + +function _resolvePipelineStatus(statusObj) { + if (!statusObj) return { label: "UNKNOWN", color: "amber", detail: "—" }; + const text = String(statusObj.text || ""); + const meta = String(statusObj.meta || ""); + const level = String(statusObj.level || "info"); + let color = "green"; + if (level === "warn") color = "amber"; + else if (level === "error") color = "red"; + else if (text.toLowerCase().includes("running") || text.toLowerCase().includes("进行中") || text.includes("正在")) color = "cyan"; + return { label: text || "IDLE", color, detail: meta }; +} + +function _refreshTaskPipelineOverview() { + const el = document.getElementById("bme-task-pipeline"); + if (!el) return; + + const debug = _getRuntimeDebugSnapshot?.() || {}; + const rd = debug.runtimeDebug || {}; + const graph = _getGraph?.() || {}; + const historyState = graph.runtimeState?.historyState || {}; + const graphPersistenceState = graph.graphPersistenceState || {}; + + const extraction = _resolvePipelineStatus(rd.lastExtractionStatus); + const vector = _resolvePipelineStatus(rd.lastVectorStatus); + const recall = _resolvePipelineStatus(rd.lastRecallStatus); + const persistLevel = graphPersistenceState.loadState === "loaded" ? "info" : graphPersistenceState.loadState === "loading" ? "info" : "warn"; + const persistence = _resolvePipelineStatus({ + text: graphPersistenceState.loadState || "unknown", + meta: `rev ${graphPersistenceState.lastAcceptedRevision || 0}`, + level: persistLevel, + }); + + const batchStatus = historyState.lastBatchStatus || {}; + const stages = [ + { key: "core", label: "Core" }, + { key: "structural", label: "结构" }, + { key: "semantic", label: "语义" }, + { key: "finalize", label: "定稿" }, + ]; + + const stageHtml = stages.map((s, i) => { + const outcome = batchStatus.stageOutcomes?.[s.key]; + let dotClass = ""; + let lineClass = ""; + let icon = ''; + if (outcome === "success" || outcome === "skipped") { + dotClass = "done"; + icon = ''; + lineClass = "done"; + } else if (outcome === "running" || outcome === "partial") { + dotClass = "running"; + icon = ''; + lineClass = "running"; + } + const linePart = i < stages.length - 1 ? `` : ""; + return ` + + ${icon} + ${_escHtml(s.label)} + ${outcome ? _escHtml(outcome) : "pending"} + ${linePart} + + `; + }).join(""); + + const batchMeta = batchStatus.persistenceOutcome + ? ` ${_escHtml(batchStatus.persistenceOutcome)}` + : ""; + const batchWarnings = (batchStatus.warnings || []).length; + const batchErrors = (batchStatus.errors || []).length; + const batchMetaExtra = [ + batchWarnings ? ` ${batchWarnings} warnings` : "", + batchErrors ? ` ${batchErrors} errors` : "", + ].filter(Boolean).join(""); + + const statusRows = [ + { label: "提取", color: extraction.color, value: extraction.label + (extraction.detail ? ` — ${extraction.detail}` : "") }, + { label: "向量", color: vector.color, value: vector.label + (vector.detail ? ` — ${vector.detail}` : "") }, + { label: "召回", color: recall.color, value: recall.label + (recall.detail ? ` — ${recall.detail}` : "") }, + { label: "持久化", color: persistence.color, value: persistence.label + (persistence.detail ? ` — ${persistence.detail}` : "") }, + ]; + + const pipelineCard = (name, s, icon) => ` + + + + ${_escHtml(name)} + ${_escHtml(s.label)} + ${_escHtml(s.detail)} + + `; + + el.innerHTML = ` + + ${pipelineCard("提取 Extraction", extraction, "scissors")} + ${pipelineCard("向量 Vector", vector, "share-nodes")} + ${pipelineCard("召回 Recall", recall, "magnifying-glass")} + ${pipelineCard("持久化 Persistence", persistence, "database")} + + + + Active Batch Progress + ID: ${_escHtml(String(batchStatus.batchId || "—"))} + + ${stageHtml} + ${batchMeta}${batchMetaExtra} + + + Recent Status + ${statusRows.map((r) => ` + + ${_escHtml(r.label)} + ${_escHtml(r.value)} + + `).join("")} + + `; +} + +// ---------- Task Timeline ---------- + +function _refreshTaskTimeline() { + const el = document.getElementById("bme-task-timeline"); + if (!el) return; + + const debug = _getRuntimeDebugSnapshot?.() || {}; + const rd = debug.runtimeDebug || {}; + const timeline = Array.isArray(rd.taskTimeline) ? rd.taskTimeline : []; + + if (!timeline.length) { + el.innerHTML = '暂无任务记录'; + return; + } + + const entries = timeline.slice().reverse().map((entry, idx) => { + const t = entry.updatedAt ? new Date(entry.updatedAt).toLocaleTimeString() : ""; + const title = entry.taskType || entry.stage || "task"; + const statusText = entry.status || ""; + const durationMs = entry.durationMs; + const durationStr = typeof durationMs === "number" ? `${(durationMs / 1000).toFixed(1)}s` : ""; + const detail = entry.text || entry.meta || ""; + const level = entry.level || "info"; + const levelIcon = level === "error" ? "circle-exclamation" : level === "warn" ? "triangle-exclamation" : "circle-check"; + const levelColor = level === "error" ? "#e74c3c" : level === "warn" ? "#f39c12" : "#2ecc71"; + + const substages = Array.isArray(entry.substages) ? entry.substages.map((sub) => ` + + + ${_escHtml(sub.label || sub.stage || "")} + ${_escHtml(sub.outcome || sub.status || "")} + + `).join("") : ""; + + return ` + + + + ${_escHtml(title)}${statusText ? ` — ${_escHtml(statusText)}` : ""} + ${durationStr} ${t} + + + + ${detail ? `${_escHtml(detail)}` : ""} + ${substages} + + + `; + }).join(""); + + el.innerHTML = ` + + + ${timeline.length} 条记录 + + ${entries} + `; +} + +// ---------- Memory Browser (Master-Detail) ---------- + +function _refreshTaskMemoryBrowser() { + const el = document.getElementById("bme-task-memory"); + if (!el) return; + + const graph = _getGraph?.(); + if (!graph) { + el.innerHTML = '图谱未加载'; + return; + } + + const nodes = graph.nodes || []; + const sorted = nodes.slice().sort((a, b) => (b.importance || 0) - (a.importance || 0)); + + const typeClass = (type) => { + switch (type) { + case "pov_memory": case "character": return "type-character"; + case "event": return "type-event"; + case "location": return "type-location"; + case "rule": return "type-rule"; + case "thread": return "type-thread"; + default: return "type-default"; + } + }; + + const typeLabel = (node) => { + if (node.scope === "characterPov") return "角色POV"; + if (node.scope === "userPov") return "用户POV"; + return node.type || "memory"; + }; + + const listItems = sorted.map((node) => { + const sel = node.id === currentSelectedMemoryNodeId ? "selected" : ""; + const preview = (node.description || "").slice(0, 80); + return ` + + + ${_escHtml(typeLabel(node))} + IMP: ${typeof node.importance === "number" ? node.importance.toFixed(1) : "—"} + + ${_escHtml(node.label || node.id)} + ${_escHtml(preview)} + + SEQ: ${node.lastSeq ?? "—"} + ${node.region ? `LOC: ${_escHtml(node.region)}` : ""} + + `; + }).join(""); + + const detailHtml = _renderMemoryDetailPanel(sorted.find((n) => n.id === currentSelectedMemoryNodeId) || null, graph); + + el.innerHTML = ` + + + + + + 全部 + 客观 + 角色 POV + 用户 POV + 主观记忆 + 事件 + 地点 + 线索 + 规则 + + + + ${listItems || '无节点'} + + + + ${detailHtml} + + + `; + + _bindTaskMemoryListClick(); +} + +function _bindTaskMemoryListClick() { + const list = document.getElementById("bme-task-memory-list"); + if (!list) return; + list.addEventListener("click", (e) => { + const item = e.target.closest(".bme-memory-node-item"); + if (!item) return; + currentSelectedMemoryNodeId = item.dataset.nodeId || ""; + list.querySelectorAll(".bme-memory-node-item").forEach((n) => n.classList.toggle("selected", n.dataset.nodeId === currentSelectedMemoryNodeId)); + const graph = _getGraph?.(); + const node = (graph?.nodes || []).find((n) => n.id === currentSelectedMemoryNodeId) || null; + const detailEl = document.getElementById("bme-task-memory-detail"); + if (detailEl) detailEl.innerHTML = _renderMemoryDetailPanel(node, graph); + }); +} + +function _renderMemoryDetailPanel(node, graph) { + if (!node) return '选择左侧节点查看详情'; + + const edges = (graph?.edges || []).filter((e) => e.source === node.id || e.target === node.id); + const badges = [ + node.type ? `${_escHtml(node.type)}` : "", + node.scope ? `${_escHtml(node.scope)}` : "", + node.archived ? 'ARCHIVED' : "", + ].filter(Boolean).join(""); + + const fields = [ + ["ID", node.id], + ["Owner", node.owner || "—"], + ["Region", node.region || "—"], + ["Importance", typeof node.importance === "number" ? node.importance.toFixed(2) : "—"], + ["Last Seq", node.lastSeq ?? "—"], + ["Created", node.createdAt || "—"], + ["Updated", node.updatedAt || "—"], + ]; + + return ` + ${_escHtml(node.label || node.id)} + ${badges} + ${_escHtml(node.description || "无描述")} + + ${fields.map(([k, v]) => `${_escHtml(k)}${_escHtml(String(v))}`).join("")} + + + ${edges.length} 条连接 + recall ${node.recallCount ?? 0} + + `; +} + +// ---------- Injection Preview ---------- + +function _refreshTaskInjectionPreview() { + const el = document.getElementById("bme-task-injection"); + if (!el) return; + + const debug = _getRuntimeDebugSnapshot?.() || {}; + const rd = debug.runtimeDebug || {}; + const injection = rd?.injections?.recall || null; + + if (!injection) { + el.innerHTML = '暂无注入数据'; + return; + } + + const totalTokens = injection.tokenCount || 0; + const budgetTokens = injection.budgetTokens || totalTokens || 1; + const pct = Math.min(100, Math.round((totalTokens / budgetTokens) * 100)); + + const sections = Array.isArray(injection.sections) ? injection.sections : []; + const injectionText = injection.text || injection.injectionText || ""; + + const cardsHtml = sections.length + ? sections.map((s) => ` + + + ${_escHtml(s.title || s.type || "section")} + ${typeof s.tokenCount === "number" ? `${s.tokenCount} tok` : ""} + + ${_escHtml(s.text || s.content || "")} + + `).join("") + : ` + + Full Injection + ${totalTokens} tok + + ${_escHtml(injectionText.slice(0, 2000))}${injectionText.length > 2000 ? "…" : ""} + `; + + el.innerHTML = ` + + ${totalTokens} / ${budgetTokens} tok + + + + ${pct}% + + ${cardsHtml} + `; +} + +// ---------- Message Trace ---------- + +function _refreshTaskMessageTrace() { + const el = document.getElementById("bme-task-trace"); + if (!el) return; + + const settings = _getSettings?.() || {}; + const state = _getMessageTraceWorkspaceState(settings); + el.innerHTML = _renderMessageTraceWorkspace(state); +} + +// ---------- Persistence Status ---------- + +function _refreshTaskPersistence() { + const el = document.getElementById("bme-task-persistence"); + if (!el) return; + + const graph = _getGraph?.() || {}; + const ps = graph.graphPersistenceState || {}; + const rs = graph.runtimeState || {}; + + const kvs = [ + ["Load State", ps.loadState || "unknown"], + ["Storage Tier", ps.acceptedStorageTier || "—"], + ["Last Accepted Rev", ps.lastAcceptedRevision ?? "—"], + ["Commit Marker", ps.currentCommitMarker ? "present" : "none"], + ["Blocked Reason", ps.blockedReason || "—"], + ["IDB Snapshot Rev", ps.indexedDbSnapshotRevision ?? "—"], + ["Chat State Rev", ps.chatStateSnapshotRevision ?? "—"], + ["Shadow Snapshot", ps.hasShadowSnapshot ? "yes" : "no"], + ]; + + const kvHtml = kvs.map(([k, v]) => `${_escHtml(k)}${_escHtml(String(v))}`).join(""); + + const journalCount = Array.isArray(rs.historyState?.batchJournal) ? rs.historyState.batchJournal.length : 0; + const secondaryKvs = [ + ["Graph Nodes", String((graph.nodes || []).length)], + ["Graph Edges", String((graph.edges || []).length)], + ["Batch Journal", String(journalCount)], + ["Runtime Rev", String(rs.graphRevision ?? "—")], + ]; + const secondaryHtml = secondaryKvs.map(([k, v]) => `${_escHtml(k)}${_escHtml(v)}`).join(""); + + el.innerHTML = ` + + + Persistence State + ${kvHtml} + + + Runtime Stats + ${secondaryHtml} + + + `; } // ==================== 图谱视图切换 ==================== @@ -4025,19 +4514,8 @@ function _bindActions() { if (!result?.skipDashboardRefresh) { _refreshDashboard(); _refreshGraph(); - if ( - document - .getElementById("bme-pane-memory") - ?.classList.contains("active") - ) { - _refreshMemoryBrowser(); - } - if ( - document - .getElementById("bme-pane-injection") - ?.classList.contains("active") - ) { - await _refreshInjectionPreview(); + if (currentTabId === "task") { + _refreshTaskMonitor(); } } if (!result?.handledToast) { @@ -4117,13 +4595,7 @@ function _bindActions() { }); _refreshDashboard(); _refreshGraph(); - if ( - document - .getElementById("bme-pane-memory") - ?.classList.contains("active") - ) { - _refreshMemoryBrowser(); - } + if (currentTabId === "task") _refreshTaskMonitor(); } catch (error) { console.error("[ST-BME] Action extractTask failed:", error); toastr.error(`重新提取失败: ${error?.message || error}`, "ST-BME"); @@ -4204,13 +4676,7 @@ function _bindActions() { }); _refreshDashboard(); _refreshGraph(); - if ( - document - .getElementById("bme-pane-memory") - ?.classList.contains("active") - ) { - _refreshMemoryBrowser(); - } + if (currentTabId === "task") _refreshTaskMonitor(); } catch (error) { console.error("[ST-BME] Action rebuildSummaryState failed:", error); toastr.error(`重建总结状态失败: ${error?.message || error}`, "ST-BME"); @@ -4248,13 +4714,7 @@ function _bindActions() { ); _refreshDashboard(); _refreshGraph(); - if ( - document - .getElementById("bme-pane-memory") - ?.classList.contains("active") - ) { - _refreshMemoryBrowser(); - } + if (currentTabId === "task") _refreshTaskMonitor(); } catch (error) { console.error("[ST-BME] Action clearGraphRange failed:", error); toastr.error(`按楼层范围清理失败: ${error?.message || error}`, "ST-BME"); @@ -4410,8 +4870,7 @@ function _bindActions() { _refreshDashboard(); _refreshGraph(); _refreshSummaryWorkspace(); - _refreshMemoryBrowser(); - void _refreshInjectionPreview(); + if (currentTabId === "task") _refreshTaskMonitor(); } catch (error) { console.error(`[ST-BME] summary workspace action failed: ${actionKey}`, error); toastr.error(String(error?.message || error || "操作失败"), "ST-BME");
- 用更白话的方式展示最近一次注入主 AI 的内容,以及送去提取模型的实际请求。 -
+ 实时查看所有任务管线的运行状态与当前批次进度。 +