mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-04-01 00:30:33 +02:00
Add files via upload
This commit is contained in:
89
README.md
89
README.md
@@ -1,6 +1,6 @@
|
||||
# CyberStrikeAI
|
||||
|
||||
🚀 **下一代AI自主渗透测试平台** - 基于Golang构建,内置98+安全工具,支持灵活扩展自定义工具,通过MCP协议实现AI智能决策与自动化执行,让安全测试像对话一样简单。
|
||||
🚀 **下一代AI自主渗透测试平台** - 基于Golang构建,内置上百个安全工具,支持灵活扩展自定义工具,通过MCP协议实现AI智能决策与自动化执行,让安全测试像对话一样简单。
|
||||

|
||||
|
||||
## ✨ 功能特性
|
||||
@@ -8,7 +8,7 @@
|
||||
### 核心功能
|
||||
- 🤖 **AI智能代理** - 集成OpenAI兼容API(支持GPT、Claude、DeepSeek等),AI自主决策和执行安全测试
|
||||
- 🧠 **智能决策引擎** - AI分析目标并自动选择最佳测试策略和工具组合
|
||||
- ⚡ **自主执行** - AI代理自动调用安全工具,无需人工干预
|
||||
- ⚡ **自主执行** - AI代理自动调用安全工具,无需人工干预,最多支持30轮迭代
|
||||
- 🔄 **自适应调整** - 根据工具执行结果和发现的漏洞,AI自动调整测试策略
|
||||
- 📝 **智能总结** - 达到最大迭代次数时,AI自动总结测试结果并提供下一步执行计划
|
||||
- 💬 **对话式交互** - 自然语言对话界面,支持流式输出(SSE),实时查看执行过程
|
||||
@@ -86,7 +86,7 @@ CyberStrikeAI/
|
||||
|
||||
- Go 1.21 或更高版本
|
||||
- OpenAI API Key(或其他兼容OpenAI协议的API,如DeepSeek、Claude等)
|
||||
- 安全工具(可选):根据您的需求安装相应的安全工具,系统支持98+个工具
|
||||
- 安全工具(可选):根据您的需求安装相应的安全工具,系统支持上百个工具
|
||||
|
||||
### 安装步骤
|
||||
|
||||
@@ -132,7 +132,7 @@ security:
|
||||
|
||||
4. **安装安全工具(可选)**
|
||||
|
||||
根据您的需求安装相应的安全工具。系统支持98+个工具,您可以根据实际需要选择性安装:
|
||||
根据您的需求安装相应的安全工具。系统支持上百个工具,您可以根据实际需要选择性安装:
|
||||
|
||||
```bash
|
||||
# macOS (使用Homebrew)
|
||||
@@ -303,6 +303,81 @@ parameters:
|
||||
首先扫描 192.168.1.1 的开放端口,然后对发现的Web服务进行漏洞扫描
|
||||
```
|
||||
|
||||
### 后渗透测试
|
||||
|
||||
在获得初始访问权限后,可以使用后渗透工具进行权限提升、横向移动和持久化:
|
||||
|
||||
#### 1. Linux 权限提升枚举
|
||||
```
|
||||
使用 linpeas 对目标 Linux 系统进行权限提升检查
|
||||
```
|
||||
|
||||
#### 2. Windows 权限提升枚举
|
||||
```
|
||||
使用 winpeas 对目标 Windows 系统进行权限提升检查
|
||||
```
|
||||
|
||||
#### 3. Active Directory 攻击路径分析
|
||||
```
|
||||
使用 bloodhound 分析 Active Directory 的攻击路径
|
||||
```
|
||||
|
||||
#### 4. 凭证提取
|
||||
```
|
||||
使用 mimikatz 提取 Windows 系统的凭证信息
|
||||
```
|
||||
|
||||
#### 5. 横向移动
|
||||
```
|
||||
使用 impacket 工具集进行网络协议攻击和横向移动
|
||||
```
|
||||
|
||||
#### 6. 后门生成
|
||||
```
|
||||
使用 msfvenom 生成反向 shell 载荷
|
||||
```
|
||||
|
||||
### CTF 竞赛支持
|
||||
|
||||
系统内置了丰富的 CTF 工具,支持各种 CTF 题型的解题:
|
||||
|
||||
#### 1. 隐写分析
|
||||
```
|
||||
使用 stegsolve 分析图片隐写
|
||||
使用 zsteg 检测 LSB 隐写
|
||||
```
|
||||
|
||||
#### 2. 密码破解
|
||||
```
|
||||
使用 hashcat 破解哈希值
|
||||
使用 john 破解密码文件
|
||||
使用 fcrackzip 破解 ZIP 文件密码
|
||||
使用 pdfcrack 破解 PDF 文件密码
|
||||
```
|
||||
|
||||
#### 3. 二进制分析
|
||||
```
|
||||
使用 gdb 调试二进制文件
|
||||
使用 radare2 进行逆向分析
|
||||
使用 strings 提取二进制文件中的字符串
|
||||
```
|
||||
|
||||
#### 4. 哈希识别
|
||||
```
|
||||
使用 hash-identifier 识别哈希类型
|
||||
```
|
||||
|
||||
#### 5. 数据转换和分析
|
||||
```
|
||||
使用 cyberchef 进行各种数据转换和分析
|
||||
使用 xxd 查看文件十六进制内容
|
||||
```
|
||||
|
||||
#### 6. 综合 CTF 解题
|
||||
```
|
||||
分析这个 CTF 题目:给定一个包含隐写和加密的文件,找出 flag
|
||||
```
|
||||
|
||||
### 监控工具执行
|
||||
|
||||
在"工具监控"标签页中,您可以:
|
||||
@@ -571,7 +646,7 @@ curl -X POST http://localhost:8080/api/mcp \
|
||||
|
||||
### 工具概览
|
||||
|
||||
当前系统集成了 **98+ 个安全工具**,涵盖以下类别:
|
||||
当前系统集成了 **上百个安全工具**,涵盖以下类别:
|
||||
|
||||
- **网络扫描工具** - nmap, masscan, rustscan, arp-scan, nbtscan 等
|
||||
- **Web应用扫描** - sqlmap, nikto, dirb, gobuster, feroxbuster, ffuf, httpx 等
|
||||
@@ -584,6 +659,8 @@ curl -X POST http://localhost:8080/api/mcp \
|
||||
- **漏洞利用** - metasploit, msfvenom, pwntools, ropper, ropgadget 等
|
||||
- **密码破解** - hashcat, john, hashpump 等
|
||||
- **取证分析** - volatility, volatility3, foremost, steghide, exiftool 等
|
||||
- **后渗透工具** - linpeas, winpeas, mimikatz, bloodhound, impacket, responder 等
|
||||
- **CTF工具** - stegsolve, zsteg, hash-identifier, fcrackzip, pdfcrack, cyberchef 等
|
||||
- **系统工具** - exec, create-file, delete-file, list-files, modify-file 等
|
||||
|
||||
### 主要工具示例
|
||||
@@ -925,7 +1002,7 @@ CMD ["./cyberstrike-ai"]
|
||||
|
||||
### AI迭代机制
|
||||
|
||||
- **最大迭代次数**:系统支持多轮AI迭代,确保复杂测试任务能够完成
|
||||
- **最大迭代次数**:系统支持最多30轮AI迭代,确保复杂测试任务能够完成
|
||||
- **智能总结**:当达到最大迭代次数时,AI会自动总结所有测试结果、发现的问题和已完成的工作
|
||||
- **下一步计划**:如果测试未完成,AI会提供详细的下一步执行计划,指导后续测试
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ openai:
|
||||
base_url: "https://api.deepseek.com/v1"
|
||||
model: "deepseek-chat"
|
||||
|
||||
agent:
|
||||
max_iterations: 30 # 最大迭代轮数
|
||||
|
||||
database:
|
||||
path: "data/conversations.db"
|
||||
|
||||
|
||||
BIN
cyberstrike-ai
Normal file
BIN
cyberstrike-ai
Normal file
Binary file not shown.
@@ -17,19 +17,25 @@ import (
|
||||
|
||||
// Agent AI代理
|
||||
type Agent struct {
|
||||
openAIClient *http.Client
|
||||
config *config.OpenAIConfig
|
||||
mcpServer *mcp.Server
|
||||
logger *zap.Logger
|
||||
openAIClient *http.Client
|
||||
config *config.OpenAIConfig
|
||||
mcpServer *mcp.Server
|
||||
logger *zap.Logger
|
||||
maxIterations int
|
||||
}
|
||||
|
||||
// NewAgent 创建新的Agent
|
||||
func NewAgent(cfg *config.OpenAIConfig, mcpServer *mcp.Server, logger *zap.Logger) *Agent {
|
||||
func NewAgent(cfg *config.OpenAIConfig, mcpServer *mcp.Server, logger *zap.Logger, maxIterations int) *Agent {
|
||||
// 如果 maxIterations 为 0 或负数,使用默认值 30
|
||||
if maxIterations <= 0 {
|
||||
maxIterations = 30
|
||||
}
|
||||
return &Agent{
|
||||
openAIClient: &http.Client{Timeout: 5 * time.Minute},
|
||||
config: cfg,
|
||||
mcpServer: mcpServer,
|
||||
logger: logger,
|
||||
openAIClient: &http.Client{Timeout: 5 * time.Minute},
|
||||
config: cfg,
|
||||
mcpServer: mcpServer,
|
||||
logger: logger,
|
||||
maxIterations: maxIterations,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +277,7 @@ func (a *Agent) AgentLoopWithProgress(ctx context.Context, userInput string, his
|
||||
MCPExecutionIDs: make([]string, 0),
|
||||
}
|
||||
|
||||
maxIterations := 30
|
||||
maxIterations := a.maxIterations
|
||||
for i := 0; i < maxIterations; i++ {
|
||||
// 检查是否是最后一次迭代
|
||||
isLastIteration := (i == maxIterations-1)
|
||||
@@ -527,7 +533,7 @@ func (a *Agent) AgentLoopWithProgress(ctx context.Context, userInput string, his
|
||||
sendProgress("progress", "达到最大迭代次数,正在生成总结...", nil)
|
||||
finalSummaryPrompt := ChatMessage{
|
||||
Role: "user",
|
||||
Content: "已达到最大迭代次数(30轮)。请总结到目前为止的所有测试结果、发现的问题和已完成的工作。如果需要继续测试,请提供详细的下一步执行计划。请直接回复,不要调用工具。",
|
||||
Content: fmt.Sprintf("已达到最大迭代次数(%d轮)。请总结到目前为止的所有测试结果、发现的问题和已完成的工作。如果需要继续测试,请提供详细的下一步执行计划。请直接回复,不要调用工具。", a.maxIterations),
|
||||
}
|
||||
messages = append(messages, finalSummaryPrompt)
|
||||
|
||||
@@ -542,7 +548,7 @@ func (a *Agent) AgentLoopWithProgress(ctx context.Context, userInput string, his
|
||||
}
|
||||
|
||||
// 如果无法生成总结,返回友好的提示
|
||||
result.Response = "已达到最大迭代次数(30轮)。系统已执行了多轮测试,但由于达到迭代上限,无法继续自动执行。建议您查看已执行的工具结果,或提出新的测试请求以继续测试。"
|
||||
result.Response = fmt.Sprintf("已达到最大迭代次数(%d轮)。系统已执行了多轮测试,但由于达到迭代上限,无法继续自动执行。建议您查看已执行的工具结果,或提出新的测试请求以继续测试。", a.maxIterations)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,11 @@ func New(cfg *config.Config, log *logger.Logger) (*App, error) {
|
||||
executor.RegisterTools(mcpServer)
|
||||
|
||||
// 创建Agent
|
||||
agent := agent.NewAgent(&cfg.OpenAI, mcpServer, log.Logger)
|
||||
maxIterations := cfg.Agent.MaxIterations
|
||||
if maxIterations <= 0 {
|
||||
maxIterations = 30 // 默认值
|
||||
}
|
||||
agent := agent.NewAgent(&cfg.OpenAI, mcpServer, log.Logger, maxIterations)
|
||||
|
||||
// 创建处理器
|
||||
agentHandler := handler.NewAgentHandler(agent, db, log.Logger)
|
||||
|
||||
@@ -14,6 +14,7 @@ type Config struct {
|
||||
Log LogConfig `yaml:"log"`
|
||||
MCP MCPConfig `yaml:"mcp"`
|
||||
OpenAI OpenAIConfig `yaml:"openai"`
|
||||
Agent AgentConfig `yaml:"agent"`
|
||||
Security SecurityConfig `yaml:"security"`
|
||||
Database DatabaseConfig `yaml:"database"`
|
||||
}
|
||||
@@ -49,6 +50,10 @@ type DatabaseConfig struct {
|
||||
Path string `yaml:"path"`
|
||||
}
|
||||
|
||||
type AgentConfig struct {
|
||||
MaxIterations int `yaml:"max_iterations"`
|
||||
}
|
||||
|
||||
type ToolConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Command string `yaml:"command"`
|
||||
@@ -200,6 +205,9 @@ func Default() *Config {
|
||||
BaseURL: "https://api.openai.com/v1",
|
||||
Model: "gpt-4",
|
||||
},
|
||||
Agent: AgentConfig{
|
||||
MaxIterations: 30, // 默认最大迭代次数
|
||||
},
|
||||
Security: SecurityConfig{
|
||||
Tools: []ToolConfig{}, // 工具配置应该从 config.yaml 或 tools/ 目录加载
|
||||
ToolsDir: "tools", // 默认工具目录
|
||||
|
||||
@@ -256,7 +256,27 @@ func (e *Executor) buildCommandArgs(toolName string, toolConfig *config.ToolConf
|
||||
|
||||
// 布尔值特殊处理:如果为 false,跳过;如果为 true,只添加标志
|
||||
if param.Type == "bool" {
|
||||
if boolVal, ok := value.(bool); ok {
|
||||
var boolVal bool
|
||||
var ok bool
|
||||
|
||||
// 尝试多种类型转换
|
||||
if boolVal, ok = value.(bool); ok {
|
||||
// 已经是布尔值
|
||||
} else if numVal, ok := value.(float64); ok {
|
||||
// JSON 数字类型(float64)
|
||||
boolVal = numVal != 0
|
||||
ok = true
|
||||
} else if numVal, ok := value.(int); ok {
|
||||
// int 类型
|
||||
boolVal = numVal != 0
|
||||
ok = true
|
||||
} else if strVal, ok := value.(string); ok {
|
||||
// 字符串类型
|
||||
boolVal = strVal == "true" || strVal == "1" || strVal == "yes"
|
||||
ok = true
|
||||
}
|
||||
|
||||
if ok {
|
||||
if !boolVal {
|
||||
continue // false 时不添加任何参数
|
||||
}
|
||||
|
||||
@@ -618,10 +618,13 @@ header {
|
||||
background: var(--bg-primary);
|
||||
border-top: 1px solid var(--border-color);
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat-input-container input {
|
||||
.chat-input-container textarea {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
@@ -630,14 +633,46 @@ header {
|
||||
transition: all 0.2s;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
resize: none;
|
||||
height: 44px;
|
||||
font-family: inherit;
|
||||
line-height: 1.5;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
/* 隐藏滚动条但保留滚动功能 */
|
||||
scrollbar-width: thin; /* Firefox */
|
||||
scrollbar-color: transparent transparent; /* Firefox - 隐藏滚动条 */
|
||||
}
|
||||
|
||||
.chat-input-container input:focus {
|
||||
/* WebKit 浏览器(Chrome, Safari, Edge)的滚动条样式 - 隐藏但保留功能 */
|
||||
.chat-input-container textarea::-webkit-scrollbar {
|
||||
width: 4px; /* 最窄的滚动条 */
|
||||
}
|
||||
|
||||
.chat-input-container textarea::-webkit-scrollbar-track {
|
||||
background: transparent; /* 隐藏轨道 */
|
||||
}
|
||||
|
||||
.chat-input-container textarea::-webkit-scrollbar-thumb {
|
||||
background: transparent; /* 默认隐藏滑块 */
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* 鼠标悬停时显示滚动条 */
|
||||
.chat-input-container textarea:hover::-webkit-scrollbar-thumb {
|
||||
background: var(--text-muted); /* 悬停时显示 */
|
||||
}
|
||||
|
||||
.chat-input-container textarea:focus::-webkit-scrollbar-thumb {
|
||||
background: var(--text-muted); /* 聚焦时显示 */
|
||||
}
|
||||
|
||||
.chat-input-container textarea:focus {
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 3px rgba(0, 102, 255, 0.1);
|
||||
}
|
||||
|
||||
.chat-input-container input::placeholder {
|
||||
.chat-input-container textarea::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ function collapseAllProgressDetails(assistantMessageId, progressId) {
|
||||
timeline.classList.remove('expanded');
|
||||
const btn = document.querySelector(`#${assistantMessageId} .process-detail-btn`);
|
||||
if (btn) {
|
||||
btn.innerHTML = '<span>📋 过程详情</span>';
|
||||
btn.innerHTML = '<span>展开详情</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,19 +271,19 @@ function toggleProcessDetails(progressId, assistantMessageId) {
|
||||
if (content && timeline) {
|
||||
if (timeline.classList.contains('expanded')) {
|
||||
timeline.classList.remove('expanded');
|
||||
if (btn) btn.innerHTML = '<span>📋 过程详情</span>';
|
||||
if (btn) btn.innerHTML = '<span>展开详情</span>';
|
||||
} else {
|
||||
timeline.classList.add('expanded');
|
||||
if (btn) btn.innerHTML = '<span>📋 收起详情</span>';
|
||||
if (btn) btn.innerHTML = '<span>收起详情</span>';
|
||||
}
|
||||
} else if (timeline) {
|
||||
// 如果只有timeline,直接切换
|
||||
if (timeline.classList.contains('expanded')) {
|
||||
timeline.classList.remove('expanded');
|
||||
if (btn) btn.innerHTML = '<span>📋 过程详情</span>';
|
||||
if (btn) btn.innerHTML = '<span>展开详情</span>';
|
||||
} else {
|
||||
timeline.classList.add('expanded');
|
||||
if (btn) btn.innerHTML = '<span>📋 收起详情</span>';
|
||||
if (btn) btn.innerHTML = '<span>收起详情</span>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,32 +613,35 @@ function addMessage(role, content, mcpExecutionIds = null, progressId = null) {
|
||||
timeDiv.textContent = new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
|
||||
contentWrapper.appendChild(timeDiv);
|
||||
|
||||
// 如果有MCP执行ID,添加查看详情区域
|
||||
if (mcpExecutionIds && Array.isArray(mcpExecutionIds) && mcpExecutionIds.length > 0 && role === 'assistant') {
|
||||
// 如果有MCP执行ID或进度ID,添加查看详情区域(统一使用"渗透测试详情"样式)
|
||||
if (role === 'assistant' && ((mcpExecutionIds && Array.isArray(mcpExecutionIds) && mcpExecutionIds.length > 0) || progressId)) {
|
||||
const mcpSection = document.createElement('div');
|
||||
mcpSection.className = 'mcp-call-section';
|
||||
|
||||
const mcpLabel = document.createElement('div');
|
||||
mcpLabel.className = 'mcp-call-label';
|
||||
mcpLabel.textContent = `工具调用 (${mcpExecutionIds.length})`;
|
||||
mcpLabel.textContent = '📋 渗透测试详情';
|
||||
mcpSection.appendChild(mcpLabel);
|
||||
|
||||
const buttonsContainer = document.createElement('div');
|
||||
buttonsContainer.className = 'mcp-call-buttons';
|
||||
|
||||
mcpExecutionIds.forEach((execId, index) => {
|
||||
const detailBtn = document.createElement('button');
|
||||
detailBtn.className = 'mcp-detail-btn';
|
||||
detailBtn.innerHTML = `<span>调用 #${index + 1}</span>`;
|
||||
detailBtn.onclick = () => showMCPDetail(execId);
|
||||
buttonsContainer.appendChild(detailBtn);
|
||||
});
|
||||
// 如果有MCP执行ID,添加MCP调用详情按钮
|
||||
if (mcpExecutionIds && Array.isArray(mcpExecutionIds) && mcpExecutionIds.length > 0) {
|
||||
mcpExecutionIds.forEach((execId, index) => {
|
||||
const detailBtn = document.createElement('button');
|
||||
detailBtn.className = 'mcp-detail-btn';
|
||||
detailBtn.innerHTML = `<span>调用 #${index + 1}</span>`;
|
||||
detailBtn.onclick = () => showMCPDetail(execId);
|
||||
buttonsContainer.appendChild(detailBtn);
|
||||
});
|
||||
}
|
||||
|
||||
// 如果有进度ID,添加过程详情按钮
|
||||
// 如果有进度ID,添加展开详情按钮(统一使用"展开详情"文本)
|
||||
if (progressId) {
|
||||
const progressDetailBtn = document.createElement('button');
|
||||
progressDetailBtn.className = 'mcp-detail-btn process-detail-btn';
|
||||
progressDetailBtn.innerHTML = `<span>📋 过程详情</span>`;
|
||||
progressDetailBtn.innerHTML = '<span>展开详情</span>';
|
||||
progressDetailBtn.onclick = () => toggleProcessDetails(progressId, messageDiv.id);
|
||||
buttonsContainer.appendChild(progressDetailBtn);
|
||||
// 存储进度ID到消息元素
|
||||
@@ -688,8 +691,11 @@ function renderProcessDetails(messageId, processDetails) {
|
||||
if (!mcpLabel && !buttonsContainer) {
|
||||
mcpLabel = document.createElement('div');
|
||||
mcpLabel.className = 'mcp-call-label';
|
||||
mcpLabel.textContent = '过程详情';
|
||||
mcpLabel.textContent = '📋 渗透测试详情';
|
||||
mcpSection.appendChild(mcpLabel);
|
||||
} else if (mcpLabel && mcpLabel.textContent !== '📋 渗透测试详情') {
|
||||
// 如果标签存在但不是统一格式,更新它
|
||||
mcpLabel.textContent = '📋 渗透测试详情';
|
||||
}
|
||||
|
||||
// 如果没有按钮容器,创建一个
|
||||
@@ -704,7 +710,7 @@ function renderProcessDetails(messageId, processDetails) {
|
||||
if (!processDetailBtn) {
|
||||
processDetailBtn = document.createElement('button');
|
||||
processDetailBtn.className = 'mcp-detail-btn process-detail-btn';
|
||||
processDetailBtn.innerHTML = '<span>📋 过程详情</span>';
|
||||
processDetailBtn.innerHTML = '<span>展开详情</span>';
|
||||
processDetailBtn.onclick = () => toggleProcessDetails(null, messageId);
|
||||
buttonsContainer.appendChild(processDetailBtn);
|
||||
}
|
||||
@@ -788,11 +794,14 @@ function removeMessage(id) {
|
||||
}
|
||||
}
|
||||
|
||||
// 回车发送消息
|
||||
document.getElementById('chat-input').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
// 回车发送消息,Shift+Enter 换行
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
chatInput.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
}
|
||||
// Shift+Enter 允许默认行为(换行)
|
||||
});
|
||||
|
||||
// 显示MCP调用详情
|
||||
@@ -1122,6 +1131,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// 加载对话列表
|
||||
loadConversations();
|
||||
|
||||
// 初始化 textarea 高度
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
if (chatInput) {
|
||||
chatInput.style.height = '44px';
|
||||
}
|
||||
|
||||
// 添加欢迎消息
|
||||
addMessage('assistant', '系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。');
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<div class="chat-container">
|
||||
<div id="chat-messages" class="chat-messages"></div>
|
||||
<div class="chat-input-container">
|
||||
<input type="text" id="chat-input" placeholder="输入测试目标或命令..." />
|
||||
<textarea id="chat-input" placeholder="输入测试目标或命令... (Shift+Enter 换行,Enter 发送)" rows="1"></textarea>
|
||||
<button onclick="sendMessage()">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user