Add files via upload

This commit is contained in:
公明
2026-03-14 01:37:26 +08:00
committed by GitHub
parent b4da3e5d33
commit 62a83f6271
3 changed files with 175 additions and 20 deletions
+24 -4
View File
@@ -330,9 +330,19 @@ func (h *AgentHandler) AgentLoop(c *gin.Context) {
if remark == "" {
remark = conn.URL
}
finalMessage = fmt.Sprintf("[WebShell 助手上下文] 当前连接 ID:%s,备注:%s。可用工具(仅在该连接上操作时使用,connection_id 填 \"%s\"):webshell_exec、webshell_file_list、webshell_file_read、webshell_file_write。请根据用户输入决定下一步:若仅为问候、闲聊或简单问题,直接简短回复即可,不必调用工具;当用户明确需要执行命令、列目录、读写字件等操作时再调用上述工具。\n\n用户请求:%s",
finalMessage = fmt.Sprintf("[WebShell 助手上下文] 当前连接 ID:%s,备注:%s。可用工具(仅在该连接上操作时使用,connection_id 填 \"%s\"):webshell_exec、webshell_file_list、webshell_file_read、webshell_file_write、record_vulnerability、list_knowledge_risk_types、search_knowledge_base、list_skills、read_skill。请根据用户输入决定下一步:若仅为问候、闲聊或简单问题,直接简短回复即可,不必调用工具;当用户明确需要执行命令、列目录、读写文件、记录漏洞或检索知识库/查看 Skills 等操作时再调用上述工具。\n\n用户请求:%s",
conn.ID, remark, conn.ID, req.Message)
roleTools = []string{builtin.ToolWebshellExec, builtin.ToolWebshellFileList, builtin.ToolWebshellFileRead, builtin.ToolWebshellFileWrite}
roleTools = []string{
builtin.ToolWebshellExec,
builtin.ToolWebshellFileList,
builtin.ToolWebshellFileRead,
builtin.ToolWebshellFileWrite,
builtin.ToolRecordVulnerability,
builtin.ToolListKnowledgeRiskTypes,
builtin.ToolSearchKnowledgeBase,
builtin.ToolListSkills,
builtin.ToolReadSkill,
}
roleSkills = nil
} else if req.Role != "" && req.Role != "默认" {
if h.config.Roles != nil {
@@ -805,9 +815,19 @@ func (h *AgentHandler) AgentLoopStream(c *gin.Context) {
if remark == "" {
remark = conn.URL
}
finalMessage = fmt.Sprintf("[WebShell 助手上下文] 当前连接 ID:%s,备注:%s。可用工具(仅在该连接上操作时使用,connection_id 填 \"%s\"):webshell_exec、webshell_file_list、webshell_file_read、webshell_file_write。请根据用户输入决定下一步:若仅为问候、闲聊或简单问题,直接简短回复即可,不必调用工具;当用户明确需要执行命令、列目录、读写字件等操作时再调用上述工具。\n\n用户请求:%s",
finalMessage = fmt.Sprintf("[WebShell 助手上下文] 当前连接 ID:%s,备注:%s。可用工具(仅在该连接上操作时使用,connection_id 填 \"%s\"):webshell_exec、webshell_file_list、webshell_file_read、webshell_file_write、record_vulnerability、list_knowledge_risk_types、search_knowledge_base、list_skills、read_skill。请根据用户输入决定下一步:若仅为问候、闲聊或简单问题,直接简短回复即可,不必调用工具;当用户明确需要执行命令、列目录、读写文件、记录漏洞或检索知识库/查看 Skills 等操作时再调用上述工具。\n\n用户请求:%s",
conn.ID, remark, conn.ID, req.Message)
roleTools = []string{builtin.ToolWebshellExec, builtin.ToolWebshellFileList, builtin.ToolWebshellFileRead, builtin.ToolWebshellFileWrite}
roleTools = []string{
builtin.ToolWebshellExec,
builtin.ToolWebshellFileList,
builtin.ToolWebshellFileRead,
builtin.ToolWebshellFileWrite,
builtin.ToolRecordVulnerability,
builtin.ToolListKnowledgeRiskTypes,
builtin.ToolSearchKnowledgeBase,
builtin.ToolListSkills,
builtin.ToolReadSkill,
}
} else if req.Role != "" && req.Role != "默认" {
if h.config.Roles != nil {
if role, exists := h.config.Roles[req.Role]; exists && role.Enabled {
+63
View File
@@ -9285,6 +9285,69 @@ header {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
}
/* AI 助手 markdown 渲染优化:避免行间距离过大和内容横向溢出 */
.webshell-ai-msg.assistant {
/* markdown 里已经有块级元素,不需要再整体 pre-wrap,否则容易在块之间产生“空行”感 */
white-space: normal;
}
.webshell-ai-msg.assistant p,
.webshell-ai-msg.assistant ul,
.webshell-ai-msg.assistant ol,
.webshell-ai-msg.assistant pre,
.webshell-ai-msg.assistant blockquote {
margin-top: 0;
margin-bottom: 4px;
}
.webshell-ai-msg.assistant p:last-child,
.webshell-ai-msg.assistant ul:last-child,
.webshell-ai-msg.assistant ol:last-child,
.webshell-ai-msg.assistant pre:last-child,
.webshell-ai-msg.assistant blockquote:last-child {
margin-bottom: 0;
}
.webshell-ai-msg pre {
font-family: var(--font-mono, Menlo, Monaco, Consolas, "Courier New", monospace);
font-size: 0.82rem;
background: #020617;
color: #e5e7eb;
border-radius: 6px;
padding: 8px 10px;
overflow-x: auto;
max-width: 100%;
white-space: pre;
box-sizing: border-box;
}
.webshell-ai-msg pre code {
padding: 0;
background: transparent;
}
.webshell-ai-msg code {
font-family: var(--font-mono, Menlo, Monaco, Consolas, "Courier New", monospace);
font-size: 0.82rem;
background: rgba(15, 23, 42, 0.06);
color: inherit;
border-radius: 4px;
padding: 0.1em 0.4em;
white-space: normal;
box-sizing: border-box;
}
.webshell-ai-msg table {
width: 100%;
max-width: 100%;
border-collapse: collapse;
overflow-x: auto;
display: block;
}
.webshell-ai-msg table th,
.webshell-ai-msg table td {
border: 1px solid var(--border-color);
padding: 4px 6px;
font-size: 0.8rem;
}
.webshell-ai-msg ul,
.webshell-ai-msg ol {
padding-left: 20px;
}
.webshell-ai-input-row {
flex-shrink: 0;
display: flex;
+88 -16
View File
@@ -351,7 +351,15 @@ function webshellAiConvListSelect(conn, convId, messagesContainer, listEl) {
if (!content && role !== 'assistant') return;
var div = document.createElement('div');
div.className = 'webshell-ai-msg ' + (role === 'user' ? 'user' : 'assistant');
div.textContent = content;
if (role === 'user') {
div.textContent = content;
} else {
if (typeof formatMarkdown === 'function') {
div.innerHTML = formatMarkdown(content);
} else {
div.textContent = content;
}
}
messagesContainer.appendChild(div);
});
if (list.length === 0) {
@@ -546,7 +554,15 @@ function loadWebshellAiHistory(conn, messagesContainer) {
if (!content && role !== 'assistant') return;
var div = document.createElement('div');
div.className = 'webshell-ai-msg ' + (role === 'user' ? 'user' : 'assistant');
div.textContent = content;
if (role === 'user') {
div.textContent = content;
} else {
if (typeof formatMarkdown === 'function') {
div.innerHTML = formatMarkdown(content);
} else {
div.textContent = content;
}
}
messagesContainer.appendChild(div);
});
if (list.length === 0) {
@@ -598,11 +614,51 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
messagesContainer.appendChild(assistantDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
function appendTimelineItem(type, title, message) {
function appendTimelineItem(type, title, message, data) {
var item = document.createElement('div');
item.className = 'webshell-ai-timeline-item webshell-ai-timeline-' + type;
item.innerHTML = '<span class="webshell-ai-timeline-title">' + escapeHtml(title || message || '') + '</span>';
if (message && message !== title) item.innerHTML += '<div class="webshell-ai-timeline-msg">' + escapeHtml(message) + '</div>';
var html = '<span class="webshell-ai-timeline-title">' + escapeHtml(title || message || '') + '</span>';
// 工具调用入参
if (type === 'tool_call' && data) {
try {
var args = data.argumentsObj || (data.arguments ? JSON.parse(data.arguments) : null);
if (args && typeof args === 'object') {
var paramsLabel = (typeof window.t === 'function') ? window.t('timeline.params') : '参数:';
html += '<div class="webshell-ai-timeline-msg"><div class="tool-arg-section"><strong>' +
escapeHtml(paramsLabel) +
'</strong><pre class="tool-args">' +
escapeHtml(JSON.stringify(args, null, 2)) +
'</pre></div></div>';
}
} catch (e) {
// JSON 解析失败时忽略参数详情,避免打断主流程
}
} else if (type === 'tool_result' && data) {
// 工具调用出参
var isError = data.isError || data.success === false;
var noResultText = (typeof window.t === 'function') ? window.t('timeline.noResult') : '无结果';
var result = data.result != null ? data.result : (data.error != null ? data.error : noResultText);
var resultStr = (typeof result === 'string') ? result : JSON.stringify(result);
var execResultLabel = (typeof window.t === 'function') ? window.t('timeline.executionResult') : '执行结果:';
var execIdLabel = (typeof window.t === 'function') ? window.t('timeline.executionId') : '执行ID:';
html += '<div class="webshell-ai-timeline-msg"><div class="tool-result-section ' +
(isError ? 'error' : 'success') +
'"><strong>' + escapeHtml(execResultLabel) + '</strong><pre class="tool-result">' +
escapeHtml(resultStr) +
'</pre>' +
(data.executionId ? '<div class="tool-execution-id"><span>' +
escapeHtml(execIdLabel) +
'</span> <code>' +
escapeHtml(String(data.executionId)) +
'</code></div>' : '') +
'</div></div>';
} else if (message && message !== title) {
html += '<div class="webshell-ai-timeline-msg">' + escapeHtml(message) + '</div>';
}
item.innerHTML = html;
timelineContainer.appendChild(item);
timelineContainer.classList.add('has-items');
messagesContainer.scrollTop = messagesContainer.scrollHeight;
@@ -663,38 +719,54 @@ function runWebshellAiSend(conn, inputEl, sendBtn, messagesContainer) {
}
} else if (eventData.type === 'error' && eventData.message) {
streamingTypingId += 1;
appendTimelineItem('error', '错误', eventData.message);
assistantDiv.textContent = '错误: ' + eventData.message;
var errLabel = (typeof window.t === 'function') ? window.t('chat.error') : '错误';
appendTimelineItem('error', '❌ ' + errLabel, eventData.message, eventData.data);
assistantDiv.textContent = errLabel + ': ' + eventData.message;
} else if (eventData.type === 'progress' && eventData.message) {
appendTimelineItem('progress', '🔍 ' + eventData.message, '');
var progressMsg = (typeof window.translateProgressMessage === 'function')
? window.translateProgressMessage(eventData.message)
: eventData.message;
appendTimelineItem('progress', '🔍 ' + progressMsg, '', eventData.data);
if (!streamingTarget) assistantDiv.textContent = '…';
} else if (eventData.type === 'iteration') {
var iterN = (eventData.data && eventData.data.iteration) || 0;
var iterTitle = iterN ? '🔍 第 ' + iterN + ' 轮迭代' : ('🔍 ' + (eventData.message || '迭代'));
appendTimelineItem('iteration', iterTitle, eventData.message || '');
var iterTitle = (typeof window.t === 'function')
? window.t('chat.iterationRound', { n: iterN || 1 })
: (iterN ? ('第 ' + iterN + ' 轮迭代') : (eventData.message || '迭代'));
appendTimelineItem('iteration', '🔍 ' + iterTitle, eventData.message || '', eventData.data);
if (!streamingTarget) assistantDiv.textContent = '…';
} else if (eventData.type === 'thinking' && eventData.message) {
appendTimelineItem('thinking', '🤔 AI 思考', eventData.message);
var thinkLabel = (typeof window.t === 'function') ? window.t('chat.aiThinking') : 'AI 思考';
appendTimelineItem('thinking', '🤔 ' + thinkLabel, eventData.message, eventData.data);
if (!streamingTarget) assistantDiv.textContent = '…';
} else if (eventData.type === 'tool_calls_detected' && eventData.data) {
var count = eventData.data.count || 0;
appendTimelineItem('tool_calls_detected', '🔧 检测到 ' + count + ' 个工具调用', eventData.message || '');
var detectedLabel = (typeof window.t === 'function')
? window.t('chat.toolCallsDetected', { count: count })
: ('检测到 ' + count + ' 个工具调用');
appendTimelineItem('tool_calls_detected', '🔧 ' + detectedLabel, eventData.message || '', eventData.data);
if (!streamingTarget) assistantDiv.textContent = '…';
} else if (eventData.type === 'tool_call' && eventData.data) {
var d = eventData.data;
var tn = d.toolName || '未知工具';
var idx = d.index || 0;
var total = d.total || 0;
var title = '🔧 调用: ' + tn + (total ? ' (' + idx + '/' + total + ')' : '');
appendTimelineItem('tool_call', title, eventData.message || '');
var callTitle = (typeof window.t === 'function')
? window.t('chat.callTool', { name: tn, index: idx, total: total })
: ('调用: ' + tn + (total ? ' (' + idx + '/' + total + ')' : ''));
var title = '🔧 ' + callTitle;
appendTimelineItem('tool_call', title, eventData.message || '', eventData.data);
if (!streamingTarget) assistantDiv.textContent = '…';
} else if (eventData.type === 'tool_result' && eventData.data) {
var dr = eventData.data;
var success = dr.success !== false;
var tname = dr.toolName || '工具';
var title = (success ? '✅ ' : '❌ ') + tname + (success ? ' 执行完成' : ' 执行失败');
var titleText = (typeof window.t === 'function')
? (success ? window.t('chat.toolExecComplete', { name: tname }) : window.t('chat.toolExecFailed', { name: tname }))
: (tname + (success ? ' 执行完成' : ' 执行失败'));
var title = (success ? '✅ ' : '❌ ') + titleText;
var sub = eventData.message || (dr.result ? String(dr.result).slice(0, 300) : '');
appendTimelineItem('tool_result', title, sub);
appendTimelineItem('tool_result', title, sub, eventData.data);
if (!streamingTarget) assistantDiv.textContent = '…';
}
} catch (e) { /* ignore parse error */ }