Add files via upload

This commit is contained in:
公明
2025-12-20 17:36:40 +08:00
committed by GitHub
parent b659fb7445
commit abc4085c8a
21 changed files with 5234 additions and 46 deletions
+1017 -4
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -908,6 +908,13 @@ function renderProcessDetails(messageId, processDetails) {
const success = data.success !== false;
const statusIcon = success ? '✅' : '❌';
itemTitle = `${statusIcon} 工具 ${escapeHtml(toolName)} 执行${success ? '完成' : '失败'}`;
// 如果是知识检索工具,添加特殊标记
if (toolName === 'search_knowledge_base' && success) {
itemTitle = `📚 ${itemTitle} - 知识检索`;
}
} else if (eventType === 'knowledge_retrieval') {
itemTitle = '📚 知识检索';
} else if (eventType === 'error') {
itemTitle = '❌ 错误';
} else if (eventType === 'cancelled') {
File diff suppressed because it is too large Load Diff
+15 -2
View File
@@ -8,7 +8,7 @@ function initRouter() {
// 从URL hash读取页面(如果有)
const hash = window.location.hash.slice(1);
if (hash && ['chat', 'mcp-monitor', 'mcp-management', 'settings'].includes(hash)) {
if (hash && ['chat', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'settings'].includes(hash)) {
switchPage(hash);
}
}
@@ -58,6 +58,19 @@ function updateNavState(pageId) {
mcpItem.classList.add('expanded');
}
const submenuItem = document.querySelector(`.nav-submenu-item[data-page="${pageId}"]`);
if (submenuItem) {
submenuItem.classList.add('active');
}
} else if (pageId === 'knowledge-management' || pageId === 'knowledge-retrieval-logs') {
// 知识子菜单项
const knowledgeItem = document.querySelector('.nav-item[data-page="knowledge"]');
if (knowledgeItem) {
knowledgeItem.classList.add('active');
// 展开知识子菜单
knowledgeItem.classList.add('expanded');
}
const submenuItem = document.querySelector(`.nav-submenu-item[data-page="${pageId}"]`);
if (submenuItem) {
submenuItem.classList.add('active');
@@ -202,7 +215,7 @@ document.addEventListener('DOMContentLoaded', function() {
// 监听hash变化
window.addEventListener('hashchange', function() {
const hash = window.location.hash.slice(1);
if (hash && ['chat', 'mcp-monitor', 'mcp-management', 'settings'].includes(hash)) {
if (hash && ['chat', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'settings'].includes(hash)) {
switchPage(hash);
}
});
+75
View File
@@ -96,6 +96,60 @@ async function loadConfig(loadTools = true) {
// 填充Agent配置
document.getElementById('agent-max-iterations').value = currentConfig.agent.max_iterations || 30;
// 填充知识库配置
const knowledgeEnabledCheckbox = document.getElementById('knowledge-enabled');
if (knowledgeEnabledCheckbox) {
knowledgeEnabledCheckbox.checked = currentConfig.knowledge?.enabled !== false;
}
// 填充知识库详细配置
if (currentConfig.knowledge) {
const knowledge = currentConfig.knowledge;
// 基本配置
const basePathInput = document.getElementById('knowledge-base-path');
if (basePathInput) {
basePathInput.value = knowledge.base_path || 'knowledge_base';
}
// 嵌入模型配置
const embeddingProviderSelect = document.getElementById('knowledge-embedding-provider');
if (embeddingProviderSelect) {
embeddingProviderSelect.value = knowledge.embedding?.provider || 'openai';
}
const embeddingModelInput = document.getElementById('knowledge-embedding-model');
if (embeddingModelInput) {
embeddingModelInput.value = knowledge.embedding?.model || '';
}
const embeddingBaseUrlInput = document.getElementById('knowledge-embedding-base-url');
if (embeddingBaseUrlInput) {
embeddingBaseUrlInput.value = knowledge.embedding?.base_url || '';
}
const embeddingApiKeyInput = document.getElementById('knowledge-embedding-api-key');
if (embeddingApiKeyInput) {
embeddingApiKeyInput.value = knowledge.embedding?.api_key || '';
}
// 检索配置
const retrievalTopKInput = document.getElementById('knowledge-retrieval-top-k');
if (retrievalTopKInput) {
retrievalTopKInput.value = knowledge.retrieval?.top_k || 5;
}
const retrievalThresholdInput = document.getElementById('knowledge-retrieval-similarity-threshold');
if (retrievalThresholdInput) {
retrievalThresholdInput.value = knowledge.retrieval?.similarity_threshold || 0.7;
}
const retrievalWeightInput = document.getElementById('knowledge-retrieval-hybrid-weight');
if (retrievalWeightInput) {
retrievalWeightInput.value = knowledge.retrieval?.hybrid_weight || 0.7;
}
}
// 只有在需要时才加载工具列表(MCP管理页面需要,系统设置页面不需要)
if (loadTools) {
// 设置每页显示数量(会在分页控件渲染时设置)
@@ -538,6 +592,26 @@ async function applySettings() {
}
// 收集配置
const knowledgeEnabledCheckbox = document.getElementById('knowledge-enabled');
const knowledgeEnabled = knowledgeEnabledCheckbox ? knowledgeEnabledCheckbox.checked : true;
// 收集知识库配置
const knowledgeConfig = {
enabled: knowledgeEnabled,
base_path: document.getElementById('knowledge-base-path')?.value.trim() || 'knowledge_base',
embedding: {
provider: document.getElementById('knowledge-embedding-provider')?.value || 'openai',
model: document.getElementById('knowledge-embedding-model')?.value.trim() || '',
base_url: document.getElementById('knowledge-embedding-base-url')?.value.trim() || '',
api_key: document.getElementById('knowledge-embedding-api-key')?.value.trim() || ''
},
retrieval: {
top_k: parseInt(document.getElementById('knowledge-retrieval-top-k')?.value) || 5,
similarity_threshold: parseFloat(document.getElementById('knowledge-retrieval-similarity-threshold')?.value) || 0.7,
hybrid_weight: parseFloat(document.getElementById('knowledge-retrieval-hybrid-weight')?.value) || 0.7
}
};
const config = {
openai: {
api_key: apiKey,
@@ -547,6 +621,7 @@ async function applySettings() {
agent: {
max_iterations: parseInt(document.getElementById('agent-max-iterations').value) || 30
},
knowledge: knowledgeConfig,
tools: []
};
+212
View File
@@ -95,6 +95,27 @@
</div>
</div>
</div>
<div class="nav-item nav-item-has-submenu" data-page="knowledge">
<div class="nav-item-content" data-title="知识" onclick="toggleSubmenu('knowledge')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 7h6M10 11h6M10 15h4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span>知识</span>
<svg class="submenu-arrow" width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="nav-submenu">
<div class="nav-submenu-item" data-page="knowledge-retrieval-logs" onclick="switchPage('knowledge-retrieval-logs')">
<span>检索历史</span>
</div>
<div class="nav-submenu-item" data-page="knowledge-management" onclick="switchPage('knowledge-management')">
<span>知识管理</span>
</div>
</div>
</div>
<div class="nav-item" data-page="settings">
<div class="nav-item-content" data-title="系统设置" onclick="switchPage('settings')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -233,6 +254,106 @@
</div>
</div>
<!-- 知识管理页面 -->
<div id="page-knowledge-management" class="page">
<div class="page-header">
<h2>知识管理</h2>
<div class="page-header-actions">
<button class="btn-secondary" onclick="refreshKnowledgeBase()">刷新</button>
<button class="btn-secondary" onclick="rebuildKnowledgeIndex()">重建索引</button>
<button class="btn-primary" onclick="showAddKnowledgeItemModal()">添加知识</button>
</div>
</div>
<div class="page-content">
<div class="knowledge-controls">
<div class="knowledge-stats-bar" id="knowledge-stats">
<div class="knowledge-stat-item">
<span class="knowledge-stat-label">总知识项</span>
<span class="knowledge-stat-value">-</span>
</div>
<div class="knowledge-stat-item">
<span class="knowledge-stat-label">分类数</span>
<span class="knowledge-stat-value">-</span>
</div>
<div class="knowledge-stat-item">
<span class="knowledge-stat-label">总内容</span>
<span class="knowledge-stat-value">-</span>
</div>
</div>
<div id="knowledge-index-progress" style="display: none; margin-bottom: 16px;"></div>
<div class="knowledge-filters">
<label>
分类筛选
<div class="custom-select-wrapper">
<div class="custom-select" id="knowledge-category-filter-wrapper">
<div class="custom-select-trigger" id="knowledge-category-filter-trigger">
<span>全部</span>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="custom-select-dropdown" id="knowledge-category-filter-dropdown">
<div class="custom-select-option" data-value="" onclick="selectKnowledgeCategory('')">全部</div>
</div>
</div>
</div>
</label>
<div class="search-box">
<input type="text" id="knowledge-search" placeholder="搜索知识..." oninput="searchKnowledgeItems()" />
<button class="btn-search" onclick="searchKnowledgeItems()" title="搜索">🔍</button>
</div>
</div>
</div>
<div id="knowledge-items-list" class="knowledge-items-list">
<div class="loading-spinner">加载中...</div>
</div>
</div>
</div>
<!-- 知识检索历史页面 -->
<div id="page-knowledge-retrieval-logs" class="page">
<div class="page-header">
<h2>检索历史</h2>
<button class="btn-secondary" onclick="refreshRetrievalLogs()">刷新</button>
</div>
<div class="page-content">
<div class="retrieval-logs-controls">
<div class="retrieval-stats-bar" id="retrieval-stats">
<div class="retrieval-stat-item">
<span class="retrieval-stat-label">总检索次数</span>
<span class="retrieval-stat-value">-</span>
</div>
<div class="retrieval-stat-item">
<span class="retrieval-stat-label">成功检索</span>
<span class="retrieval-stat-value">-</span>
</div>
<div class="retrieval-stat-item">
<span class="retrieval-stat-label">成功率</span>
<span class="retrieval-stat-value">-</span>
</div>
<div class="retrieval-stat-item">
<span class="retrieval-stat-label">检索到知识项</span>
<span class="retrieval-stat-value">-</span>
</div>
</div>
<div class="retrieval-logs-filters">
<label>
对话ID
<input type="text" id="retrieval-logs-conversation-id" placeholder="可选:筛选特定对话" />
</label>
<label>
消息ID
<input type="text" id="retrieval-logs-message-id" placeholder="可选:筛选特定消息" />
</label>
<button class="btn-secondary" onclick="filterRetrievalLogs()">筛选</button>
</div>
</div>
<div id="retrieval-logs-list" class="retrieval-logs-list">
<div class="loading-spinner">加载中...</div>
</div>
</div>
</div>
<!-- 系统设置页面 -->
<div id="page-settings" class="page">
<div class="page-header">
@@ -289,6 +410,68 @@
</div>
</div>
<!-- 知识库配置 -->
<div class="settings-subsection">
<h4>知识库配置</h4>
<div class="settings-form">
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" id="knowledge-enabled" class="modern-checkbox" />
<span class="checkbox-custom"></span>
<span class="checkbox-text">启用知识检索功能</span>
</label>
</div>
<div class="form-group">
<label for="knowledge-base-path">知识库路径</label>
<input type="text" id="knowledge-base-path" placeholder="knowledge_base" />
<small class="form-hint">相对于配置文件所在目录的路径</small>
</div>
<div class="settings-subsection-header">
<h5>嵌入模型配置</h5>
</div>
<div class="form-group">
<label for="knowledge-embedding-provider">提供商</label>
<select id="knowledge-embedding-provider">
<option value="openai">OpenAI</option>
</select>
</div>
<div class="form-group">
<label for="knowledge-embedding-model">模型名称</label>
<input type="text" id="knowledge-embedding-model" placeholder="text-embedding-v4" />
</div>
<div class="form-group">
<label for="knowledge-embedding-base-url">Base URL</label>
<input type="text" id="knowledge-embedding-base-url" placeholder="留空则使用OpenAI配置的base_url" />
<small class="form-hint">留空则使用OpenAI配置的base_url</small>
</div>
<div class="form-group">
<label for="knowledge-embedding-api-key">API Key</label>
<input type="password" id="knowledge-embedding-api-key" placeholder="留空则使用OpenAI配置的api_key" />
<small class="form-hint">留空则使用OpenAI配置的api_key</small>
</div>
<div class="settings-subsection-header">
<h5>检索配置</h5>
</div>
<div class="form-group">
<label for="knowledge-retrieval-top-k">Top-K 结果数量</label>
<input type="number" id="knowledge-retrieval-top-k" min="1" max="20" placeholder="5" />
<small class="form-hint">检索返回的Top-K结果数量</small>
</div>
<div class="form-group">
<label for="knowledge-retrieval-similarity-threshold">相似度阈值</label>
<input type="number" id="knowledge-retrieval-similarity-threshold" min="0" max="1" step="0.1" placeholder="0.7" />
<small class="form-hint">相似度阈值(0-1),低于此值的结果将被过滤</small>
</div>
<div class="form-group">
<label for="knowledge-retrieval-hybrid-weight">混合检索权重</label>
<input type="number" id="knowledge-retrieval-hybrid-weight" min="0" max="1" step="0.1" placeholder="0.7" />
<small class="form-hint">向量检索的权重(0-1),1.0表示纯向量检索,0.0表示纯关键词检索</small>
</div>
</div>
</div>
<div class="settings-actions">
<button class="btn-primary" onclick="applySettings()">应用配置</button>
</div>
@@ -540,11 +723,40 @@
<script src="https://cdn.jsdelivr.net/npm/dagre@0.8.5/dist/dagre.min.js"></script>
<!-- dagre layout for hierarchical layout -->
<script src="https://cdn.jsdelivr.net/npm/cytoscape-dagre@2.5.0/cytoscape-dagre.min.js"></script>
<!-- 知识项编辑模态框 -->
<div id="knowledge-item-modal" class="modal">
<div class="modal-content" style="max-width: 900px;">
<div class="modal-header">
<h2 id="knowledge-item-modal-title">添加知识</h2>
<span class="modal-close" onclick="closeKnowledgeItemModal()">&times;</span>
</div>
<div class="modal-body">
<div class="form-group">
<label for="knowledge-item-category">分类(风险类型)<span style="color: red;">*</span></label>
<input type="text" id="knowledge-item-category" placeholder="例如:SQL注入" required />
</div>
<div class="form-group">
<label for="knowledge-item-title">标题<span style="color: red;">*</span></label>
<input type="text" id="knowledge-item-title" placeholder="知识项标题" required />
</div>
<div class="form-group">
<label for="knowledge-item-content">内容(Markdown格式)<span style="color: red;">*</span></label>
<textarea id="knowledge-item-content" rows="20" placeholder="输入知识内容,支持Markdown格式..." style="font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 0.875rem; line-height: 1.5;" required></textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeKnowledgeItemModal()">取消</button>
<button class="btn-primary" onclick="saveKnowledgeItem()">保存</button>
</div>
</div>
</div>
<script src="/static/js/auth.js"></script>
<script src="/static/js/router.js"></script>
<script src="/static/js/monitor.js"></script>
<script src="/static/js/chat.js"></script>
<script src="/static/js/settings.js"></script>
<script src="/static/js/knowledge.js"></script>
</body>
</html>