Files
insightflow/frontend/workbench.html

3188 lines
105 KiB
HTML
Raw Permalink 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); }
}
/* Phase 5: Entity Attributes */
.attributes-list {
max-height: 300px;
overflow-y: auto;
}
.attribute-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
background: #0a0a0a;
border-radius: 6px;
margin-bottom: 8px;
}
.attribute-info {
flex: 1;
}
.attribute-name {
font-weight: 500;
color: #e0e0e0;
margin-bottom: 4px;
}
.attribute-value {
color: #00d4ff;
font-size: 0.9rem;
}
.attribute-type {
font-size: 0.7rem;
color: #666;
margin-left: 8px;
}
.attribute-actions {
display: flex;
gap: 8px;
}
.attribute-btn {
background: transparent;
border: 1px solid #333;
color: #888;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 0.75rem;
}
.attribute-btn:hover {
border-color: #00d4ff;
color: #00d4ff;
}
.attribute-btn.delete:hover {
border-color: #ff6b6b;
color: #ff6b6b;
}
.attributes-add-form {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #333;
}
.templates-list {
max-height: 300px;
overflow-y: auto;
}
.template-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
background: #0a0a0a;
border-radius: 6px;
margin-bottom: 8px;
}
.template-info {
flex: 1;
}
.template-name {
font-weight: 500;
color: #e0e0e0;
}
.template-desc {
font-size: 0.8rem;
color: #666;
margin-top: 2px;
}
.history-item {
padding: 12px;
background: #0a0a0a;
border-radius: 6px;
margin-bottom: 8px;
font-size: 0.85rem;
}
.history-header {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
color: #888;
}
.history-change {
display: flex;
align-items: center;
gap: 8px;
}
.history-old {
color: #ff6b6b;
text-decoration: line-through;
}
.history-new {
color: #4ecdc4;
}
.history-arrow {
color: #666;
}
/* Phase 5: Attribute filter in KB */
.attribute-filter-bar {
display: flex;
gap: 12px;
margin-bottom: 16px;
padding: 12px;
background: #141414;
border-radius: 8px;
flex-wrap: wrap;
}
.attribute-filter-item {
display: flex;
align-items: center;
gap: 8px;
}
.attribute-filter-item select,
.attribute-filter-item input {
background: #1a1a1a;
border: 1px solid #333;
color: #e0e0e0;
padding: 6px 10px;
border-radius: 4px;
font-size: 0.85rem;
}
/* Export Panel Styles */
.export-section {
background: #141414;
border-radius: 8px;
padding: 16px;
}
.export-options {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 12px;
}
.export-btn {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 8px;
padding: 16px 12px;
cursor: pointer;
transition: all 0.2s;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 8px;
}
.export-btn:hover {
border-color: #00d4ff;
background: #1f1f1f;
transform: translateY(-2px);
}
.export-icon {
font-size: 1.5rem;
}
.export-label {
color: #e0e0e0;
font-size: 0.9rem;
font-weight: 500;
}
.export-desc {
color: #666;
font-size: 0.75rem;
}
.export-loading {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
color: #00d4ff;
padding: 20px;
}
.export-success {
background: rgba(0, 212, 255, 0.1);
border: 1px solid #00d4ff;
border-radius: 8px;
padding: 12px;
color: #00d4ff;
text-align: center;
}
/* Phase 5: Graph Analysis Panel */
.graph-analysis-panel {
display: none;
flex-direction: column;
width: 100%;
height: 100%;
background: #0a0a0a;
}
.graph-analysis-panel.active {
display: flex;
}
.graph-analysis-header {
padding: 16px 20px;
background: #141414;
border-bottom: 1px solid #222;
display: flex;
justify-content: space-between;
align-items: center;
}
.graph-analysis-header h2 {
font-size: 1.3rem;
margin-bottom: 4px;
}
.graph-analysis-actions {
display: flex;
gap: 10px;
}
.graph-analysis-content {
flex: 1;
display: flex;
overflow: hidden;
}
.graph-analysis-sidebar {
width: 320px;
background: #111;
border-right: 1px solid #222;
padding: 16px;
overflow-y: auto;
}
.graph-analysis-section {
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #222;
}
.graph-analysis-section:last-child {
border-bottom: none;
}
.graph-analysis-section h4 {
color: #00d4ff;
font-size: 0.9rem;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.graph-stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.graph-stat-item {
background: #141414;
border: 1px solid #222;
border-radius: 8px;
padding: 12px;
text-align: center;
}
.graph-stat-value {
display: block;
font-size: 1.5rem;
font-weight: 600;
color: #00d4ff;
margin-bottom: 4px;
}
.graph-stat-label {
font-size: 0.75rem;
color: #666;
}
.graph-query-group {
background: #141414;
border: 1px solid #222;
border-radius: 8px;
padding: 12px;
margin-bottom: 12px;
}
.graph-query-group label {
display: block;
color: #888;
font-size: 0.8rem;
margin-bottom: 8px;
}
.graph-query-select,
.graph-query-input {
width: 100%;
background: #1a1a1a;
border: 1px solid #333;
border-radius: 6px;
padding: 8px 12px;
color: #e0e0e0;
font-size: 0.85rem;
margin-bottom: 8px;
}
.graph-query-select:focus,
.graph-query-input:focus {
outline: none;
border-color: #00d4ff;
}
.graph-query-options {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.graph-query-options label {
margin-bottom: 0;
}
.graph-query-input {
width: 60px;
margin-bottom: 0;
}
.graph-analysis-tabs {
display: flex;
gap: 8px;
}
.graph-analysis-tab {
flex: 1;
padding: 10px;
background: #141414;
border: 1px solid #222;
border-radius: 6px;
color: #888;
cursor: pointer;
font-size: 0.85rem;
transition: all 0.2s;
}
.graph-analysis-tab:hover {
border-color: #00d4ff;
color: #00d4ff;
}
.graph-analysis-tab.active {
background: #00d4ff22;
border-color: #00d4ff;
color: #00d4ff;
}
.graph-analysis-viz {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.graph-viz-panel {
display: none;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.graph-viz-panel.active {
display: flex;
}
.graph-viz-header {
padding: 16px 20px;
background: #141414;
border-bottom: 1px solid #222;
}
.graph-viz-header h3 {
font-size: 1.1rem;
margin-bottom: 4px;
}
.centrality-list {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.centrality-item {
display: flex;
align-items: center;
padding: 12px 16px;
background: #141414;
border: 1px solid #222;
border-radius: 8px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.2s;
}
.centrality-item:hover {
border-color: #00d4ff;
background: #1a1a1a;
}
.centrality-rank {
width: 32px;
height: 32px;
background: #00d4ff22;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #00d4ff;
font-weight: 600;
font-size: 0.9rem;
margin-right: 12px;
}
.centrality-rank.top3 {
background: #00d4ff;
color: #000;
}
.centrality-info {
flex: 1;
}
.centrality-name {
font-weight: 500;
color: #e0e0e0;
margin-bottom: 2px;
}
.centrality-type {
font-size: 0.75rem;
color: #666;
}
.centrality-score {
text-align: right;
}
.centrality-value {
font-size: 1.2rem;
font-weight: 600;
color: #00d4ff;
}
.centrality-label {
font-size: 0.75rem;
color: #666;
}
.communities-viz {
flex: 1;
background: #0a0a0a;
position: relative;
overflow: hidden;
}
.communities-viz svg {
width: 100%;
height: 100%;
}
.communities-list {
max-height: 200px;
overflow-y: auto;
padding: 12px 16px;
background: #111;
border-top: 1px solid #222;
}
.community-item {
padding: 10px 12px;
background: #141414;
border-radius: 6px;
margin-bottom: 8px;
}
.community-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.community-name {
font-weight: 500;
color: #e0e0e0;
}
.community-size {
font-size: 0.75rem;
color: #00d4ff;
background: #00d4ff22;
padding: 2px 8px;
border-radius: 10px;
}
.community-nodes {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.community-node-tag {
font-size: 0.75rem;
color: #888;
background: #1a1a1a;
padding: 2px 8px;
border-radius: 4px;
}
.path-viz {
flex: 1;
background: #0a0a0a;
position: relative;
overflow: hidden;
}
.path-viz svg {
width: 100%;
height: 100%;
}
.path-info {
max-height: 150px;
overflow-y: auto;
padding: 12px 16px;
background: #111;
border-top: 1px solid #222;
}
.path-step {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 0;
border-bottom: 1px solid #222;
}
.path-step:last-child {
border-bottom: none;
}
.path-step-number {
width: 24px;
height: 24px;
background: #00d4ff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #000;
font-size: 0.75rem;
font-weight: 600;
}
.path-step-content {
flex: 1;
}
.path-step-entity {
font-weight: 500;
color: #e0e0e0;
}
.path-step-relation {
font-size: 0.8rem;
color: #00d4ff;
}
.graph-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #666;
}
.graph-empty-state-icon {
font-size: 3rem;
margin-bottom: 16px;
opacity: 0.5;
}
.graph-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #00d4ff;
}
.graph-loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #222;
border-top-color: #00d4ff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 12px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.neo4j-status {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.8rem;
padding: 4px 10px;
border-radius: 4px;
}
.neo4j-status.connected {
background: #00d4ff22;
color: #00d4ff;
}
.neo4j-status.disconnected {
background: #ff6b6b22;
color: #ff6b6b;
}
.neo4j-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
}
/* Phase 6: API Key Management Panel */
.api-keys-panel {
display: none;
flex-direction: column;
width: 100%;
height: 100%;
background: #0a0a0a;
}
.api-keys-panel.active {
display: flex;
}
.api-keys-header {
padding: 16px 20px;
background: #141414;
border-bottom: 1px solid #222;
display: flex;
justify-content: space-between;
align-items: center;
}
.api-keys-header h2 {
font-size: 1.3rem;
margin-bottom: 4px;
}
.api-keys-content {
flex: 1;
padding: 24px;
overflow-y: auto;
}
.api-keys-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.api-key-stat-card {
background: #141414;
border: 1px solid #222;
border-radius: 8px;
padding: 16px;
text-align: center;
}
.api-key-stat-value {
font-size: 1.5rem;
font-weight: 600;
color: #00d4ff;
}
.api-key-stat-label {
font-size: 0.75rem;
color: #666;
margin-top: 4px;
}
.api-keys-list {
background: #141414;
border: 1px solid #222;
border-radius: 12px;
overflow: hidden;
}
.api-keys-list-header {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr 1fr 120px;
padding: 12px 16px;
background: #1a1a1a;
border-bottom: 1px solid #222;
font-size: 0.85rem;
color: #888;
font-weight: 500;
}
.api-key-item {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr 1fr 120px;
padding: 16px;
border-bottom: 1px solid #222;
align-items: center;
transition: background 0.2s;
}
.api-key-item:hover {
background: #1a1a1a;
}
.api-key-item:last-child {
border-bottom: none;
}
.api-key-name {
font-weight: 500;
color: #e0e0e0;
}
.api-key-preview {
font-family: monospace;
font-size: 0.85rem;
color: #00d4ff;
background: #00d4ff11;
padding: 4px 8px;
border-radius: 4px;
}
.api-key-permissions {
display: flex;
gap: 4px;
flex-wrap: wrap;
}
.api-key-permission {
font-size: 0.7rem;
padding: 2px 6px;
border-radius: 4px;
background: #333;
color: #888;
}
.api-key-permission.read {
background: #00d4ff22;
color: #00d4ff;
}
.api-key-permission.write {
background: #7b2cbf22;
color: #7b2cbf;
}
.api-key-permission.delete {
background: #ff6b6b22;
color: #ff6b6b;
}
.api-key-status {
font-size: 0.8rem;
padding: 4px 10px;
border-radius: 20px;
display: inline-block;
}
.api-key-status.active {
background: #00d4ff22;
color: #00d4ff;
}
.api-key-status.revoked {
background: #ff6b6b22;
color: #ff6b6b;
}
.api-key-status.expired {
background: #66666622;
color: #666;
}
.api-key-actions {
display: flex;
gap: 8px;
}
.api-key-btn {
background: transparent;
border: 1px solid #333;
color: #888;
padding: 6px 12px;
border-radius: 6px;
cursor: pointer;
font-size: 0.8rem;
transition: all 0.2s;
}
.api-key-btn:hover {
border-color: #00d4ff;
color: #00d4ff;
}
.api-key-btn.danger:hover {
border-color: #ff6b6b;
color: #ff6b6b;
}
.api-key-empty {
text-align: center;
padding: 60px 20px;
color: #666;
}
.api-key-modal-form {
display: flex;
flex-direction: column;
gap: 16px;
}
.api-key-form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.api-key-form-group label {
font-size: 0.9rem;
color: #888;
}
.api-key-form-group input,
.api-key-form-group select {
background: #0a0a0a;
border: 1px solid #333;
border-radius: 6px;
padding: 10px 12px;
color: #e0e0e0;
font-size: 0.95rem;
}
.api-key-form-group input:focus,
.api-key-form-group select:focus {
outline: none;
border-color: #00d4ff;
}
.api-key-permissions-select {
display: flex;
gap: 12px;
}
.api-key-permission-checkbox {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
}
.api-key-permission-checkbox input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: #00d4ff;
}
.api-key-created-modal .api-key-value {
background: #0a0a0a;
border: 1px solid #00d4ff;
border-radius: 8px;
padding: 16px;
font-family: monospace;
font-size: 1rem;
color: #00d4ff;
margin: 16px 0;
word-break: break-all;
}
.api-key-created-modal .warning {
background: #ff6b6b22;
border: 1px solid #ff6b6b;
border-radius: 8px;
padding: 12px;
color: #ff6b6b;
font-size: 0.85rem;
margin-bottom: 16px;
}
.api-key-stats-modal .stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 24px;
}
.api-key-stats-modal .stat-item {
background: #0a0a0a;
border-radius: 8px;
padding: 16px;
text-align: center;
}
.api-key-stats-modal .stat-value {
font-size: 1.5rem;
font-weight: 600;
color: #00d4ff;
}
.api-key-stats-modal .stat-label {
font-size: 0.8rem;
color: #666;
margin-top: 4px;
}
.api-key-logs {
max-height: 300px;
overflow-y: auto;
}
.api-key-log-item {
display: grid;
grid-template-columns: 1fr 80px 60px 80px;
gap: 12px;
padding: 12px;
border-bottom: 1px solid #222;
font-size: 0.85rem;
}
.api-key-log-item:last-child {
border-bottom: none;
}
.api-key-log-endpoint {
color: #e0e0e0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.api-key-log-method {
color: #00d4ff;
font-family: monospace;
}
.api-key-log-status {
text-align: center;
}
.api-key-log-status.success {
color: #4ecdc4;
}
.api-key-log-status.error {
color: #ff6b6b;
}
.api-key-log-time {
color: #666;
text-align: right;
}
</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>
<button class="sidebar-btn" onclick="switchView('graph-analysis')" title="图分析">🕸️</button>
<button class="sidebar-btn" onclick="switchView('api-keys')" title="API Keys">🔑</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 class="kb-stat" style="cursor:pointer;" onclick="showExportPanel()">
<div class="kb-stat-value">📥</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">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
<h3>所有实体</h3>
<button class="btn btn-small" onclick="showAttributeTemplates()">🏷️ 管理属性模板</button>
</div>
<!-- Attribute Filter Bar -->
<div class="attribute-filter-bar">
<div class="attribute-filter-item">
<label>属性筛选:</label>
<input type="text" id="attrFilterName" placeholder="属性名 (如: 职位)" style="width:120px;">
</div>
<div class="attribute-filter-item">
<select id="attrFilterOp">
<option value="eq">等于</option>
<option value="contains">包含</option>
<option value="gt">大于</option>
<option value="lt">小于</option>
</select>
</div>
<div class="attribute-filter-item">
<input type="text" id="attrFilterValue" placeholder="属性值">
</div>
<button class="btn-icon" onclick="searchByAttributes()">🔍 搜索</button>
<button class="btn-icon" onclick="loadKnowledgeBase()">重置</button>
</div>
<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>
<!-- Graph Analysis View -->
<div id="graphAnalysisView" class="graph-analysis-panel">
<div class="graph-analysis-header">
<h2>🕸️ 图分析</h2>
<p style="color:#888;">基于 Neo4j 的图数据库分析与可视化</p>
</div>
<div class="graph-analysis-stats" id="graphStats">
<div class="graph-stat-card">
<span class="graph-stat-value" id="statNodeCount">-</span>
<span class="graph-stat-label">节点数</span>
</div>
<div class="graph-stat-card">
<span class="graph-stat-value" id="statEdgeCount">-</span>
<span class="graph-stat-label">关系数</span>
</div>
<div class="graph-stat-card">
<span class="graph-stat-value" id="statDensity">-</span>
<span class="graph-stat-label">图密度</span>
</div>
<div class="graph-stat-card">
<span class="graph-stat-value" id="statComponents">-</span>
<span class="graph-stat-label">连通分量</span>
</div>
</div>
<div class="graph-analysis-content">
<div class="graph-analysis-sidebar">
<div class="graph-analysis-section">
<h4>🔍 最短路径查询</h4>
<div class="graph-query-form">
<div class="form-group">
<label>起点实体</label>
<select id="pathStartEntity"></select>
</div>
<div class="form-group">
<label>终点实体</label>
<select id="pathEndEntity"></select>
</div>
<button class="btn btn-small" onclick="findShortestPath()">查找路径</button>
</div>
</div>
<div class="graph-analysis-section">
<h4>📊 中心性分析</h4>
<div class="centrality-controls">
<select id="centralityMetric" onchange="loadCentralityAnalysis()">
<option value="degree">度中心性</option>
<option value="betweenness">中介中心性</option>
<option value="closeness">接近中心性</option>
</select>
<button class="btn btn-small" onclick="loadCentralityAnalysis()">刷新</button>
</div>
<div class="centrality-list" id="centralityList"></div>
</div>
<div class="graph-analysis-section">
<h4>👥 社区发现</h4>
<button class="btn btn-small" onclick="loadCommunities()">分析社区</button>
<div class="communities-list" id="communitiesList"></div>
</div>
<div class="graph-analysis-section">
<h4>🌐 邻居查询</h4>
<div class="form-group">
<label>选择实体</label>
<select id="neighborEntity"></select>
</div>
<button class="btn btn-small" onclick="findNeighbors()">查找邻居</button>
</div>
</div>
<div class="graph-analysis-visualization">
<div class="graph-viz-header">
<span id="graphVizTitle">图可视化</span>
<div class="graph-viz-controls">
<button class="btn-icon" onclick="resetGraphViz()">重置</button>
<button class="btn-icon" onclick="syncToNeo4j()">同步到 Neo4j</button>
</div>
</div>
<svg id="graphAnalysisSvg"></svg>
<div class="graph-analysis-legend">
<div class="legend-item">
<span class="legend-dot" style="background:#7b2cbf"></span>
<span>项目</span>
</div>
<div class="legend-item">
<span class="legend-dot" style="background:#00d4ff"></span>
<span>技术</span>
</div>
<div class="legend-item">
<span class="legend-dot" style="background:#ff6b6b"></span>
<span>人物</span>
</div>
<div class="legend-item">
<span class="legend-dot" style="background:#4ecdc4"></span>
<span>组织</span>
</div>
<div class="legend-item">
<span class="legend-dot" style="background:#666"></span>
<span>其他</span>
</div>
</div>
</div>
</div>
<div class="graph-analysis-results" id="graphAnalysisResults"></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>
<!-- API Keys Management View -->
<div id="apiKeysView" class="api-keys-panel">
<div class="api-keys-header">
<div>
<h2>🔑 API Key 管理</h2>
<p style="color:#888;">管理 API 访问密钥和调用统计</p>
</div>
<button class="btn" onclick="showCreateApiKeyModal()">+ 创建 API Key</button>
</div>
<div class="api-keys-content">
<div class="api-keys-stats">
<div class="api-key-stat-card">
<div class="api-key-stat-value" id="apiKeyTotalCount">-</div>
<div class="api-key-stat-label">总 API Keys</div>
</div>
<div class="api-key-stat-card">
<div class="api-key-stat-value" id="apiKeyActiveCount">-</div>
<div class="api-key-stat-label">活跃</div>
</div>
<div class="api-key-stat-card">
<div class="api-key-stat-value" id="apiKeyRevokedCount">-</div>
<div class="api-key-stat-label">已撤销</div>
</div>
<div class="api-key-stat-card">
<div class="api-key-stat-value" id="apiKeyTotalCalls">-</div>
<div class="api-key-stat-label">总调用次数</div>
</div>
</div>
<div class="api-keys-list" id="apiKeysList">
<div class="api-keys-list-header">
<span>名称 / Key</span>
<span>权限</span>
<span>限流</span>
<span>状态</span>
<span>调用次数</span>
<span>操作</span>
</div>
<div id="apiKeysListContent">
<div class="api-key-empty">
<p>加载中...</p>
</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 Attributes Modal -->
<div class="modal-overlay" id="attributesModal">
<div class="modal" style="max-width: 600px;">
<h3 class="modal-header">实体属性</h3>
<div id="attributesContent">
<div class="attributes-list" id="attributesList"></div>
<div class="attributes-add-form" id="attributesAddForm" style="display:none;">
<h4 style="color:#888;font-size:0.9rem;margin:16px 0 12px;">添加新属性</h4>
<div class="form-group">
<label>属性名称</label>
<input type="text" id="attrName" placeholder="如: 职位、部门、年龄">
</div>
<div class="form-group">
<label>属性类型</label>
<select id="attrType" onchange="onAttrTypeChange()">
<option value="text">文本</option>
<option value="number">数字</option>
<option value="date">日期</option>
<option value="select">单选</option>
<option value="multiselect">多选</option>
</select>
</div>
<div class="form-group" id="attrOptionsGroup" style="display:none;">
<label>选项 (用逗号分隔)</label>
<input type="text" id="attrOptions" placeholder="选项1, 选项2, 选项3">
</div>
<div class="form-group">
<label>属性值</label>
<div id="attrValueContainer">
<input type="text" id="attrValue" placeholder="输入属性值">
</div>
</div>
<div class="form-group">
<label>变更原因 (可选)</label>
<input type="text" id="attrChangeReason" placeholder="为什么添加/修改这个属性">
</div>
</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideAttributesModal()">关闭</button>
<button class="btn" onclick="toggleAddAttributeForm()" id="toggleAddAttrBtn">添加属性</button>
<button class="btn" onclick="saveAttribute()" id="saveAttrBtn" style="display:none;">保存</button>
</div>
</div>
</div>
<!-- Attribute History Modal -->
<div class="modal-overlay" id="attrHistoryModal">
<div class="modal" style="max-width: 700px;">
<h3 class="modal-header">属性变更历史</h3>
<div id="attrHistoryContent" style="max-height: 400px; overflow-y: auto;">
<p style="color:#666;">加载中...</p>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideAttrHistoryModal()">关闭</button>
</div>
</div>
</div>
<!-- Attribute Templates Modal -->
<div class="modal-overlay" id="attrTemplatesModal">
<div class="modal" style="max-width: 700px;">
<h3 class="modal-header">属性模板管理</h3>
<div id="attrTemplatesContent">
<div class="templates-list" id="templatesList"></div>
<div class="template-form" id="templateForm" style="display:none; margin-top: 20px; padding-top: 20px; border-top: 1px solid #333;">
<h4 style="color:#888;font-size:0.9rem;margin-bottom:12px;">新建模板</h4>
<div class="form-group">
<label>模板名称</label>
<input type="text" id="templateName" placeholder="如: 职位">
</div>
<div class="form-group">
<label>类型</label>
<select id="templateType" onchange="onTemplateTypeChange()">
<option value="text">文本</option>
<option value="number">数字</option>
<option value="date">日期</option>
<option value="select">单选</option>
<option value="multiselect">多选</option>
</select>
</div>
<div class="form-group">
<label>描述 (可选)</label>
<input type="text" id="templateDesc" placeholder="属性的用途说明">
</div>
<div class="form-group" id="templateOptionsGroup" style="display:none;">
<label>选项 (用逗号分隔)</label>
<input type="text" id="templateOptions" placeholder="选项1, 选项2, 选项3">
</div>
<div class="form-group">
<label>
<input type="checkbox" id="templateRequired"> 必填属性
</label>
</div>
<div class="form-group">
<label>默认值 (可选)</label>
<input type="text" id="templateDefault" placeholder="默认值">
</div>
</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideAttrTemplatesModal()">关闭</button>
<button class="btn" onclick="toggleTemplateForm()" id="toggleTemplateBtn">新建模板</button>
<button class="btn" onclick="saveTemplate()" id="saveTemplateBtn" style="display:none;">保存模板</button>
</div>
</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>
<!-- Export Panel Modal -->
<div class="modal-overlay" id="exportPanelModal" style="display:none;z-index:2000;">
<div class="modal" style="max-width:600px;">
<div class="modal-header">
<h3>📥 导出数据</h3>
<button class="close-btn" onclick="hideExportPanel()">×</button>
</div>
<div class="modal-body">
<div class="export-section">
<h4 style="margin-bottom:12px;color:#00d4ff;">🎨 知识图谱</h4>
<div class="export-options">
<button class="export-btn" onclick="exportGraph('svg')">
<span class="export-icon">🖼️</span>
<span class="export-label">导出 SVG</span>
<span class="export-desc">矢量图,可编辑</span>
</button>
<button class="export-btn" onclick="exportGraph('png')">
<span class="export-icon">🖼️</span>
<span class="export-label">导出 PNG</span>
<span class="export-desc">图片格式</span>
</button>
</div>
</div>
<div class="export-section" style="margin-top:20px;">
<h4 style="margin-bottom:12px;color:#00d4ff;">📊 数据导出</h4>
<div class="export-options">
<button class="export-btn" onclick="exportEntities('excel')">
<span class="export-icon">📑</span>
<span class="export-label">实体 Excel</span>
<span class="export-desc">包含所有属性</span>
</button>
<button class="export-btn" onclick="exportEntities('csv')">
<span class="export-icon">📄</span>
<span class="export-label">实体 CSV</span>
<span class="export-desc">逗号分隔</span>
</button>
<button class="export-btn" onclick="exportRelations('csv')">
<span class="export-icon">🔗</span>
<span class="export-label">关系 CSV</span>
<span class="export-desc">关系数据</span>
</button>
</div>
</div>
<div class="export-section" style="margin-top:20px;">
<h4 style="margin-bottom:12px;color:#00d4ff;">📄 文档导出</h4>
<div class="export-options">
<button class="export-btn" onclick="exportReport('pdf')">
<span class="export-icon">📕</span>
<span class="export-label">项目报告 PDF</span>
<span class="export-desc">完整项目报告</span>
</button>
<button class="export-btn" onclick="exportProject('json')">
<span class="export-icon">🗂️</span>
<span class="export-label">项目 JSON</span>
<span class="export-desc">完整数据备份</span>
</button>
</div>
</div>
<div class="export-section" style="margin-top:20px;" id="transcriptExportSection" style="display:none;">
<h4 style="margin-bottom:12px;color:#00d4ff;">📝 当前转录文本</h4>
<div class="export-options">
<button class="export-btn" onclick="exportTranscript('markdown')">
<span class="export-icon">📝</span>
<span class="export-label">导出 Markdown</span>
<span class="export-desc">带实体标注</span>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- API Key Create Modal -->
<div class="modal-overlay" id="apiKeyCreateModal">
<div class="modal" style="max-width: 500px;">
<h3 class="modal-header">创建 API Key</h3>
<div class="api-key-modal-form">
<div class="api-key-form-group">
<label>名称 / 描述</label>
<input type="text" id="apiKeyName" placeholder="例如:移动应用开发">
</div>
<div class="api-key-form-group">
<label>权限</label>
<div class="api-key-permissions-select">
<label class="api-key-permission-checkbox">
<input type="checkbox" id="permRead" checked>
<span>读取 (read)</span>
</label>
<label class="api-key-permission-checkbox">
<input type="checkbox" id="permWrite">
<span>写入 (write)</span>
</label>
<label class="api-key-permission-checkbox">
<input type="checkbox" id="permDelete">
<span>删除 (delete)</span>
</label>
</div>
</div>
<div class="api-key-form-group">
<label>限流 (请求/分钟)</label>
<select id="apiKeyRateLimit">
<option value="60">60 (默认)</option>
<option value="120">120</option>
<option value="300">300</option>
<option value="600">600</option>
<option value="1000">1000</option>
</select>
</div>
<div class="api-key-form-group">
<label>过期时间 (可选)</label>
<select id="apiKeyExpires">
<option value="">永不过期</option>
<option value="7">7 天</option>
<option value="30">30 天</option>
<option value="90">90 天</option>
<option value="365">1 年</option>
</select>
</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideCreateApiKeyModal()">取消</button>
<button class="btn" onclick="createApiKey()">创建</button>
</div>
</div>
</div>
<!-- API Key Created Success Modal -->
<div class="modal-overlay" id="apiKeyCreatedModal">
<div class="modal api-key-created-modal" style="max-width: 600px;">
<h3 class="modal-header">✅ API Key 创建成功</h3>
<div class="warning">
⚠️ 请立即复制并保存此 API Key它只会显示一次之后无法再次查看。
</div>
<label style="color:#888;font-size:0.9rem;">你的 API Key</label>
<div class="api-key-value" id="createdApiKeyValue">
ak_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
</div>
<div style="display:flex;gap:12px;">
<button class="btn" onclick="copyApiKey()" style="flex:1;">📋 复制到剪贴板</button>
<button class="btn btn-secondary" onclick="hideApiKeyCreatedModal()" style="flex:1;">我已保存</button>
</div>
</div>
</div>
<!-- API Key Stats Modal -->
<div class="modal-overlay" id="apiKeyStatsModal">
<div class="modal api-key-stats-modal" style="max-width: 700px;">
<h3 class="modal-header" id="apiKeyStatsTitle">API Key 统计</h3>
<div class="stats-grid">
<div class="stat-item">
<div class="stat-value" id="statsTotalCalls">-</div>
<div class="stat-label">总调用次数</div>
</div>
<div class="stat-item">
<div class="stat-value" id="statsSuccessCalls">-</div>
<div class="stat-label">成功</div>
</div>
<div class="stat-item">
<div class="stat-value" id="statsErrorCalls">-</div>
<div class="stat-label">错误</div>
</div>
<div class="stat-item">
<div class="stat-value" id="statsAvgTime">-</div>
<div class="stat-label">平均响应时间 (ms)</div>
</div>
</div>
<h4 style="color:#888;font-size:0.9rem;margin:20px 0 12px;">最近调用日志</h4>
<div class="api-key-logs" id="apiKeyLogs">
<div class="api-key-empty">
<p>暂无调用记录</p>
</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="hideApiKeyStatsModal()">关闭</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="showEntityAttributes()">🏷️ 管理属性</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>