mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-06 12:37:56 +02:00
Add files via upload
This commit is contained in:
+125
-1
@@ -304,7 +304,131 @@ header {
|
||||
word-break: break-word;
|
||||
line-height: 1.6;
|
||||
box-shadow: var(--shadow-sm);
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* Markdown 样式 */
|
||||
.message-bubble p {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.message-bubble p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.message-bubble p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.message-bubble strong,
|
||||
.message-bubble b {
|
||||
font-weight: 600;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.message-bubble em,
|
||||
.message-bubble i {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.message-bubble code {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.message.user .message-bubble code {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.message-bubble pre {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
margin: 0.5em 0;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.875em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.message.user .message-bubble pre {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.message-bubble pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.message-bubble ul,
|
||||
.message-bubble ol {
|
||||
margin: 0.5em 0;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
.message-bubble li {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
|
||||
.message-bubble blockquote {
|
||||
border-left: 3px solid var(--border-color);
|
||||
padding-left: 1em;
|
||||
margin: 0.5em 0;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.message-bubble h1,
|
||||
.message-bubble h2,
|
||||
.message-bubble h3,
|
||||
.message-bubble h4,
|
||||
.message-bubble h5,
|
||||
.message-bubble h6 {
|
||||
margin: 0.8em 0 0.4em 0;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.message-bubble h1:first-child,
|
||||
.message-bubble h2:first-child,
|
||||
.message-bubble h3:first-child,
|
||||
.message-bubble h4:first-child,
|
||||
.message-bubble h5:first-child,
|
||||
.message-bubble h6:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.message-bubble h1 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.message-bubble h2 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.message-bubble h3 {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.message-bubble hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.message-bubble a {
|
||||
color: var(--accent-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.message-bubble a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.message.user .message-bubble a {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.message.user .message-bubble {
|
||||
|
||||
+139
-21
@@ -15,11 +15,15 @@ async function sendMessage() {
|
||||
addMessage('user', message);
|
||||
input.value = '';
|
||||
|
||||
// 显示加载状态
|
||||
const loadingId = addMessage('system', '正在处理中...');
|
||||
// 创建进度消息容器
|
||||
const progressId = addMessage('system', '正在处理中...');
|
||||
const progressElement = document.getElementById(progressId);
|
||||
const progressBubble = progressElement.querySelector('.message-bubble');
|
||||
let assistantMessageId = null;
|
||||
let mcpExecutionIds = [];
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/agent-loop', {
|
||||
const response = await fetch('/api/agent-loop/stream', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -30,30 +34,124 @@ async function sendMessage() {
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error('请求失败: ' + response.status);
|
||||
}
|
||||
|
||||
// 移除加载消息
|
||||
removeMessage(loadingId);
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
if (response.ok) {
|
||||
// 更新当前对话ID
|
||||
if (data.conversationId) {
|
||||
currentConversationId = data.conversationId;
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop(); // 保留最后一个不完整的行
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const eventData = JSON.parse(line.slice(6));
|
||||
handleStreamEvent(eventData, progressElement, progressBubble, progressId,
|
||||
() => assistantMessageId, (id) => { assistantMessageId = id; },
|
||||
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
|
||||
} catch (e) {
|
||||
console.error('解析事件数据失败:', e, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理剩余的buffer
|
||||
if (buffer.trim()) {
|
||||
const lines = buffer.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const eventData = JSON.parse(line.slice(6));
|
||||
handleStreamEvent(eventData, progressElement, progressBubble, progressId,
|
||||
() => assistantMessageId, (id) => { assistantMessageId = id; },
|
||||
() => mcpExecutionIds, (ids) => { mcpExecutionIds = ids; });
|
||||
} catch (e) {
|
||||
console.error('解析事件数据失败:', e, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
removeMessage(progressId);
|
||||
addMessage('system', '错误: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理流式事件
|
||||
function handleStreamEvent(event, progressElement, progressBubble, progressId,
|
||||
getAssistantId, setAssistantId, getMcpIds, setMcpIds) {
|
||||
switch (event.type) {
|
||||
case 'progress':
|
||||
// 更新进度消息
|
||||
progressBubble.textContent = event.message;
|
||||
break;
|
||||
|
||||
case 'tool_call':
|
||||
// 显示工具调用信息
|
||||
const toolInfo = event.data || {};
|
||||
const toolName = toolInfo.toolName || '未知工具';
|
||||
const index = toolInfo.index || 0;
|
||||
const total = toolInfo.total || 0;
|
||||
progressBubble.innerHTML = `🔧 正在调用工具: <strong>${escapeHtml(toolName)}</strong> (${index}/${total})`;
|
||||
break;
|
||||
|
||||
case 'tool_result':
|
||||
// 显示工具执行结果
|
||||
const resultInfo = event.data || {};
|
||||
const resultToolName = resultInfo.toolName || '未知工具';
|
||||
const success = resultInfo.success !== false;
|
||||
const statusIcon = success ? '✅' : '❌';
|
||||
progressBubble.innerHTML = `${statusIcon} 工具 <strong>${escapeHtml(resultToolName)}</strong> 执行${success ? '完成' : '失败'}`;
|
||||
break;
|
||||
|
||||
case 'response':
|
||||
// 移除进度消息,显示最终回复
|
||||
removeMessage(progressId);
|
||||
const responseData = event.data || {};
|
||||
const mcpIds = responseData.mcpExecutionIds || [];
|
||||
setMcpIds(mcpIds);
|
||||
|
||||
// 更新对话ID
|
||||
if (responseData.conversationId) {
|
||||
currentConversationId = responseData.conversationId;
|
||||
updateActiveConversation();
|
||||
}
|
||||
|
||||
// 如果有MCP执行ID,显示所有调用
|
||||
const mcpIds = data.mcpExecutionIds || [];
|
||||
addMessage('assistant', data.response, mcpIds);
|
||||
// 添加助手回复
|
||||
const assistantId = addMessage('assistant', event.message, mcpIds);
|
||||
setAssistantId(assistantId);
|
||||
|
||||
// 刷新对话列表
|
||||
loadConversations();
|
||||
} else {
|
||||
addMessage('system', '错误: ' + (data.error || '未知错误'));
|
||||
}
|
||||
} catch (error) {
|
||||
removeMessage(loadingId);
|
||||
addMessage('system', '错误: ' + error.message);
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
// 显示错误
|
||||
removeMessage(progressId);
|
||||
addMessage('system', '错误: ' + event.message);
|
||||
break;
|
||||
|
||||
case 'done':
|
||||
// 完成,确保进度消息已移除
|
||||
if (progressElement && progressElement.parentNode) {
|
||||
removeMessage(progressId);
|
||||
}
|
||||
// 更新对话ID
|
||||
if (event.data && event.data.conversationId) {
|
||||
currentConversationId = event.data.conversationId;
|
||||
updateActiveConversation();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +186,28 @@ function addMessage(role, content, mcpExecutionIds = null) {
|
||||
// 创建消息气泡
|
||||
const bubble = document.createElement('div');
|
||||
bubble.className = 'message-bubble';
|
||||
// 处理换行和格式化
|
||||
const formattedContent = content.replace(/\n/g, '<br>');
|
||||
|
||||
// 解析 Markdown 格式
|
||||
let formattedContent;
|
||||
if (typeof marked !== 'undefined') {
|
||||
// 使用 marked.js 解析 Markdown
|
||||
try {
|
||||
// 配置 marked 选项
|
||||
marked.setOptions({
|
||||
breaks: true, // 支持换行
|
||||
gfm: true, // 支持 GitHub Flavored Markdown
|
||||
});
|
||||
formattedContent = marked.parse(content);
|
||||
} catch (e) {
|
||||
console.error('Markdown 解析失败:', e);
|
||||
// 降级处理:转义 HTML 并保留换行
|
||||
formattedContent = escapeHtml(content).replace(/\n/g, '<br>');
|
||||
}
|
||||
} else {
|
||||
// 如果没有 marked.js,使用简单处理
|
||||
formattedContent = escapeHtml(content).replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
bubble.innerHTML = formattedContent;
|
||||
contentWrapper.appendChild(bubble);
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Marked.js for Markdown parsing -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked@11.1.1/marked.min.js"></script>
|
||||
<script src="/static/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user