Files
insightflow/frontend/workbench.html
OpenClaw Bot 9dd54b3a38 Phase 5: 知识推理与问答增强
- 新增 knowledge_reasoner.py 推理引擎
- 支持因果/对比/时序/关联四种推理类型
- 智能项目总结 API (全面/高管/技术/风险)
- 实体关联路径发现功能
- 前端推理面板 UI 和交互
- 更新 API 端点和健康检查

Refs: Phase 5 开发任务
2026-02-19 18:07:00 +08:00

1709 lines
56 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>InsightFlow - 知识工作台 (Phase 3)</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0a0a0a;
color: #e0e0e0;
height: 100vh;
overflow: hidden;
}
.header {
height: 50px;
background: #111;
border-bottom: 1px solid #222;
display: flex;
align-items: center;
padding: 0 20px;
justify-content: space-between;
}
.header h1 {
font-size: 1.2rem;
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.header-left {
display: flex;
align-items: center;
gap: 20px;
}
.back-link {
color: #666;
text-decoration: none;
font-size: 0.9rem;
}
.back-link:hover {
color: #00d4ff;
}
.project-name {
color: #888;
font-size: 0.9rem;
}
.header-actions {
display: flex;
gap: 10px;
}
.main {
display: flex;
height: calc(100vh - 50px);
}
.sidebar {
width: 60px;
background: #111;
border-right: 1px solid #222;
display: flex;
flex-direction: column;
align-items: center;
padding: 10px 0;
}
.sidebar-btn {
width: 44px;
height: 44px;
background: transparent;
border: none;
color: #666;
font-size: 1.2rem;
cursor: pointer;
border-radius: 8px;
margin-bottom: 8px;
transition: all 0.2s;
}
.sidebar-btn:hover, .sidebar-btn.active {
background: #1a1a1a;
color: #00d4ff;
}
.content-area {
flex: 1;
display: flex;
overflow: hidden;
}
.editor-panel {
width: 50%;
border-right: 1px solid #222;
display: flex;
flex-direction: column;
}
.panel-header {
padding: 12px 20px;
background: #141414;
border-bottom: 1px solid #222;
font-size: 0.9rem;
color: #888;
display: flex;
justify-content: space-between;
align-items: center;
}
.panel-actions {
display: flex;
gap: 8px;
}
.btn-icon {
background: transparent;
border: 1px solid #333;
color: #888;
padding: 4px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 0.8rem;
}
.btn-icon:hover {
border-color: #00d4ff;
color: #00d4ff;
}
.transcript-content {
flex: 1;
padding: 20px;
overflow-y: auto;
line-height: 1.8;
font-size: 1rem;
}
.segment {
margin-bottom: 16px;
padding: 12px;
background: #141414;
border-radius: 8px;
cursor: pointer;
transition: background 0.2s;
}
.segment:hover {
background: #1a1a1a;
}
.speaker {
color: #00d4ff;
font-weight: 600;
font-size: 0.85rem;
margin-bottom: 4px;
}
.segment-text {
color: #e0e0e0;
outline: none;
}
.segment-text[contenteditable="true"] {
background: #1a1a1a;
padding: 8px;
border-radius: 4px;
border: 1px solid #00d4ff;
}
.entity {
background: rgba(123, 44, 191, 0.3);
border-bottom: 2px solid #7b2cbf;
padding: 0 4px;
border-radius: 3px;
cursor: pointer;
position: relative;
}
.entity:hover {
background: rgba(123, 44, 191, 0.5);
}
.entity.selected {
background: #ff6b6b;
border-color: #ff6b6b;
color: #fff;
}
.graph-panel {
width: 50%;
display: flex;
flex-direction: column;
}
#graph-svg {
flex: 1;
background: #0a0a0a;
}
.entity-list {
height: 200px;
border-top: 1px solid #222;
background: #111;
padding: 16px;
overflow-y: auto;
}
.entity-item {
display: flex;
align-items: center;
padding: 8px 12px;
background: #1a1a1a;
border-radius: 6px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.2s;
}
.entity-item:hover {
background: #222;
}
.entity-item.selected {
background: #2a2a2a;
border-left: 3px solid #ff6b6b;
}
.entity-type-badge {
padding: 2px 8px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 600;
margin-right: 12px;
text-transform: uppercase;
}
.type-PROJECT { background: #7b2cbf; }
.type-TECH { background: #00d4ff; color: #000; }
.type-PERSON { background: #ff6b6b; }
.type-ORG { background: #4ecdc4; color: #000; }
.type-OTHER { background: #666; }
.upload-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.95);
display: none;
align-items: center;
justify-content: center;
z-index: 2000;
}
.upload-overlay.show {
display: flex;
}
.upload-box {
border: 2px dashed #333;
border-radius: 16px;
padding: 60px;
text-align: center;
max-width: 500px;
}
.upload-box:hover {
border-color: #00d4ff;
}
.upload-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
justify-content: center;
}
.upload-tab {
padding: 8px 16px;
background: #1a1a1a;
border: 1px solid #333;
border-radius: 6px;
cursor: pointer;
color: #888;
}
.upload-tab.active {
border-color: #00d4ff;
color: #00d4ff;
}
.btn {
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
color: white;
border: none;
padding: 12px 32px;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
margin-top: 20px;
}
.btn:hover {
opacity: 0.9;
}
.btn-small {
padding: 8px 16px;
font-size: 0.85rem;
margin-top: 0;
}
.btn-danger {
background: #ff6b6b;
}
.btn-secondary {
background: #333;
}
.empty-state {
text-align: center;
padding: 60px 20px;
}
/* Phase 2: Entity Editor Modal */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.8);
display: none;
align-items: center;
justify-content: center;
z-index: 3000;
}
.modal-overlay.show {
display: flex;
}
.modal {
background: #1a1a1a;
border-radius: 12px;
padding: 24px;
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
font-size: 1.2rem;
margin-bottom: 20px;
color: #fff;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 6px;
color: #888;
font-size: 0.85rem;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 10px 12px;
background: #0a0a0a;
border: 1px solid #333;
border-radius: 6px;
color: #e0e0e0;
font-size: 0.95rem;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #00d4ff;
}
.form-group textarea {
min-height: 80px;
resize: vertical;
}
.modal-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 20px;
}
/* Phase 2: Context Menu */
.context-menu {
position: absolute;
background: #1a1a1a;
border: 1px solid #333;
border-radius: 6px;
padding: 6px 0;
z-index: 4000;
display: none;
min-width: 160px;
}
.context-menu.show {
display: block;
}
.context-menu-item {
padding: 8px 16px;
cursor: pointer;
font-size: 0.9rem;
color: #e0e0e0;
}
.context-menu-item:hover {
background: #2a2a2a;
}
.context-menu-divider {
height: 1px;
background: #333;
margin: 6px 0;
}
/* Phase 2: Relation Editor */
.relation-editor {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #333;
}
.relation-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
background: #0a0a0a;
border-radius: 4px;
margin-bottom: 8px;
font-size: 0.85rem;
}
.relation-item button {
background: transparent;
border: none;
color: #ff6b6b;
cursor: pointer;
font-size: 0.8rem;
}
/* Phase 2: Selection toolbar */
.selection-toolbar {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: #1a1a1a;
border: 1px solid #333;
border-radius: 8px;
padding: 10px 20px;
display: none;
gap: 10px;
z-index: 3500;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
.selection-toolbar.show {
display: flex;
}
/* Graph node styles */
.node-circle {
cursor: pointer;
}
.node-label {
pointer-events: none;
}
/* Phase 3: Knowledge Base Panel */
.kb-panel {
width: 100%;
height: 100%;
display: none;
flex-direction: column;
background: #0a0a0a;
}
.kb-panel.show {
display: flex;
}
.kb-header {
padding: 16px 20px;
background: #141414;
border-bottom: 1px solid #222;
display: flex;
justify-content: space-between;
align-items: center;
}
.kb-stats {
display: flex;
gap: 24px;
}
.kb-stat {
text-align: center;
}
.kb-stat-value {
font-size: 1.5rem;
font-weight: 600;
color: #00d4ff;
}
.kb-stat-label {
font-size: 0.75rem;
color: #666;
}
.kb-content {
flex: 1;
display: flex;
overflow: hidden;
}
.kb-sidebar {
width: 200px;
background: #111;
border-right: 1px solid #222;
padding: 16px 0;
}
.kb-nav-item {
padding: 12px 20px;
cursor: pointer;
color: #888;
border-left: 3px solid transparent;
}
.kb-nav-item:hover {
background: #1a1a1a;
color: #e0e0e0;
}
.kb-nav-item.active {
background: #1a1a1a;
color: #00d4ff;
border-left-color: #00d4ff;
}
.kb-main {
flex: 1;
padding: 20px;
overflow-y: auto;
}
.kb-section {
display: none;
}
.kb-section.active {
display: block;
}
.kb-entity-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.kb-entity-card {
background: #141414;
border: 1px solid #222;
border-radius: 8px;
padding: 16px;
cursor: pointer;
transition: all 0.2s;
}
.kb-entity-card:hover {
border-color: #00d4ff;
}
.kb-entity-name {
font-weight: 600;
margin-bottom: 4px;
}
.kb-entity-def {
font-size: 0.85rem;
color: #888;
margin-bottom: 8px;
}
.kb-entity-meta {
font-size: 0.75rem;
color: #666;
}
.kb-glossary-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: #141414;
border-radius: 6px;
margin-bottom: 8px;
}
.kb-transcript-item {
padding: 12px 16px;
background: #141414;
border-radius: 6px;
margin-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.file-type-icon {
padding: 4px 8px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 600;
}
.type-audio { background: #7b2cbf; }
.type-document { background: #00d4ff; color: #000; }
/* Transcript selector */
.transcript-selector {
position: relative;
}
.transcript-dropdown {
position: absolute;
top: 100%;
right: 0;
background: #1a1a1a;
border: 1px solid #333;
border-radius: 8px;
min-width: 200px;
max-height: 300px;
overflow-y: auto;
display: none;
z-index: 100;
}
.transcript-dropdown.show {
display: block;
}
.transcript-option {
padding: 10px 16px;
cursor: pointer;
border-bottom: 1px solid #222;
}
.transcript-option:hover {
background: #2a2a2a;
}
.transcript-option.active {
background: #00d4ff22;
}
/* Phase 4: Agent Panel */
.agent-panel {
width: 320px;
background: #111;
border-left: 1px solid #222;
display: flex;
flex-direction: column;
transition: width 0.3s ease;
}
.agent-panel.collapsed {
width: 50px;
}
.agent-header {
padding: 12px 16px;
background: #141414;
border-bottom: 1px solid #222;
display: flex;
align-items: center;
justify-content: space-between;
}
.agent-title {
font-size: 0.9rem;
color: #00d4ff;
display: flex;
align-items: center;
gap: 8px;
}
.agent-toggle {
background: none;
border: none;
color: #666;
cursor: pointer;
font-size: 1.2rem;
padding: 4px;
}
.agent-toggle:hover {
color: #00d4ff;
}
.agent-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.agent-panel.collapsed .agent-content {
display: none;
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.chat-message {
margin-bottom: 16px;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.chat-message.user {
text-align: right;
}
.chat-message.assistant {
text-align: left;
}
.message-bubble {
display: inline-block;
padding: 10px 14px;
border-radius: 12px;
max-width: 90%;
font-size: 0.9rem;
line-height: 1.5;
}
.chat-message.user .message-bubble {
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
color: white;
}
.chat-message.assistant .message-bubble {
background: #1a1a1a;
color: #e0e0e0;
border: 1px solid #333;
}
.chat-input-area {
padding: 12px 16px;
border-top: 1px solid #222;
background: #141414;
}
.chat-input {
width: 100%;
background: #1a1a1a;
border: 1px solid #333;
border-radius: 8px;
padding: 10px 12px;
color: #e0e0e0;
font-size: 0.9rem;
resize: none;
outline: none;
}
.chat-input:focus {
border-color: #00d4ff;
}
.chat-actions {
display: flex;
gap: 8px;
margin-top: 8px;
}
.chat-btn {
background: #222;
border: 1px solid #333;
color: #888;
padding: 6px 12px;
border-radius: 6px;
font-size: 0.8rem;
cursor: pointer;
transition: all 0.2s;
}
.chat-btn:hover {
background: #333;
color: #00d4ff;
border-color: #00d4ff;
}
.chat-btn.primary {
background: linear-gradient(90deg, #00d4ff, #7b2cbf);
color: white;
border: none;
}
.chat-btn.primary:hover {
opacity: 0.9;
}
/* Phase 4: Entity Card */
.entity-card {
position: fixed;
background: #1a1a1a;
border: 1px solid #333;
border-radius: 12px;
padding: 16px;
min-width: 280px;
max-width: 350px;
box-shadow: 0 10px 40px rgba(0,0,0,0.5);
z-index: 1000;
display: none;
}
.entity-card.show {
display: block;
animation: popIn 0.2s ease;
}
@keyframes popIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
.entity-card-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #333;
}
.entity-card-name {
font-size: 1.1rem;
font-weight: 600;
color: #fff;
}
.entity-card-stats {
display: flex;
gap: 16px;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #333;
font-size: 0.85rem;
color: #888;
}
.entity-card-stat {
display: flex;
align-items: center;
gap: 6px;
}
/* Phase 4: Provenance Modal */
.provenance-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.8);
display: none;
align-items: center;
justify-content: center;
z-index: 2000;
}
.provenance-modal.show {
display: flex;
}
.provenance-content {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 16px;
width: 90%;
max-width: 600px;
max-height: 80vh;
overflow: hidden;
}
.provenance-header {
padding: 20px;
background: #141414;
border-bottom: 1px solid #333;
display: flex;
justify-content: space-between;
align-items: center;
}
.provenance-body {
padding: 20px;
overflow-y: auto;
max-height: 60vh;
}
.provenance-evidence {
background: #0a0a0a;
border-left: 3px solid #00d4ff;
padding: 16px;
margin: 16px 0;
border-radius: 0 8px 8px 0;
font-style: italic;
color: #ccc;
}
/* Phase 4: Suggestion Card */
.suggestion-card {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 8px;
padding: 12px;
margin-bottom: 12px;
cursor: pointer;
transition: all 0.2s;
}
.suggestion-card:hover {
border-color: #00d4ff;
background: #222;
}
.suggestion-type {
font-size: 0.75rem;
color: #00d4ff;
text-transform: uppercase;
margin-bottom: 4px;
}
.suggestion-title {
font-weight: 500;
margin-bottom: 4px;
}
.suggestion-desc {
font-size: 0.85rem;
color: #888;
}
/* Phase 5: Timeline View */
.timeline-panel {
width: 100%;
height: 100%;
display: none;
flex-direction: column;
background: #0a0a0a;
}
.timeline-panel.show {
display: flex;
}
.timeline-header {
padding: 16px 20px;
background: #141414;
border-bottom: 1px solid #222;
display: flex;
justify-content: space-between;
align-items: center;
}
.timeline-filters {
display: flex;
gap: 12px;
align-items: center;
}
.timeline-filter-select {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 6px;
padding: 8px 12px;
color: #e0e0e0;
font-size: 0.9rem;
}
.timeline-content {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.timeline-container {
position: relative;
max-width: 900px;
margin: 0 auto;
}
.timeline-line {
position: absolute;
left: 120px;
top: 0;
bottom: 0;
width: 2px;
background: linear-gradient(180deg, #00d4ff, #7b2cbf);
}
.timeline-date-group {
margin-bottom: 32px;
}
.timeline-date-header {
display: flex;
align-items: center;
margin-bottom: 16px;
position: relative;
}
.timeline-date-label {
width: 100px;
text-align: right;
padding-right: 20px;
color: #888;
font-size: 0.85rem;
font-weight: 500;
}
.timeline-date-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: #00d4ff;
position: absolute;
left: 115px;
box-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
}
.timeline-events {
margin-left: 140px;
}
.timeline-event {
background: #141414;
border: 1px solid #222;
border-radius: 8px;
padding: 16px;
margin-bottom: 12px;
transition: all 0.2s;
cursor: pointer;
}
.timeline-event:hover {
border-color: #00d4ff;
background: #1a1a1a;
}
.timeline-event-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.timeline-event-type {
padding: 2px 8px;
border-radius: 4px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
}
.timeline-event-type.mention {
background: #7b2cbf;
}
.timeline-event-type.relation {
background: #00d4ff;
color: #000;
}
.timeline-event-entity {
font-weight: 600;
color: #fff;
}
.timeline-event-snippet {
color: #aaa;
font-size: 0.9rem;
line-height: 1.5;
margin-bottom: 8px;
padding-left: 12px;
border-left: 2px solid #333;
}
.timeline-event-source {
font-size: 0.8rem;
color: #666;
display: flex;
align-items: center;
gap: 8px;
}
.timeline-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.timeline-stat-card {
background: #141414;
border: 1px solid #222;
border-radius: 8px;
padding: 16px;
}
.timeline-stat-title {
font-size: 0.8rem;
color: #666;
margin-bottom: 8px;
}
.timeline-stat-value {
font-size: 1.5rem;
font-weight: 600;
color: #00d4ff;
}
.timeline-empty {
text-align: center;
padding: 60px 20px;
color: #666;
}
.timeline-legend {
display: flex;
gap: 20px;
margin-top: 8px;
}
.timeline-legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.8rem;
color: #888;
}
.timeline-legend-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.timeline-legend-dot.mention { background: #7b2cbf; }
.timeline-legend-dot.relation { background: #00d4ff; }
/* Phase 5: Reasoning Panel */
.reasoning-panel {
display: none;
flex-direction: column;
width: 100%;
height: 100%;
padding: 24px;
overflow-y: auto;
}
.reasoning-panel.active {
display: flex;
}
.reasoning-header {
margin-bottom: 24px;
}
.reasoning-header h2 {
font-size: 1.5rem;
margin-bottom: 8px;
}
.reasoning-types {
display: flex;
gap: 12px;
margin-bottom: 24px;
flex-wrap: wrap;
}
.reasoning-type-btn {
padding: 10px 20px;
background: #141414;
border: 1px solid #222;
border-radius: 8px;
color: #888;
cursor: pointer;
transition: all 0.2s;
font-size: 0.9rem;
}
.reasoning-type-btn:hover, .reasoning-type-btn.active {
border-color: #00d4ff;
color: #00d4ff;
background: #00d4ff11;
}
.reasoning-query-box {
background: #141414;
border: 1px solid #222;
border-radius: 12px;
padding: 20px;
margin-bottom: 24px;
}
.reasoning-input {
width: 100%;
background: transparent;
border: none;
color: #e0e0e0;
font-size: 1rem;
resize: none;
outline: none;
min-height: 60px;
font-family: inherit;
}
.reasoning-input::placeholder {
color: #666;
}
.reasoning-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #222;
}
.reasoning-options {
display: flex;
gap: 16px;
align-items: center;
}
.reasoning-option {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.85rem;
color: #888;
}
.reasoning-option select {
background: #1a1a1a;
border: 1px solid #333;
color: #e0e0e0;
padding: 4px 8px;
border-radius: 4px;
}
.reasoning-result {
background: #141414;
border: 1px solid #222;
border-radius: 12px;
padding: 24px;
margin-bottom: 16px;
}
.reasoning-result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px solid #222;
}
.reasoning-result-type {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.9rem;
color: #00d4ff;
}
.reasoning-confidence {
padding: 4px 12px;
background: #00d4ff22;
border-radius: 20px;
font-size: 0.8rem;
color: #00d4ff;
}
.reasoning-answer {
line-height: 1.8;
color: #e0e0e0;
font-size: 1rem;
margin-bottom: 20px;
}
.reasoning-evidence {
background: #0a0a0a;
border-radius: 8px;
padding: 16px;
margin-top: 16px;
}
.reasoning-evidence h4 {
font-size: 0.85rem;
color: #888;
margin-bottom: 12px;
}
.evidence-item {
padding: 10px 12px;
background: #141414;
border-radius: 6px;
margin-bottom: 8px;
font-size: 0.85rem;
color: #aaa;
border-left: 3px solid #00d4ff;
}
.reasoning-gaps {
margin-top: 16px;
padding: 12px;
background: #ff6b6b11;
border: 1px solid #ff6b6b33;
border-radius: 8px;
}
.reasoning-gaps h4 {
font-size: 0.85rem;
color: #ff6b6b;
margin-bottom: 8px;
}
.reasoning-gaps ul {
margin: 0;
padding-left: 20px;
font-size: 0.85rem;
color: #aaa;
}
.reasoning-summary-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.summary-card {
background: #141414;
border: 1px solid #222;
border-radius: 12px;
padding: 20px;
cursor: pointer;
transition: all 0.2s;
}
.summary-card:hover {
border-color: #00d4ff;
background: #1a1a1a;
}
.summary-card h4 {
color: #00d4ff;
font-size: 0.9rem;
margin-bottom: 8px;
}
.summary-card p {
color: #888;
font-size: 0.85rem;
}
.inference-paths {
background: #141414;
border: 1px solid #222;
border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
}
.inference-path {
padding: 16px;
background: #0a0a0a;
border-radius: 8px;
margin-bottom: 12px;
}
.inference-path-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.path-strength {
padding: 4px 10px;
background: #7b2cbf33;
border-radius: 20px;
font-size: 0.8rem;
color: #7b2cbf;
}
.path-visual {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
font-size: 0.9rem;
}
.path-node {
padding: 6px 12px;
background: #1a1a1a;
border-radius: 6px;
color: #e0e0e0;
}
.path-arrow {
color: #00d4ff;
}
/* Typing indicator */
.typing-indicator {
display: flex;
gap: 4px;
padding: 10px 14px;
}
.typing-indicator span {
width: 8px;
height: 8px;
background: #666;
border-radius: 50%;
animation: typing 1.4s infinite;
}
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing {
0%, 60%, 100% { transform: translateY(0); }
30% { transform: translateY(-10px); }
}
</style>
</head>
<body>
<div class="header">
<div class="header-left">
<a href="/" class="back-link">← 返回项目列表</a>
<span class="project-name" id="projectName">加载中...</span>
</div>
<div class="header-actions">
<button class="btn btn-small" onclick="showUpload()">+ 上传文件</button>
</div>
</div>
<div class="main">
<!-- Sidebar -->
<div class="sidebar">
<button class="sidebar-btn active" onclick="switchView('workbench')" title="工作台">📝</button>
<button class="sidebar-btn" onclick="switchView('knowledge-base')" title="知识库">📚</button>
<button class="sidebar-btn" onclick="switchView('timeline')" title="时间线">📅</button>
<button class="sidebar-btn" onclick="switchView('reasoning')" title="智能推理">🧠</button>
</div>
<!-- Content Area -->
<div class="content-area">
<!-- Workbench View -->
<div id="workbenchView" class="workbench-view" style="display: flex; width: 100%;">
<!-- Agent Panel -->
<div class="agent-panel" id="agentPanel">
<div class="agent-header">
<div class="agent-title">
<span>🤖</span>
<span>Agent 助手</span>
</div>
<button class="agent-toggle" onclick="toggleAgentPanel()"></button>
</div>
<div class="agent-content">
<div class="chat-messages" id="chatMessages">
<div class="chat-message assistant">
<div class="message-bubble">
你好!我是 InsightFlow Agent。我可以帮你<br><br>
<b>问答</b>:询问项目中的任何信息<br>
<b>合并实体</b>"把所有'客户端'合并到'App'"<br>
<b>分析演变</b>"张总对项目的态度变化"<br>
<b>编辑定义</b>"修改K8s的定义为..."
</div>
</div>
</div>
<div class="chat-input-area">
<textarea class="chat-input" id="chatInput" rows="2" placeholder="输入指令或问题..."></textarea>
<div class="chat-actions">
<button class="chat-btn" onclick="loadSuggestions()">💡 建议</button>
<button class="chat-btn primary" onclick="sendAgentMessage()">发送</button>
</div>
</div>
</div>
</div>
<div class="editor-panel">
<div class="panel-header">
<div style="display: flex; align-items: center; gap: 12px;">
<span>📄 转录文本</span>
<div class="transcript-selector">
<button class="btn-icon" onclick="toggleTranscriptDropdown()">📁 选择文件</button>
<div class="transcript-dropdown" id="transcriptDropdown"></div>
</div>
</div>
<div class="panel-actions">
<button class="btn-icon" onclick="toggleEditMode()" id="editBtn">✏️ 编辑</button>
<button class="btn-icon" onclick="saveTranscript()" id="saveBtn" style="display:none;">💾 保存</button>
</div>
</div>
<div class="transcript-content" id="transcriptContent">
<div class="empty-state">
<p style="color:#666;">暂无转录内容</p>
<button class="btn" onclick="showUpload()">上传音频或文档</button>
</div>
</div>
</div>
<div class="graph-panel">
<div class="panel-header">
<span>🔗 知识图谱</span>
<span style="font-size:0.8rem;color:#666;">右键节点编辑 | 点击连线溯源</span>
</div>
<svg id="graph-svg"></svg>
<div class="entity-list" id="entityList">
<h3 style="margin-bottom:12px;color:#888;font-size:0.9rem;">项目实体</h3>
<p style="color:#666;font-size:0.85rem;">暂无实体数据</p>
</div>
</div>
</div>
<!-- Timeline View -->
<div id="timelineView" class="timeline-panel">
<div class="timeline-header">
<div>
<h2>📅 项目时间线</h2>
<div class="timeline-legend">
<div class="timeline-legend-item">
<div class="timeline-legend-dot mention"></div>
<span>实体提及</span>
</div>
<div class="timeline-legend-item">
<div class="timeline-legend-dot relation"></div>
<span>关系建立</span>
</div>
</div>
</div>
<div class="timeline-filters">
<select class="timeline-filter-select" id="timelineEntityFilter" onchange="loadTimeline()">
<option value="">全部实体</option>
</select>
<button class="btn btn-small" onclick="loadTimeline()">🔄 刷新</button>
</div>
</div>
<div class="timeline-content" id="timelineContent">
<div class="timeline-stats" id="timelineStats">
<div class="timeline-stat-card">
<div class="timeline-stat-title">总事件数</div>
<div class="timeline-stat-value" id="timelineTotalEvents">-</div>
</div>
<div class="timeline-stat-card">
<div class="timeline-stat-title">实体提及</div>
<div class="timeline-stat-value" id="timelineMentions">-</div>
</div>
<div class="timeline-stat-card">
<div class="timeline-stat-title">关系建立</div>
<div class="timeline-stat-value" id="timelineRelations">-</div>
</div>
</div>
<div class="timeline-container" id="timelineContainer">
<div class="timeline-empty">
<p>加载时间线数据...</p>
</div>
</div>
</div>
</div>
<!-- Knowledge Base View -->
<div id="knowledgeBaseView" class="kb-panel" style="display: none;">
<div class="kb-header">
<h2>📚 项目知识库</h2>
<div class="kb-stats">
<div class="kb-stat">
<div class="kb-stat-value" id="kbEntityCount">0</div>
<div class="kb-stat-label">实体</div>
</div>
<div class="kb-stat">
<div class="kb-stat-value" id="kbRelationCount">0</div>
<div class="kb-stat-label">关系</div>
</div>
<div class="kb-stat">
<div class="kb-stat-value" id="kbTranscriptCount">0</div>
<div class="kb-stat-label">文件</div>
</div>
<div class="kb-stat">
<div class="kb-stat-value" id="kbGlossaryCount">0</div>
<div class="kb-stat-label">术语</div>
</div>
</div>
</div>
<div class="kb-content">
<div class="kb-sidebar">
<div class="kb-nav-item active" onclick="switchKBTab('entities')">🏷️ 实体</div>
<div class="kb-nav-item" onclick="switchKBTab('relations')">🔗 关系</div>
<div class="kb-nav-item" onclick="switchKBTab('glossary')">📖 术语表</div>
<div class="kb-nav-item" onclick="switchKBTab('transcripts')">📁 文件</div>
</div>
<div class="kb-main">
<!-- Entities Section -->
<div class="kb-section active" id="kbEntitiesSection">
<h3 style="margin-bottom:16px;">所有实体</h3>
<div class="kb-entity-grid" id="kbEntityGrid"></div>
</div>
<!-- Relations Section -->
<div class="kb-section" id="kbRelationsSection">
<h3 style="margin-bottom:16px;">所有关系</h3>
<div id="kbRelationsList"></div>
</div>
<!-- Glossary Section -->
<div class="kb-section" id="kbGlossarySection">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
<h3>术语表</h3>
<button class="btn btn-small" onclick="showAddTermModal()">+ 添加术语</button>
</div>
<div id="kbGlossaryList"></div>
</div>
<!-- Transcripts Section -->
<div class="kb-section" id="kbTranscriptsSection">
<h3 style="margin-bottom:16px;">所有文件</h3>
<div id="kbTranscriptsList"></div>
</div>
</div>
</div>
</div>
<!-- Reasoning View -->
<div id="reasoningView" class="reasoning-panel">
<div class="reasoning-header">
<h2>🧠 智能推理与问答</h2>
<p style="color:#888;">基于知识图谱的深度推理分析</p>
</div>
<div class="reasoning-summary-cards">
<div class="summary-card" onclick="generateSummary('comprehensive')">
<h4>📋 全面总结</h4>
<p>生成项目的完整概览和关键洞察</p>
</div>
<div class="summary-card" onclick="generateSummary('executive')">
<h4>💼 高管摘要</h4>
<p>关注关键决策、风险和行动项</p>
</div>
<div class="summary-card" onclick="generateSummary('technical')">
<h4>⚙️ 技术总结</h4>
<p>分析技术架构、依赖和实现细节</p>
</div>
<div class="summary-card" onclick="generateSummary('risk')">
<h4>⚠️ 风险分析</h4>
<p>识别潜在风险、依赖和缓解建议</p>
</div>
</div>
<div class="reasoning-query-box">
<textarea
class="reasoning-input"
id="reasoningInput"
placeholder="输入你的问题,例如:
• 为什么项目延期了?(因果推理)
• 前端和后端技术栈有什么区别?(对比推理)
• 项目进度如何随时间变化?(时序推理)
• 哪些实体之间存在隐含关联?(关联推理)"
></textarea>
<div class="reasoning-actions">
<div class="reasoning-options">
<div class="reasoning-option">
<label>推理深度:</label>
<select id="reasoningDepth">
<option value="shallow">浅层</option>
<option value="medium" selected>中等</option>
<option value="deep">深度</option>
</select>
</div>
</div>
<div style="display:flex;gap:12px;">
<button class="btn btn-secondary" onclick="clearReasoningResult()">清除</button>
<button class="btn" onclick="submitReasoningQuery()">开始推理</button>
</div>
</div>
</div>
<div id="reasoningResults"></div>
<div class="inference-paths" id="inferencePathsSection" style="display:none;">
<h3 style="margin-bottom:16px;">🔗 实体关联路径</h3>
<div id="inferencePathsList"></div>
</div>
</div>
</div>
</div>
<!-- Entity Hover Card -->
<div class="entity-card" id="entityCard">
<div class="entity-card-header">
<span class="entity-type-badge" id="cardBadge">TYPE</span>
<span class="entity-card-name" id="cardName">Entity Name</span>
</div>
<div id="cardDefinition" style="color:#aaa;font-size:0.9rem;line-height:1.5;">暂无定义</div>
<div class="entity-card-stats">
<div class="entity-card-stat">
<span>📍</span>
<span id="cardMentions">0 次提及</span>
</div>
<div class="entity-card-stat">
<span>🔗</span>
<span id="cardRelations">0 个关系</span>
</div>
</div>
</div>
<!-- Provenance Modal -->
<div class="provenance-modal" id="provenanceModal">
<div class="provenance-content">
<div class="provenance-header">
<h3>🔗 知识溯源</h3>
<button class="close-btn" onclick="closeProvenance()">×</button>
</div>
<div class="provenance-body" id="provenanceBody">
<p style="color:#666;">加载中...</p>
</div>
</div>
</div>
<!-- Upload Modal -->
<div class="upload-overlay" id="uploadOverlay">
<div class="upload-box">
<h2 style="margin-bottom:10px;">上传文件</h2>
<div class="upload-tabs">
<div class="upload-tab active" onclick="switchUploadTab('audio')">🎵 音频</div>
<div class="upload-tab" onclick="switchUploadTab('document')">📄 文档</div>
</div>
<p style="color:#666;" id="uploadHint">支持 MP3, WAV, M4A (最大 500MB)</p>
<input type="file" id="fileInput" accept="audio/*" hidden>
<input type="file" id="docInput" accept=".pdf,.docx,.doc,.txt,.md" hidden>
<button class="btn" onclick="triggerFileSelect()">选择文件</button>
<br><br>
<button class="btn btn-secondary" onclick="hideUpload()">取消</button>
</div>
</div>
<!-- Entity Editor Modal -->
<div class="modal-overlay" id="entityModal">
<div class="modal">
<h3 class="modal-header">编辑实体</h3>
<div class="form-group">
<label>实体名称</label>
<input type="text" id="entityName" placeholder="实体名称">
</div>
<div class="form-group">
<label>实体类型</label>
<select id="entityType">
<option value="PROJECT">项目 (PROJECT)</option>
<option value="TECH">技术 (TECH)</option>
<option value="PERSON">人物 (PERSON)</option>
<option value="ORG">组织 (ORG)</option>
<option value="OTHER">其他 (OTHER)</option>
</select>
</div>
<div class="form-group">
<label>定义描述</label>
<textarea id="entityDefinition" placeholder="一句话描述这个实体..."></textarea>
</div>
<div class="form-group">
<label>别名 (用逗号分隔)</label>
<input type="text" id="entityAliases" placeholder="别名1, 别名2, 别名3">
</div>
<div class="relation-editor" id="relationEditor" style="display:none;">
<h4 style="color:#888;font-size:0.9rem;margin-bottom:12px;">实体关系</h4>
<div id="relationList"></div>
<button class="btn-icon" onclick="showAddRelation()" style="margin-top:8px;">+ 添加关系</button>
</div>
<div class="modal-actions">
<button class="btn btn-danger" onclick="deleteEntity()" id="deleteEntityBtn">删除</button>
<button class="btn btn-secondary" onclick="hideEntityModal()">取消</button>
<button class="btn" onclick="saveEntity()">保存</button>
</div>
</div>
</div>
<!-- Add Relation Modal -->
<div class="modal-overlay" id="relationModal">
<div class="modal">
<h3 class="modal-header">添加关系</h3>
<div class="form-group">
<label>目标实体</label>
<select id="relationTarget"></select>
</div>
<div class="form-group">
<label>关系类型</label>
<select id="relationType">
<option value="belongs_to">属于 (belongs_to)</option>
<option value="works_with">合作 (works_with)</option>
<option value="depends_on">依赖 (depends_on)</option>
<option value="mentions">提及 (mentions)</option>
<option value="related">相关 (related)</option>
</select>
</div>
<div class="form-group">
<label>关系证据/说明</label>
<textarea id="relationEvidence" placeholder="描述这个关系的依据..."></textarea>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideRelationModal()">取消</button>
<button class="btn" onclick="saveRelation()">添加</button>
</div>
</div>
</div>
<!-- Merge Entities Modal -->
<div class="modal-overlay" id="mergeModal">
<div class="modal">
<h3 class="modal-header">合并实体</h3>
<p style="color:#888;margin-bottom:16px;font-size:0.9rem;">将选中的实体合并到目标实体中</p>
<div class="form-group">
<label>源实体</label>
<input type="text" id="mergeSource" disabled>
</div>
<div class="form-group">
<label>目标实体 (保留)</label>
<select id="mergeTarget"></select>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideMergeModal()">取消</button>
<button class="btn" onclick="confirmMerge()">合并</button>
</div>
</div>
</div>
<!-- Add Glossary Term Modal -->
<div class="modal-overlay" id="glossaryModal">
<div class="modal">
<h3 class="modal-header">添加术语</h3>
<div class="form-group">
<label>术语</label>
<input type="text" id="glossaryTerm" placeholder="术语名称">
</div>
<div class="form-group">
<label>发音提示 (可选)</label>
<input type="text" id="glossaryPronunciation" placeholder="如: K8s 发音为 Kubernetes">
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideGlossaryModal()">取消</button>
<button class="btn" onclick="saveGlossaryTerm()">添加</button>
</div>
</div>
</div>
<!-- Context Menu -->
<div class="context-menu" id="contextMenu">
<div class="context-menu-item" onclick="editEntity()">✏️ 编辑实体</div>
<div class="context-menu-item" onclick="showMergeModal()">🔄 合并实体</div>
<div class="context-menu-divider"></div>
<div class="context-menu-item" onclick="createEntityFromSelection()"> 标记为实体</div>
</div>
<!-- Selection Toolbar -->
<div class="selection-toolbar" id="selectionToolbar">
<span style="color:#888;font-size:0.85rem;">选中文本:</span>
<button class="btn-icon" onclick="createEntityFromSelection()">标记为实体</button>
<button class="btn-icon" onclick="hideSelectionToolbar()">取消</button>
</div>
<script src="app.js"></script>
</body>
</html>