2 Commits

Author SHA1 Message Date
ageerle
accac603cf chore(sql): 更新数据库脚本v3版本
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 14:16:45 +08:00
ageerle
7c96c730e6 refactor(chat): 重构聊天服务架构,引入Handler模式
主要变更:
1. 移除ruoyi-ai-copilot模块
2. 重构docker配置目录结构,统一迁移至docs/docker/
3. 聊天服务引入Handler模式:
   - 新增ChatHandler接口及多种实现
   - DefaultChatHandler: 默认聊天处理
   - AgentChatHandler: Agent模式处理
   - WorkflowChatHandler: 工作流处理
   - ResumeChatHandler: 恢复会话处理
   - ChatContextBuilder: 上下文构建器
4. 简化AbstractStreamingChatService和ChatServiceFacade代码
5. 优化各Provider实现,统一代码风格

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 14:14:56 +08:00
88 changed files with 1189 additions and 12581 deletions

View File

@@ -0,0 +1,28 @@
---
version: '3.8'
services:
minio:
image: minio/minio
container_name: minio
ports:
- "9000:9000"
- "9090:9090"
environment:
- MINIO_ACCESS_KEY=ruoyi
- MINIO_SECRET_KEY=ruoyi123
volumes:
- minio_data:/data
- minio_config:/root/.minio
command: server /data --console-address ":9090"
restart: always
networks:
- minio-net
networks:
minio-net:
driver: bridge
volumes:
minio_data:
minio_config:

View File

@@ -0,0 +1,65 @@
version: '3.8'
services:
neo4j:
image: neo4j:5.15.0
container_name: ruoyi-neo4j
restart: unless-stopped
ports:
# HTTP端口
- "7474:7474"
# HTTPS端口
- "7473:7473"
# Bolt端口
- "7687:7687"
environment:
# 初始密码设置(首次启动后需要修改)
- NEO4J_AUTH=neo4j/your_password
# 接受许可协议
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
# 内存配置(根据服务器配置调整)
- NEO4J_dbms_memory_heap_initial__size=512m
- NEO4J_dbms_memory_heap_max__size=2g
- NEO4J_dbms_memory_pagecache_size=1g
# 事务日志配置
- NEO4J_dbms_tx__log_rotation_retention__policy=3 days
# 允许从任何主机连接
- NEO4J_dbms_default__listen__address=0.0.0.0
# 启用APOC插件
- NEO4J_dbms_security_procedures_unrestricted=apoc.*
- NEO4J_dbms_security_procedures_allowlist=apoc.*
# 日志级别
- NEO4J_dbms_logs_debug_level=INFO
volumes:
# 数据持久化
- neo4j_data:/data
# 日志持久化
- neo4j_logs:/logs
# 导入目录
- neo4j_import:/var/lib/neo4j/import
# 插件目录
- neo4j_plugins:/plugins
networks:
- ruoyi-network
healthcheck:
test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider localhost:7474 || exit 1" ]
interval: 30s
timeout: 10s
retries: 5
start_period: 40s
volumes:
neo4j_data:
name: ruoyi-neo4j-data
neo4j_logs:
name: ruoyi-neo4j-logs
neo4j_import:
name: ruoyi-neo4j-import
neo4j_plugins:
name: ruoyi-neo4j-plugins
networks:
ruoyi-network:
name: ruoyi-network
driver: bridge

View File

@@ -0,0 +1,75 @@
version: '3.5'
services:
etcd:
container_name: milvus-etcd
image: quay.io/coreos/etcd:v3.5.18
environment:
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1000
- ETCD_QUOTA_BACKEND_BYTES=4294967296
- ETCD_SNAPSHOT_COUNT=50000
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd
command: etcd -advertise-client-urls=http://etcd:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
healthcheck:
test: ["CMD", "etcdctl", "endpoint", "health"]
interval: 30s
timeout: 20s
retries: 3
minio:
container_name: milvus-minio
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
environment:
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
ports:
- "9001:9001"
- "9000:9000"
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data
command: minio server /minio_data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
standalone:
container_name: milvus-standalone
image: milvusdb/milvus:v2.5.7
command: ["milvus", "run", "standalone"]
security_opt:
- seccomp:unconfined
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
interval: 30s
start_period: 90s
timeout: 20s
retries: 3
ports:
- "19530:19530"
- "9091:9091"
depends_on:
- "etcd"
- "minio"
attu:
container_name: attu
image: zilliz/attu:v2.5.7
environment:
MILVUS_URL: milvus-standalone:19530
ports:
- "19500:3000" # 外部端口19500可以自定义
depends_on:
- "standalone"
networks:
default:
name: milvus

View File

@@ -0,0 +1,25 @@
---
services:
weaviate:
command:
- --host
- 0.0.0.0
- --port
- '6038'
- --scheme
- http
image: semitechnologies/weaviate:1.19.7
ports:
- 6038:6038
- 50051:50051
volumes:
- weaviate_data:/var/lib/weaviate
environment:
QUERY_DEFAULTS_LIMIT: 25
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
DEFAULT_VECTORIZER_MODULE: 'none'
CLUSTER_HOSTNAME: 'node1'
volumes:
weaviate_data:
...

View File

@@ -1,161 +0,0 @@
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
# 可以根据业务并发量适当调高
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
# 高效传输文件
sendfile on;
# 长连接超时时间
keepalive_timeout 65;
# 单连接最大请求数,提高长连接复用率
keepalive_requests 100000;
# 限制body大小
client_max_body_size 100m;
client_header_buffer_size 32k;
client_body_buffer_size 512k;
# 开启静态资源压缩
gzip_static on;
# 连接数限制 (防御类配置) 10m 一般够用了,能存储上万 IP 的计数
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
# 隐藏 nginx 版本号,防止暴露版本信息
server_tokens off;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
upstream server {
ip_hash;
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}
upstream monitor-admin {
server 127.0.0.1:9090;
}
upstream snailjob-server {
server 127.0.0.1:8800;
}
server {
listen 80;
server_name localhost;
# https配置参考 start
#listen 443 ssl;
# 证书直接存放 /docker/nginx/cert/ 目录下即可 更改证书名称即可 无需更改证书路径
#ssl on;
#ssl_certificate /etc/nginx/cert/xxx.local.crt; # /etc/nginx/cert/ 为docker映射路径 不允许更改
#ssl_certificate_key /etc/nginx/cert/xxx.local.key; # /etc/nginx/cert/ 为docker映射路径 不允许更改
#ssl_session_timeout 5m;
#ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
#ssl_protocols TLSv1.3 TLSv1.2 TLSv1.1 TLSv1;
#ssl_prefer_server_ciphers on;
# https配置参考 end
# 演示环境配置 拦截除 GET POST 之外的所有请求
# if ($request_method !~* GET|POST) {
# rewrite ^/(.*)$ /403;
# }
# location = /403 {
# default_type application/json;
# return 200 '{"msg":"演示模式,不允许操作","code":500}';
# }
# 限制外网访问内网 actuator 相关路径
location ~ ^(/[^/]*)?/actuator.*(/.*)?$ {
return 403;
}
location / {
root /usr/share/nginx/html; # docker映射路径 不允许更改
try_files $uri $uri/ /index.html;
index index.html index.htm;
}
location /prod-api/ {
# 设置客户端请求头中的 Host 信息(保持原始 Host
proxy_set_header Host $http_host;
# 获取客户端真实 IP
proxy_set_header X-Real-IP $remote_addr;
# 自定义头 REMOTE-HOST记录客户端 IP
proxy_set_header REMOTE-HOST $remote_addr;
# 获取完整的客户端 IP 链(经过多级代理时)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 设置后端响应超时时间(这里是 24 小时,适合长连接/SSE
proxy_read_timeout 86400s;
# SSE (Server-Sent Events) 与 WebSocket 支持参数
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 禁用代理缓冲,数据直接传给客户端
proxy_buffering off;
# 禁用代理缓存
proxy_cache off;
# 按 IP 限制连接数(防 CC 攻击) 小型站10~20 就够 中型站50~100
limit_conn perip 20;
# 按 Server 限制总并发连接数 根据服务器的最大并发处理能力来定 太小会限制合法用户访问,太大会占满服务器资源
limit_conn perserver 500;
proxy_pass http://server/;
}
# https 会拦截内链所有的 http 请求 造成功能无法使用
# 解决方案1 将 admin 服务 也配置成 https
# 解决方案2 将菜单配置为外链访问 走独立页面 http 访问
location /admin/ {
# 设置客户端请求头中的 Host 信息(保持原始 Host
proxy_set_header Host $http_host;
# 获取客户端真实 IP
proxy_set_header X-Real-IP $remote_addr;
# 自定义头 REMOTE-HOST记录客户端 IP
proxy_set_header REMOTE-HOST $remote_addr;
# 获取完整的客户端 IP 链(经过多级代理时)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 禁用代理缓冲,数据直接传给客户端
proxy_buffering off;
# 禁用代理缓存
proxy_cache off;
proxy_pass http://monitor-admin/admin/;
}
location /snail-job/ {
# 设置客户端请求头中的 Host 信息(保持原始 Host
proxy_set_header Host $http_host;
# 获取客户端真实 IP
proxy_set_header X-Real-IP $remote_addr;
# 自定义头 REMOTE-HOST记录客户端 IP
proxy_set_header REMOTE-HOST $remote_addr;
# 获取完整的客户端 IP 链(经过多级代理时)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# SSE (Server-Sent Events) 与 WebSocket 支持参数
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 禁用代理缓冲,直接传输给客户端
proxy_buffering off;
# 禁用代理缓存
proxy_cache off;
proxy_pass http://snailjob-server/snail-job/;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}

View File

@@ -1,28 +0,0 @@
# redis 密码
requirepass ruoyi123
# key 监听器配置
# notify-keyspace-events Ex
# 配置持久化文件存储路径
dir /redis/data
# 配置rdb
# 15分钟内有至少1个key被更改则进行快照
save 900 1
# 5分钟内有至少10个key被更改则进行快照
save 300 10
# 1分钟内有至少10000个key被更改则进行快照
save 60 10000
# 开启压缩
rdbcompression yes
# rdb文件名 用默认的即可
dbfilename dump.rdb
# 开启aof
appendonly yes
# 文件名
appendfilename "appendonly.aof"
# 持久化策略,no:不同步,everysec:每秒一次,always:总是同步,速度比较慢
# appendfsync always
appendfsync everysec
# appendfsync no

View File

@@ -1 +0,0 @@
数据目录 请执行 `chmod 777 /docker/redis/data` 赋予读写权限 否则将无法写入数据

File diff suppressed because it is too large Load Diff

View File

@@ -80,10 +80,16 @@ public class AuthController {
// 授权类型和客户端id
String clientId = loginBody.getClientId();
String grantType = loginBody.getGrantType();
log.info("登录请求 - clientId: {}, grantType: {}", clientId, grantType);
SysClientVo client = clientService.queryByClientId(clientId);
log.info("查询客户端结果 - client: {}, grantType: {}", client, client != null ? client.getGrantType() : "null");
// 查询不到 client 或 client 内不包含 grantType
if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
if (ObjectUtil.isNull(client)) {
log.info("客户端id: {} 不存在!", clientId);
return R.fail(MessageUtils.message("auth.grant.type.error"));
}
if (!StringUtils.contains(client.getGrantType(), grantType)) {
log.info("客户端id: {} 认证类型:{} 不匹配! 数据库grantType: {}", clientId, grantType, client.getGrantType());
return R.fail(MessageUtils.message("auth.grant.type.error"));
} else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
return R.fail(MessageUtils.message("auth.grant.type.blocked"));

View File

@@ -58,15 +58,15 @@ spring:
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://127.0.0.1:3306/ruoyi_ai_agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: root
agent:
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
# url: jdbc:mysql://localhost:3306/agent_db
username: root
password: root
driverClassName: com.mysql.cj.jdbc.Driver
# agent:
# url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
# # url: jdbc:mysql://localhost:3306/agent_db
# username: root
# password: root
# driverClassName: com.mysql.cj.jdbc.Driver
hikari:
# 最大连接池数量
@@ -268,4 +268,4 @@ justauth:
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitea
AGENT_ALLOWED_TABLES: "abtest_rule,abtest_project,agent_ban_log,agent_ban_logs,agent_install_sub_task,agent_install_sum_task,agent_install_task"
AGENT_ALLOWED_TABLES: ""

View File

@@ -7,6 +7,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.common.chat.service.chat.IChatService;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
@@ -54,4 +55,9 @@ public class ChatContext {
* 响应处理器
*/
private StreamingChatResponseHandler handler;
/**
* 聊天服务实例
*/
private IChatService chatService;
}

View File

@@ -12,7 +12,6 @@
<packaging>pom</packaging>
<modules>
<module>ruoyi-ai-copilot</module>
<module>ruoyi-monitor-admin</module>
<module>ruoyi-snailjob-server</module>
</modules>

View File

@@ -1,100 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>ruoyi-ai-copilot</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>copilot</name>
<description>SpringAI - Alibaba - Copilot</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>4.0.1</spring-boot.version>
<spring-ai.version>2.0.0-M2</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>spring-ai-agent-utils</artifactId>
<version>0.4.2</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- JSON Schema Validation -->
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>1.0.87</version>
</dependency>
<!-- Diff Utils -->
<dependency>
<groupId>io.github.java-diff-utils</groupId>
<artifactId>java-diff-utils</artifactId>
<version>4.12</version>
</dependency>
<!-- Apache Commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,82 +0,0 @@
package com.example.demo;
import com.example.demo.config.AppProperties;
import com.example.demo.utils.BrowserUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
/**
* 主要功能:
* 1. 文件读取、写入、编辑
* 2. 目录列表和结构查看
* 4. 连续性文件操作
*/
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class CopilotApplication {
private static final Logger logger = LoggerFactory.getLogger(CopilotApplication.class);
@Autowired
private AppProperties appProperties;
@Autowired
private Environment environment;
public static void main(String[] args) {
SpringApplication.run(CopilotApplication.class, args);
}
/**
* 应用启动完成后的事件监听器
* 自动打开浏览器访问应用首页
*/
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
AppProperties.Browser browserConfig = appProperties.getBrowser();
if (!browserConfig.isAutoOpen()) {
logger.info("Browser auto-open is disabled");
return;
}
// 获取实际的服务器端口
String port = environment.getProperty("server.port", "8080");
String actualUrl = browserConfig.getUrl().replace("${server.port:8080}", port);
logger.info("Application started successfully!");
logger.info("Preparing to open browser in {} seconds...", browserConfig.getDelaySeconds());
// 在新线程中延迟打开浏览器,避免阻塞主线程
new Thread(() -> {
try {
Thread.sleep(browserConfig.getDelaySeconds() * 1000L);
if (BrowserUtil.isValidUrl(actualUrl)) {
boolean success = BrowserUtil.openBrowser(actualUrl);
if (success) {
logger.info("✅ Browser opened successfully: {}", actualUrl);
System.out.println("🌐 Web interface opened: " + actualUrl);
} else {
logger.warn("❌ Failed to open browser automatically");
System.out.println("⚠️ Please manually open: " + actualUrl);
}
} else {
logger.error("❌ Invalid URL: {}", actualUrl);
System.out.println("⚠️ Invalid URL configured: " + actualUrl);
}
} catch (InterruptedException e) {
logger.warn("Browser opening was interrupted", e);
Thread.currentThread().interrupt();
}
}).start();
}
}

View File

@@ -1,232 +0,0 @@
package com.example.demo.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.nio.file.Paths;
import java.util.List;
/**
* 应用配置属性
*/
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private Workspace workspace = new Workspace();
private Security security = new Security();
private Tools tools = new Tools();
private Browser browser = new Browser();
// Getters and Setters
public Workspace getWorkspace() {
return workspace;
}
public void setWorkspace(Workspace workspace) {
this.workspace = workspace;
}
public Security getSecurity() {
return security;
}
public void setSecurity(Security security) {
this.security = security;
}
public Tools getTools() {
return tools;
}
public void setTools(Tools tools) {
this.tools = tools;
}
public Browser getBrowser() {
return browser;
}
public void setBrowser(Browser browser) {
this.browser = browser;
}
/**
* 审批模式
*/
public enum ApprovalMode {
DEFAULT, // 默认模式,危险操作需要确认
AUTO_EDIT, // 自动编辑模式,文件编辑不需要确认
YOLO // 完全自动模式,所有操作都不需要确认
}
/**
* 工作空间配置
*/
public static class Workspace {
// 使用 Paths.get() 和 File.separator 实现跨平台兼容
private String rootDirectory = Paths.get(System.getProperty("user.dir"), "workspace").toString();
private long maxFileSize = 10485760L; // 10MB
private List<String> allowedExtensions = List.of(
".txt", ".md", ".java", ".js", ".ts", ".json", ".xml",
".yml", ".yaml", ".properties", ".html", ".css", ".sql"
);
// Getters and Setters
public String getRootDirectory() {
return rootDirectory;
}
public void setRootDirectory(String rootDirectory) {
// 确保设置的路径也是跨平台兼容的
this.rootDirectory = Paths.get(rootDirectory).toString();
}
public long getMaxFileSize() {
return maxFileSize;
}
public void setMaxFileSize(long maxFileSize) {
this.maxFileSize = maxFileSize;
}
public List<String> getAllowedExtensions() {
return allowedExtensions;
}
public void setAllowedExtensions(List<String> allowedExtensions) {
this.allowedExtensions = allowedExtensions;
}
}
/**
* 安全配置
*/
public static class Security {
private ApprovalMode approvalMode = ApprovalMode.DEFAULT;
private List<String> dangerousCommands = List.of("rm", "del", "format", "fdisk", "mkfs");
// Getters and Setters
public ApprovalMode getApprovalMode() {
return approvalMode;
}
public void setApprovalMode(ApprovalMode approvalMode) {
this.approvalMode = approvalMode;
}
public List<String> getDangerousCommands() {
return dangerousCommands;
}
public void setDangerousCommands(List<String> dangerousCommands) {
this.dangerousCommands = dangerousCommands;
}
}
/**
* 工具配置
*/
public static class Tools {
private ToolConfig readFile = new ToolConfig(true);
private ToolConfig writeFile = new ToolConfig(true);
private ToolConfig editFile = new ToolConfig(true);
private ToolConfig listDirectory = new ToolConfig(true);
private ToolConfig shell = new ToolConfig(true);
// Getters and Setters
public ToolConfig getReadFile() {
return readFile;
}
public void setReadFile(ToolConfig readFile) {
this.readFile = readFile;
}
public ToolConfig getWriteFile() {
return writeFile;
}
public void setWriteFile(ToolConfig writeFile) {
this.writeFile = writeFile;
}
public ToolConfig getEditFile() {
return editFile;
}
public void setEditFile(ToolConfig editFile) {
this.editFile = editFile;
}
public ToolConfig getListDirectory() {
return listDirectory;
}
public void setListDirectory(ToolConfig listDirectory) {
this.listDirectory = listDirectory;
}
public ToolConfig getShell() {
return shell;
}
public void setShell(ToolConfig shell) {
this.shell = shell;
}
}
/**
* 工具配置
*/
public static class ToolConfig {
private boolean enabled;
public ToolConfig() {
}
public ToolConfig(boolean enabled) {
this.enabled = enabled;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
/**
* 浏览器配置
*/
public static class Browser {
private boolean autoOpen = true;
private String url = "http://localhost:8080";
private int delaySeconds = 2;
// Getters and Setters
public boolean isAutoOpen() {
return autoOpen;
}
public void setAutoOpen(boolean autoOpen) {
this.autoOpen = autoOpen;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getDelaySeconds() {
return delaySeconds;
}
public void setDelaySeconds(int delaySeconds) {
this.delaySeconds = delaySeconds;
}
}
}

View File

@@ -1,130 +0,0 @@
package com.example.demo.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import java.util.concurrent.TimeoutException;
/**
* 全局异常处理器
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理超时异常
*/
@ExceptionHandler({TimeoutException.class, AsyncRequestTimeoutException.class})
public ResponseEntity<ErrorResponse> handleTimeoutException(Exception e, WebRequest request) {
logger.error("Request timeout occurred", e);
ErrorResponse errorResponse = new ErrorResponse(
"TIMEOUT_ERROR",
"Request timed out. The operation took too long to complete.",
"Please try again with a simpler request or check your network connection."
);
return ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body(errorResponse);
}
/**
* 处理AI相关异常
*/
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException e, WebRequest request) {
logger.error("Runtime exception occurred", e);
// 检查是否是AI调用相关的异常
String message = e.getMessage();
if (message != null && (message.contains("tool") || message.contains("function") || message.contains("AI"))) {
ErrorResponse errorResponse = new ErrorResponse(
"AI_TOOL_ERROR",
"An error occurred during AI tool execution: " + message,
"The AI encountered an issue while processing your request. Please try rephrasing your request or try again."
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
ErrorResponse errorResponse = new ErrorResponse(
"RUNTIME_ERROR",
"An unexpected error occurred: " + message,
"Please try again. If the problem persists, contact support."
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
/**
* 处理所有其他异常
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception e, WebRequest request) {
logger.error("Unexpected exception occurred", e);
ErrorResponse errorResponse = new ErrorResponse(
"INTERNAL_ERROR",
"An internal server error occurred",
"Something went wrong on our end. Please try again later."
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
/**
* 错误响应类
*/
public static class ErrorResponse {
private String errorCode;
private String message;
private String suggestion;
private long timestamp;
public ErrorResponse(String errorCode, String message, String suggestion) {
this.errorCode = errorCode;
this.message = message;
this.suggestion = suggestion;
this.timestamp = System.currentTimeMillis();
}
// Getters and setters
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getSuggestion() {
return suggestion;
}
public void setSuggestion(String suggestion) {
this.suggestion = suggestion;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
}

View File

@@ -1,49 +0,0 @@
package com.example.demo.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import java.io.File;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 日志配置类
* 确保日志目录存在并记录应用启动信息
*/
@Configuration
public class LoggingConfiguration {
private static final Logger logger = LoggerFactory.getLogger(LoggingConfiguration.class);
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
// 确保日志目录存在
File logsDir = new File("logs");
if (!logsDir.exists()) {
boolean created = logsDir.mkdirs();
if (created) {
logger.info("📁 创建日志目录: {}", logsDir.getAbsolutePath());
}
}
// 记录应用启动信息
String startTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
logger.info("🎉 ========================================");
logger.info("🚀 (♥◠‿◠)ノ゙ AI Copilot启动成功 ლ(´ڡ`ლ)゙");
logger.info("🕐 启动时间: {}", startTime);
logger.info("📝 日志级别: DEBUG (工具调用详细日志已启用)");
logger.info("📁 日志文件: logs/copilot-file-ops.log");
logger.info("🔧 支持的工具:");
logger.info(" 📖 read_file - 读取文件内容");
logger.info(" ✏️ write_file - 写入文件内容");
logger.info(" 📝 edit_file - 编辑文件内容");
logger.info(" 🔍 analyze_project - 分析项目结构");
logger.info(" 🏗️ scaffold_project - 创建项目脚手架");
logger.info(" 🧠 smart_edit - 智能编辑项目");
logger.info("🎉 ========================================");
}
}

View File

@@ -1,39 +0,0 @@
package com.example.demo.config;
import org.springaicommunity.agent.tools.FileSystemTools;
import org.springaicommunity.agent.tools.ShellTools;
import org.springaicommunity.agent.tools.SkillsTool;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import java.util.List;
/**
* Spring AI 配置 - 使用Spring AI 1.0.0规范
*/
@Configuration
public class SpringAIConfiguration {
@Value("${agent.skills.dirs:Unknown}") List<Resource> agentSkillsDirs;
@Bean
public ChatClient chatClient(ChatModel chatModel, AppProperties appProperties) {
// 动态获取工作目录路径
ChatClient.Builder chatClientBuilder = ChatClient.builder(chatModel);
return chatClientBuilder
.defaultSystem("Always use the available skills to assist the user in their requests.")
// Skills tool callbacks
.defaultToolCallbacks(SkillsTool.builder().addSkillsResources(agentSkillsDirs).build())
// Built-in tools
.defaultTools(
// FileSystemTools.builder().build(),
ShellTools.builder().build()
)
.build();
}
}

View File

@@ -1,38 +0,0 @@
package com.example.demo.config;
/**
* 任务上下文持有者
* 使用ThreadLocal存储当前任务ID供AOP切面使用
*/
public class TaskContextHolder {
private static final ThreadLocal<String> taskIdHolder = new ThreadLocal<>();
/**
* 获取当前任务ID
*/
public static String getCurrentTaskId() {
return taskIdHolder.get();
}
/**
* 设置当前任务ID
*/
public static void setCurrentTaskId(String taskId) {
taskIdHolder.set(taskId);
}
/**
* 清除当前任务ID
*/
public static void clearCurrentTaskId() {
taskIdHolder.remove();
}
/**
* 检查是否有当前任务ID
*/
public static boolean hasCurrentTaskId() {
return taskIdHolder.get() != null;
}
}

View File

@@ -1,223 +0,0 @@
package com.example.demo.controller;
import com.example.demo.dto.ChatRequestDto;
import com.example.demo.service.ContinuousConversationService;
import com.example.demo.service.ToolExecutionLogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 聊天控制器
* 处理与AI的对话和工具调用
*/
@RestController
@RequestMapping("/api/chat")
@CrossOrigin(origins = "*")
public class ChatController {
private static final Logger logger = LoggerFactory.getLogger(ChatController.class);
private final ChatClient chatClient;
private final ContinuousConversationService continuousConversationService;
private final ToolExecutionLogger executionLogger;
// 简单的会话存储生产环境应该使用数据库或Redis
private final List<Message> conversationHistory = new ArrayList<>();
public ChatController(ChatClient chatClient, ContinuousConversationService continuousConversationService, ToolExecutionLogger executionLogger) {
this.chatClient = chatClient;
this.continuousConversationService = continuousConversationService;
this.executionLogger = executionLogger;
}
/**
* 流式聊天 - 直接返回流式数据
*/
@PostMapping(value = "/message", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamMessage(@RequestBody ChatRequestDto request) {
logger.info("📨 开始流式聊天: {}", request.getMessage());
return Flux.create(sink -> {
try {
UserMessage userMessage = new UserMessage(request.getMessage());
conversationHistory.add(userMessage);
// 使用Spring AI的流式API
Flux<String> contentStream = chatClient.prompt()
.messages(conversationHistory)
.stream()
.content();
// 订阅流式内容并转发给前端
contentStream
.doOnNext(content -> {
logger.debug("📨 流式内容片段: {}", content);
// 发送内容片段SSE格式会自动添加 "data: " 前缀)
sink.next(content);
})
.doOnComplete(() -> {
logger.info("✅ 流式聊天完成");
// 发送完成标记
sink.next("[DONE]");
sink.complete();
})
.doOnError(error -> {
logger.error("❌ 流式聊天错误: {}", error.getMessage());
sink.error(error);
})
.subscribe();
} catch (Exception e) {
logger.error("❌ 流式聊天启动失败: {}", e.getMessage());
sink.error(e);
}
});
}
/**
* 清除对话历史
*/
@PostMapping("/clear")
public Mono<Map<String, String>> clearHistory() {
conversationHistory.clear();
logger.info("Conversation history cleared");
return Mono.just(Map.of("status", "success", "message", "Conversation history cleared"));
}
/**
* 获取对话历史
*/
@GetMapping("/history")
public Mono<List<MessageDto>> getHistory() {
List<MessageDto> history = conversationHistory.stream()
.map(message -> {
MessageDto dto = new MessageDto();
dto.setContent(message.getText());
dto.setRole(message instanceof UserMessage ? "user" : "assistant");
return dto;
})
.toList();
return Mono.just(history);
}
// 注意Spring AI 1.0.0 使用不同的函数调用方式
// 函数需要在配置中注册,而不是在运行时动态创建
public static class ChatResponseDto {
private String taskId;
private String message;
private boolean success;
private boolean asyncTask;
private boolean streamResponse; // 新增:标识是否为流式响应
private int totalTurns;
private boolean reachedMaxTurns;
private String stopReason;
private long totalDurationMs;
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public boolean isAsyncTask() {
return asyncTask;
}
public void setAsyncTask(boolean asyncTask) {
this.asyncTask = asyncTask;
}
public boolean isStreamResponse() {
return streamResponse;
}
public void setStreamResponse(boolean streamResponse) {
this.streamResponse = streamResponse;
}
public int getTotalTurns() {
return totalTurns;
}
public void setTotalTurns(int totalTurns) {
this.totalTurns = totalTurns;
}
public boolean isReachedMaxTurns() {
return reachedMaxTurns;
}
public void setReachedMaxTurns(boolean reachedMaxTurns) {
this.reachedMaxTurns = reachedMaxTurns;
}
public String getStopReason() {
return stopReason;
}
public void setStopReason(String stopReason) {
this.stopReason = stopReason;
}
public long getTotalDurationMs() {
return totalDurationMs;
}
public void setTotalDurationMs(long totalDurationMs) {
this.totalDurationMs = totalDurationMs;
}
}
public static class MessageDto {
private String content;
private String role;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}
}

View File

@@ -1,89 +0,0 @@
package com.example.demo.controller;
import com.example.demo.service.LogStreamService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* SSE日志流控制器
* 提供SSE连接端点
*/
@RestController
@RequestMapping("/api/logs")
@CrossOrigin(origins = "*")
public class LogStreamController {
private static final Logger logger = LoggerFactory.getLogger(LogStreamController.class);
@Autowired
private LogStreamService logStreamService;
/**
* 建立SSE连接
* 前端通过此端点建立实时日志推送连接
*/
@GetMapping(value = "/stream/{taskId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamLogs(@PathVariable("taskId") String taskId) {
logger.info("🔗 收到SSE连接请求: taskId={}", taskId);
try {
SseEmitter emitter = logStreamService.createConnection(taskId);
logger.info("✅ SSE连接建立成功: taskId={}", taskId);
return emitter;
} catch (Exception e) {
logger.error("❌ SSE连接建立失败: taskId={}, error={}", taskId, e.getMessage());
throw new RuntimeException("Failed to create SSE connection: " + e.getMessage());
}
}
/**
* 关闭SSE连接
*/
@PostMapping("/close/{taskId}")
public void closeConnection(@PathVariable("taskId") String taskId) {
logger.info("🔚 收到关闭SSE连接请求: taskId={}", taskId);
logStreamService.closeConnection(taskId);
}
/**
* 获取连接状态
*/
@GetMapping("/status")
public ConnectionStatus getConnectionStatus() {
int activeConnections = logStreamService.getActiveConnectionCount();
logger.debug("📊 当前活跃SSE连接数: {}", activeConnections);
ConnectionStatus status = new ConnectionStatus();
status.setActiveConnections(activeConnections);
status.setStatus("OK");
return status;
}
/**
* 连接状态DTO
*/
public static class ConnectionStatus {
private int activeConnections;
private String status;
public int getActiveConnections() {
return activeConnections;
}
public void setActiveConnections(int activeConnections) {
this.activeConnections = activeConnections;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
}

View File

@@ -1,222 +0,0 @@
package com.example.demo.controller;
import com.example.demo.model.TaskStatus;
import com.example.demo.service.ContinuousConversationService;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/api/task")
@CrossOrigin(origins = "*")
public class TaskStatusController {
private final ContinuousConversationService conversationService;
public TaskStatusController(ContinuousConversationService conversationService) {
this.conversationService = conversationService;
}
/**
* 获取任务状态
*/
@GetMapping("/status/{taskId}")
public Mono<TaskStatusDto> getTaskStatus(@PathVariable("taskId") String taskId) {
return Mono.fromCallable(() -> {
TaskStatus status = conversationService.getTaskStatus(taskId);
if (status == null) {
throw new RuntimeException("Task not found: " + taskId);
}
TaskStatusDto dto = new TaskStatusDto();
dto.setTaskId(status.getTaskId());
dto.setStatus(status.getStatus());
dto.setCurrentAction(status.getCurrentAction());
dto.setSummary(status.getSummary());
dto.setCurrentTurn(status.getCurrentTurn());
dto.setTotalEstimatedTurns(status.getTotalEstimatedTurns());
dto.setProgressPercentage(status.getProgressPercentage());
dto.setElapsedTime(status.getElapsedTime());
dto.setErrorMessage(status.getErrorMessage());
return dto;
});
}
/**
* 获取对话结果
*/
@GetMapping("/result/{taskId}")
public Mono<ConversationResultDto> getConversationResult(@PathVariable("taskId") String taskId) {
return Mono.fromCallable(() -> {
ContinuousConversationService.ConversationResult result = conversationService.getConversationResult(taskId);
if (result == null) {
throw new RuntimeException("Conversation result not found: " + taskId);
}
ConversationResultDto dto = new ConversationResultDto();
dto.setTaskId(taskId);
dto.setFullResponse(result.getFullResponse());
dto.setTurnResponses(result.getTurnResponses());
dto.setTotalTurns(result.getTotalTurns());
dto.setReachedMaxTurns(result.isReachedMaxTurns());
dto.setStopReason(result.getStopReason());
dto.setTotalDurationMs(result.getTotalDurationMs());
return dto;
});
}
// DTO类
public static class TaskStatusDto {
private String taskId;
private String status;
private String currentAction;
private String summary;
private int currentTurn;
private int totalEstimatedTurns;
private double progressPercentage;
private long elapsedTime;
private String errorMessage;
// Getters and Setters
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getCurrentAction() {
return currentAction;
}
public void setCurrentAction(String currentAction) {
this.currentAction = currentAction;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public int getCurrentTurn() {
return currentTurn;
}
public void setCurrentTurn(int currentTurn) {
this.currentTurn = currentTurn;
}
public int getTotalEstimatedTurns() {
return totalEstimatedTurns;
}
public void setTotalEstimatedTurns(int totalEstimatedTurns) {
this.totalEstimatedTurns = totalEstimatedTurns;
}
public double getProgressPercentage() {
return progressPercentage;
}
public void setProgressPercentage(double progressPercentage) {
this.progressPercentage = progressPercentage;
}
public long getElapsedTime() {
return elapsedTime;
}
public void setElapsedTime(long elapsedTime) {
this.elapsedTime = elapsedTime;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
// 对话结果DTO类
public static class ConversationResultDto {
private String taskId;
private String fullResponse;
private java.util.List<String> turnResponses;
private int totalTurns;
private boolean reachedMaxTurns;
private String stopReason;
private long totalDurationMs;
// Getters and Setters
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getFullResponse() {
return fullResponse;
}
public void setFullResponse(String fullResponse) {
this.fullResponse = fullResponse;
}
public java.util.List<String> getTurnResponses() {
return turnResponses;
}
public void setTurnResponses(java.util.List<String> turnResponses) {
this.turnResponses = turnResponses;
}
public int getTotalTurns() {
return totalTurns;
}
public void setTotalTurns(int totalTurns) {
this.totalTurns = totalTurns;
}
public boolean isReachedMaxTurns() {
return reachedMaxTurns;
}
public void setReachedMaxTurns(boolean reachedMaxTurns) {
this.reachedMaxTurns = reachedMaxTurns;
}
public String getStopReason() {
return stopReason;
}
public void setStopReason(String stopReason) {
this.stopReason = stopReason;
}
public long getTotalDurationMs() {
return totalDurationMs;
}
public void setTotalDurationMs(long totalDurationMs) {
this.totalDurationMs = totalDurationMs;
}
}
}

View File

@@ -1,26 +0,0 @@
package com.example.demo.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Web页面控制器
*/
@Controller
public class WebController {
@GetMapping("/")
public String index() {
return "index";
}
/**
* 处理favicon.ico请求避免404错误
*/
@GetMapping("/favicon.ico")
public ResponseEntity<Void> favicon() {
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
}

View File

@@ -1,45 +0,0 @@
package com.example.demo.dto;
/**
* 聊天请求数据传输对象
*/
public class ChatRequestDto {
private String message;
private String sessionId; // 可选:用于会话管理
public ChatRequestDto() {
}
public ChatRequestDto(String message) {
this.message = message;
}
public ChatRequestDto(String message, String sessionId) {
this.message = message;
this.sessionId = sessionId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
@Override
public String toString() {
return "ChatRequestDto{" +
"message='" + message + '\'' +
", sessionId='" + sessionId + '\'' +
'}';
}
}

View File

@@ -1,389 +0,0 @@
package com.example.demo.model;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Project context information
* Contains complete project analysis results for AI understanding
*/
public class ProjectContext {
private Path projectRoot;
private ProjectType projectType;
private ProjectStructure projectStructure;
private List<DependencyInfo> dependencies;
private List<ConfigFile> configFiles;
private CodeStatistics codeStatistics;
private Map<String, Object> metadata;
private String contextSummary;
public ProjectContext() {
this.dependencies = new ArrayList<>();
this.configFiles = new ArrayList<>();
this.metadata = new HashMap<>();
}
public ProjectContext(Path projectRoot) {
this();
this.projectRoot = projectRoot;
}
/**
* Generate project context summary
*/
public String generateContextSummary() {
StringBuilder summary = new StringBuilder();
// Basic information
summary.append("=== PROJECT CONTEXT ===\n");
summary.append("Project: ").append(projectRoot != null ? projectRoot.getFileName() : "Unknown").append("\n");
summary.append("Type: ").append(projectType != null ? projectType.getDisplayName() : "Unknown").append("\n");
summary.append("Language: ").append(projectType != null ? projectType.getPrimaryLanguage() : "Unknown").append("\n");
summary.append("Package Manager: ").append(projectType != null ? projectType.getPackageManager() : "Unknown").append("\n\n");
// Project structure
if (projectStructure != null) {
summary.append("=== PROJECT STRUCTURE ===\n");
summary.append(projectStructure.getStructureSummary()).append("\n");
}
// Dependencies
if (!dependencies.isEmpty()) {
summary.append("=== DEPENDENCIES ===\n");
dependencies.stream()
.filter(DependencyInfo::isDirectDependency)
.limit(10)
.forEach(dep -> summary.append("- ").append(dep.toString()).append("\n"));
if (dependencies.size() > 10) {
summary.append("... and ").append(dependencies.size() - 10).append(" more dependencies\n");
}
summary.append("\n");
}
// Configuration files
if (!configFiles.isEmpty()) {
summary.append("=== CONFIGURATION FILES ===\n");
configFiles.stream()
.filter(ConfigFile::isMainConfig)
.forEach(config -> summary.append("- ").append(config.getFileName())
.append(" (").append(config.getFileType()).append(")\n"));
summary.append("\n");
}
// Code statistics
if (codeStatistics != null) {
summary.append("=== CODE STATISTICS ===\n");
summary.append("Total Lines: ").append(codeStatistics.getTotalLines()).append("\n");
summary.append("Code Lines: ").append(codeStatistics.getCodeLines()).append("\n");
if (codeStatistics.getTotalClasses() > 0) {
summary.append("Classes: ").append(codeStatistics.getTotalClasses()).append("\n");
}
if (codeStatistics.getTotalMethods() > 0) {
summary.append("Methods: ").append(codeStatistics.getTotalMethods()).append("\n");
}
summary.append("\n");
}
this.contextSummary = summary.toString();
return this.contextSummary;
}
/**
* Get dependency summary
*/
public String getDependencySummary() {
if (dependencies.isEmpty()) {
return "No dependencies found";
}
return dependencies.stream()
.filter(DependencyInfo::isDirectDependency)
.limit(5)
.map(DependencyInfo::getName)
.reduce((a, b) -> a + ", " + b)
.orElse("No direct dependencies");
}
// Getters and Setters
public Path getProjectRoot() {
return projectRoot;
}
public void setProjectRoot(Path projectRoot) {
this.projectRoot = projectRoot;
}
public ProjectType getProjectType() {
return projectType;
}
public void setProjectType(ProjectType projectType) {
this.projectType = projectType;
}
public ProjectStructure getProjectStructure() {
return projectStructure;
}
public void setProjectStructure(ProjectStructure projectStructure) {
this.projectStructure = projectStructure;
}
public List<DependencyInfo> getDependencies() {
return dependencies;
}
public void setDependencies(List<DependencyInfo> dependencies) {
this.dependencies = dependencies;
}
public List<ConfigFile> getConfigFiles() {
return configFiles;
}
public void setConfigFiles(List<ConfigFile> configFiles) {
this.configFiles = configFiles;
}
public CodeStatistics getCodeStatistics() {
return codeStatistics;
}
public void setCodeStatistics(CodeStatistics codeStatistics) {
this.codeStatistics = codeStatistics;
}
public Map<String, Object> getMetadata() {
return metadata;
}
public void setMetadata(Map<String, Object> metadata) {
this.metadata = metadata;
}
public String getContextSummary() {
return contextSummary;
}
public void setContextSummary(String contextSummary) {
this.contextSummary = contextSummary;
}
/**
* Dependency information class
*/
public static class DependencyInfo {
private String name;
private String version;
private String type; // "compile", "test", "runtime", etc.
private String scope;
private boolean isDirectDependency;
public DependencyInfo(String name, String version, String type) {
this.name = name;
this.version = version;
this.type = type;
this.isDirectDependency = true;
}
// Getters and Setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public boolean isDirectDependency() {
return isDirectDependency;
}
public void setDirectDependency(boolean directDependency) {
isDirectDependency = directDependency;
}
@Override
public String toString() {
return String.format("%s:%s (%s)", name, version, type);
}
}
/**
* Configuration file information class
*/
public static class ConfigFile {
private String fileName;
private String relativePath;
private String fileType; // "properties", "yaml", "json", "xml", etc.
private Map<String, Object> keySettings;
private boolean isMainConfig;
public ConfigFile(String fileName, String relativePath, String fileType) {
this.fileName = fileName;
this.relativePath = relativePath;
this.fileType = fileType;
this.keySettings = new HashMap<>();
this.isMainConfig = false;
}
// Getters and Setters
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getRelativePath() {
return relativePath;
}
public void setRelativePath(String relativePath) {
this.relativePath = relativePath;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
public Map<String, Object> getKeySettings() {
return keySettings;
}
public void setKeySettings(Map<String, Object> keySettings) {
this.keySettings = keySettings;
}
public boolean isMainConfig() {
return isMainConfig;
}
public void setMainConfig(boolean mainConfig) {
isMainConfig = mainConfig;
}
public void addSetting(String key, Object value) {
this.keySettings.put(key, value);
}
}
/**
* Code statistics information class
*/
public static class CodeStatistics {
private int totalLines;
private int codeLines;
private int commentLines;
private int blankLines;
private Map<String, Integer> languageLines;
private int totalClasses;
private int totalMethods;
private int totalFunctions;
public CodeStatistics() {
this.languageLines = new HashMap<>();
}
// Getters and Setters
public int getTotalLines() {
return totalLines;
}
public void setTotalLines(int totalLines) {
this.totalLines = totalLines;
}
public int getCodeLines() {
return codeLines;
}
public void setCodeLines(int codeLines) {
this.codeLines = codeLines;
}
public int getCommentLines() {
return commentLines;
}
public void setCommentLines(int commentLines) {
this.commentLines = commentLines;
}
public int getBlankLines() {
return blankLines;
}
public void setBlankLines(int blankLines) {
this.blankLines = blankLines;
}
public Map<String, Integer> getLanguageLines() {
return languageLines;
}
public void setLanguageLines(Map<String, Integer> languageLines) {
this.languageLines = languageLines;
}
public int getTotalClasses() {
return totalClasses;
}
public void setTotalClasses(int totalClasses) {
this.totalClasses = totalClasses;
}
public int getTotalMethods() {
return totalMethods;
}
public void setTotalMethods(int totalMethods) {
this.totalMethods = totalMethods;
}
public int getTotalFunctions() {
return totalFunctions;
}
public void setTotalFunctions(int totalFunctions) {
this.totalFunctions = totalFunctions;
}
public void addLanguageLines(String language, int lines) {
this.languageLines.put(language, this.languageLines.getOrDefault(language, 0) + lines);
}
}
}

View File

@@ -1,278 +0,0 @@
package com.example.demo.model;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Project structure information model
* Contains project directory structure, file statistics and other information
*/
public class ProjectStructure {
private Path projectRoot;
private ProjectType projectType;
private List<DirectoryInfo> directories;
private Map<String, Integer> fileTypeCount;
private List<String> keyFiles;
private int totalFiles;
private int totalDirectories;
private long totalSize;
public ProjectStructure() {
this.directories = new ArrayList<>();
this.fileTypeCount = new HashMap<>();
this.keyFiles = new ArrayList<>();
}
public ProjectStructure(Path projectRoot, ProjectType projectType) {
this();
this.projectRoot = projectRoot;
this.projectType = projectType;
}
/**
* Add directory information
*/
public void addDirectory(DirectoryInfo directoryInfo) {
this.directories.add(directoryInfo);
this.totalDirectories++;
}
/**
* Add file type statistics
*/
public void addFileType(String extension, int count) {
this.fileTypeCount.put(extension, this.fileTypeCount.getOrDefault(extension, 0) + count);
}
/**
* Add key file
*/
public void addKeyFile(String fileName) {
if (!this.keyFiles.contains(fileName)) {
this.keyFiles.add(fileName);
}
}
/**
* Get project structure summary
*/
public String getStructureSummary() {
StringBuilder summary = new StringBuilder();
summary.append("Project: ").append(projectRoot != null ? projectRoot.getFileName() : "Unknown").append("\n");
summary.append("Type: ").append(projectType != null ? projectType.getDisplayName() : "Unknown").append("\n");
summary.append("Directories: ").append(totalDirectories).append("\n");
summary.append("Files: ").append(totalFiles).append("\n");
if (!keyFiles.isEmpty()) {
summary.append("Key Files: ").append(String.join(", ", keyFiles)).append("\n");
}
if (!fileTypeCount.isEmpty()) {
summary.append("File Types: ");
fileTypeCount.entrySet().stream()
.sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
.limit(5)
.forEach(entry -> summary.append(entry.getKey()).append("(").append(entry.getValue()).append(") "));
summary.append("\n");
}
return summary.toString();
}
/**
* Get important directories list
*/
public List<DirectoryInfo> getImportantDirectories() {
return directories.stream()
.filter(DirectoryInfo::isImportant)
.toList();
}
/**
* Mark important directories based on project type
*/
public void markImportantDirectories() {
if (projectType == null) return;
for (DirectoryInfo dir : directories) {
String dirName = dir.getName().toLowerCase();
// Common important directories
if (dirName.equals("src") || dirName.equals("source") ||
dirName.equals("test") || dirName.equals("tests") ||
dirName.equals("config") || dirName.equals("conf") ||
dirName.equals("docs") || dirName.equals("doc")) {
dir.setImportant(true);
continue;
}
// Project type specific important directories
switch (projectType) {
case JAVA_MAVEN:
case JAVA_GRADLE:
case SPRING_BOOT:
if (dirName.equals("main") || dirName.equals("resources") ||
dirName.equals("webapp") || dirName.equals("target") ||
dirName.equals("build")) {
dir.setImportant(true);
}
break;
case NODE_JS:
case REACT:
case VUE:
case ANGULAR:
case NEXT_JS:
if (dirName.equals("node_modules") || dirName.equals("public") ||
dirName.equals("dist") || dirName.equals("build") ||
dirName.equals("components") || dirName.equals("pages")) {
dir.setImportant(true);
}
break;
case PYTHON:
case DJANGO:
case FLASK:
case FASTAPI:
if (dirName.equals("venv") || dirName.equals("env") ||
dirName.equals("__pycache__") || dirName.equals("migrations") ||
dirName.equals("static") || dirName.equals("templates")) {
dir.setImportant(true);
}
break;
}
}
}
// Getters and Setters
public Path getProjectRoot() {
return projectRoot;
}
public void setProjectRoot(Path projectRoot) {
this.projectRoot = projectRoot;
}
public ProjectType getProjectType() {
return projectType;
}
public void setProjectType(ProjectType projectType) {
this.projectType = projectType;
}
public List<DirectoryInfo> getDirectories() {
return directories;
}
public void setDirectories(List<DirectoryInfo> directories) {
this.directories = directories;
}
public Map<String, Integer> getFileTypeCount() {
return fileTypeCount;
}
public void setFileTypeCount(Map<String, Integer> fileTypeCount) {
this.fileTypeCount = fileTypeCount;
}
public List<String> getKeyFiles() {
return keyFiles;
}
public void setKeyFiles(List<String> keyFiles) {
this.keyFiles = keyFiles;
}
public int getTotalFiles() {
return totalFiles;
}
public void setTotalFiles(int totalFiles) {
this.totalFiles = totalFiles;
}
public int getTotalDirectories() {
return totalDirectories;
}
public void setTotalDirectories(int totalDirectories) {
this.totalDirectories = totalDirectories;
}
public long getTotalSize() {
return totalSize;
}
public void setTotalSize(long totalSize) {
this.totalSize = totalSize;
}
/**
* Directory information inner class
*/
public static class DirectoryInfo {
private String name;
private String relativePath;
private int fileCount;
private List<String> files;
private boolean isImportant; // Whether it's an important directory (like src, test, etc.)
public DirectoryInfo(String name, String relativePath) {
this.name = name;
this.relativePath = relativePath;
this.files = new ArrayList<>();
this.isImportant = false;
}
// Getters and Setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRelativePath() {
return relativePath;
}
public void setRelativePath(String relativePath) {
this.relativePath = relativePath;
}
public int getFileCount() {
return fileCount;
}
public void setFileCount(int fileCount) {
this.fileCount = fileCount;
}
public List<String> getFiles() {
return files;
}
public void setFiles(List<String> files) {
this.files = files;
}
public boolean isImportant() {
return isImportant;
}
public void setImportant(boolean important) {
isImportant = important;
}
public void addFile(String fileName) {
this.files.add(fileName);
this.fileCount++;
}
}
}

View File

@@ -1,147 +0,0 @@
package com.example.demo.model;
/**
* Project type enumeration
* Supports mainstream project type detection
*/
public enum ProjectType {
// Java projects
JAVA_MAVEN("Java Maven", "pom.xml", "Maven-based Java project"),
JAVA_GRADLE("Java Gradle", "build.gradle", "Gradle-based Java project"),
SPRING_BOOT("Spring Boot", "pom.xml", "Spring Boot application"),
// JavaScript/Node.js projects
NODE_JS("Node.js", "package.json", "Node.js project"),
REACT("React", "package.json", "React application"),
VUE("Vue.js", "package.json", "Vue.js application"),
ANGULAR("Angular", "package.json", "Angular application"),
NEXT_JS("Next.js", "package.json", "Next.js application"),
// Python projects
PYTHON("Python", "requirements.txt", "Python project"),
DJANGO("Django", "manage.py", "Django web application"),
FLASK("Flask", "app.py", "Flask web application"),
FASTAPI("FastAPI", "main.py", "FastAPI application"),
// Other project types
DOTNET("ASP.NET", "*.csproj", ".NET project"),
GO("Go", "go.mod", "Go project"),
RUST("Rust", "Cargo.toml", "Rust project"),
PHP("PHP", "composer.json", "PHP project"),
// Web frontend
HTML_STATIC("Static HTML", "index.html", "Static HTML website"),
// Unknown type
UNKNOWN("Unknown", "", "Unknown project type");
private final String displayName;
private final String keyFile;
private final String description;
ProjectType(String displayName, String keyFile, String description) {
this.displayName = displayName;
this.keyFile = keyFile;
this.description = description;
}
public String getDisplayName() {
return displayName;
}
public String getKeyFile() {
return keyFile;
}
public String getDescription() {
return description;
}
/**
* Check if it's a Java project
*/
public boolean isJavaProject() {
return this == JAVA_MAVEN || this == JAVA_GRADLE || this == SPRING_BOOT;
}
/**
* Check if it's a JavaScript project
*/
public boolean isJavaScriptProject() {
return this == NODE_JS || this == REACT || this == VUE ||
this == ANGULAR || this == NEXT_JS;
}
/**
* Check if it's a Python project
*/
public boolean isPythonProject() {
return this == PYTHON || this == DJANGO || this == FLASK || this == FASTAPI;
}
/**
* Check if it's a Web project
*/
public boolean isWebProject() {
return isJavaScriptProject() || this == HTML_STATIC ||
this == DJANGO || this == FLASK || this == FASTAPI || this == SPRING_BOOT;
}
/**
* Get the primary programming language of the project
*/
public String getPrimaryLanguage() {
if (isJavaProject()) return "Java";
if (isJavaScriptProject()) return "JavaScript";
if (isPythonProject()) return "Python";
switch (this) {
case DOTNET:
return "C#";
case GO:
return "Go";
case RUST:
return "Rust";
case PHP:
return "PHP";
case HTML_STATIC:
return "HTML";
default:
return "Unknown";
}
}
/**
* Get the recommended package manager
*/
public String getPackageManager() {
switch (this) {
case JAVA_MAVEN:
case SPRING_BOOT:
return "Maven";
case JAVA_GRADLE:
return "Gradle";
case NODE_JS:
case REACT:
case VUE:
case ANGULAR:
case NEXT_JS:
return "npm/yarn";
case PYTHON:
case DJANGO:
case FLASK:
case FASTAPI:
return "pip";
case DOTNET:
return "NuGet";
case GO:
return "go mod";
case RUST:
return "Cargo";
case PHP:
return "Composer";
default:
return "Unknown";
}
}
}

View File

@@ -1,117 +0,0 @@
package com.example.demo.model;
import java.util.ArrayList;
import java.util.List;
public class TaskStatus {
private String taskId;
private String status; // RUNNING, COMPLETED, ERROR
private String currentAction;
private String summary;
private int currentTurn;
private int totalEstimatedTurns;
private long startTime;
private long lastUpdateTime;
private List<String> actionHistory;
private String errorMessage;
private double progressPercentage;
public TaskStatus(String taskId) {
this.taskId = taskId;
this.status = "RUNNING";
this.startTime = System.currentTimeMillis();
this.lastUpdateTime = this.startTime;
this.actionHistory = new ArrayList<>();
this.progressPercentage = 0.0;
}
// Getters and Setters
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
this.lastUpdateTime = System.currentTimeMillis();
}
public String getCurrentAction() {
return currentAction;
}
public void setCurrentAction(String currentAction) {
this.currentAction = currentAction;
this.lastUpdateTime = System.currentTimeMillis();
if (currentAction != null && !currentAction.trim().isEmpty()) {
this.actionHistory.add(currentAction);
}
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public int getCurrentTurn() {
return currentTurn;
}
public void setCurrentTurn(int currentTurn) {
this.currentTurn = currentTurn;
updateProgress();
}
public int getTotalEstimatedTurns() {
return totalEstimatedTurns;
}
public void setTotalEstimatedTurns(int totalEstimatedTurns) {
this.totalEstimatedTurns = totalEstimatedTurns;
updateProgress();
}
public long getStartTime() {
return startTime;
}
public long getLastUpdateTime() {
return lastUpdateTime;
}
public List<String> getActionHistory() {
return actionHistory;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public double getProgressPercentage() {
return progressPercentage;
}
private void updateProgress() {
if (totalEstimatedTurns > 0) {
this.progressPercentage = Math.min(100.0, (double) currentTurn / totalEstimatedTurns * 100.0);
}
}
public long getElapsedTime() {
return System.currentTimeMillis() - startTime;
}
}

View File

@@ -1,195 +0,0 @@
package com.example.demo.schema;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* JSON Schema definition class
* Used to define tool parameter structure and validation rules
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class JsonSchema {
private String type;
private String description;
private String pattern;
private Number minimum;
private Number maximum;
private List<Object> enumValues;
@JsonProperty("properties")
private Map<String, JsonSchema> properties;
@JsonProperty("required")
private List<String> requiredFields;
@JsonProperty("items")
private JsonSchema items;
// Constructor
public JsonSchema() {
}
// Static factory methods
public static JsonSchema object() {
JsonSchema schema = new JsonSchema();
schema.type = "object";
schema.properties = new HashMap<>();
return schema;
}
public static JsonSchema string(String description) {
JsonSchema schema = new JsonSchema();
schema.type = "string";
schema.description = description;
return schema;
}
public static JsonSchema number(String description) {
JsonSchema schema = new JsonSchema();
schema.type = "number";
schema.description = description;
return schema;
}
public static JsonSchema integer(String description) {
JsonSchema schema = new JsonSchema();
schema.type = "integer";
schema.description = description;
return schema;
}
public static JsonSchema bool(String description) {
JsonSchema schema = new JsonSchema();
schema.type = "boolean";
schema.description = description;
return schema;
}
public static JsonSchema array(JsonSchema items) {
JsonSchema schema = new JsonSchema();
schema.type = "array";
schema.items = items;
return schema;
}
public static JsonSchema array(String description, JsonSchema items) {
JsonSchema schema = new JsonSchema();
schema.type = "array";
schema.description = description;
schema.items = items;
return schema;
}
// Fluent methods
public JsonSchema addProperty(String name, JsonSchema property) {
if (this.properties == null) {
this.properties = new HashMap<>();
}
this.properties.put(name, property);
return this;
}
public JsonSchema required(String... fields) {
this.requiredFields = Arrays.asList(fields);
return this;
}
public JsonSchema pattern(String pattern) {
this.pattern = pattern;
return this;
}
public JsonSchema minimum(Number minimum) {
this.minimum = minimum;
return this;
}
public JsonSchema maximum(Number maximum) {
this.maximum = maximum;
return this;
}
public JsonSchema enumValues(Object... values) {
this.enumValues = Arrays.asList(values);
return this;
}
// Getters and Setters
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getPattern() {
return pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
public Number getMinimum() {
return minimum;
}
public void setMinimum(Number minimum) {
this.minimum = minimum;
}
public Number getMaximum() {
return maximum;
}
public void setMaximum(Number maximum) {
this.maximum = maximum;
}
public List<Object> getEnumValues() {
return enumValues;
}
public void setEnumValues(List<Object> enumValues) {
this.enumValues = enumValues;
}
public Map<String, JsonSchema> getProperties() {
return properties;
}
public void setProperties(Map<String, JsonSchema> properties) {
this.properties = properties;
}
public List<String> getRequiredFields() {
return requiredFields;
}
public void setRequiredFields(List<String> requiredFields) {
this.requiredFields = requiredFields;
}
public JsonSchema getItems() {
return items;
}
public void setItems(JsonSchema items) {
this.items = items;
}
}

View File

@@ -1,134 +0,0 @@
package com.example.demo.schema;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SpecVersion;
import com.networknt.schema.ValidationMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Set;
import java.util.stream.Collectors;
/**
* JSON Schema validator
* Used to validate tool parameters against defined schema
*/
@Component
public class SchemaValidator {
private static final Logger logger = LoggerFactory.getLogger(SchemaValidator.class);
private final ObjectMapper objectMapper;
private final JsonSchemaFactory schemaFactory;
public SchemaValidator() {
this.objectMapper = new ObjectMapper();
this.schemaFactory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
}
/**
* Validate data against schema
*
* @param schema JSON Schema definition
* @param data Data to validate
* @return Validation error message, null means validation passed
*/
public String validate(JsonSchema schema, Object data) {
try {
// Convert custom JsonSchema to standard JSON Schema string
String schemaJson = objectMapper.writeValueAsString(schema);
logger.debug("Schema JSON: {}", schemaJson);
// Create JSON Schema validator
com.networknt.schema.JsonSchema jsonSchema = schemaFactory.getSchema(schemaJson);
// Convert data to JsonNode
String dataJson = objectMapper.writeValueAsString(data);
JsonNode dataNode = objectMapper.readTree(dataJson);
logger.debug("Data JSON: {}", dataJson);
// Execute validation
Set<ValidationMessage> errors = jsonSchema.validate(dataNode);
if (errors.isEmpty()) {
logger.debug("Schema validation passed");
return null; // Validation passed
} else {
String errorMessage = errors.stream()
.map(ValidationMessage::getMessage)
.collect(Collectors.joining("; "));
logger.warn("Schema validation failed: {}", errorMessage);
return errorMessage;
}
} catch (Exception e) {
String errorMessage = "Schema validation error: " + e.getMessage();
logger.error(errorMessage, e);
return errorMessage;
}
}
/**
* Simple type validation (fallback solution)
* Used when JSON Schema validation fails
*/
public String validateSimple(JsonSchema schema, Object data) {
if (schema == null || data == null) {
return "Schema or data is null";
}
// Basic type checking
String expectedType = schema.getType();
if (expectedType != null) {
String actualType = getDataType(data);
if (!isTypeCompatible(expectedType, actualType)) {
return String.format("Type mismatch: expected %s, got %s", expectedType, actualType);
}
}
// Required field checking (only for object type)
if ("object".equals(expectedType) && schema.getRequiredFields() != null) {
if (!(data instanceof java.util.Map)) {
return "Expected object type for required field validation";
}
@SuppressWarnings("unchecked")
java.util.Map<String, Object> dataMap = (java.util.Map<String, Object>) data;
for (String requiredField : schema.getRequiredFields()) {
if (!dataMap.containsKey(requiredField) || dataMap.get(requiredField) == null) {
return "Missing required field: " + requiredField;
}
}
}
return null; // Validation passed
}
private String getDataType(Object data) {
if (data == null) return "null";
if (data instanceof String) return "string";
if (data instanceof Integer || data instanceof Long) return "integer";
if (data instanceof Number) return "number";
if (data instanceof Boolean) return "boolean";
if (data instanceof java.util.List) return "array";
if (data instanceof java.util.Map) return "object";
return "unknown";
}
private boolean isTypeCompatible(String expectedType, String actualType) {
if (expectedType.equals(actualType)) {
return true;
}
// Number type compatibility
if ("number".equals(expectedType) && "integer".equals(actualType)) {
return true;
}
return false;
}
}

View File

@@ -1,80 +0,0 @@
package com.example.demo.service;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* 日志事件基类
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class LogEvent {
private String type;
private String taskId;
private String message;
private String timestamp;
// Constructors
public LogEvent() {
}
public LogEvent(String type, String taskId, String message, String timestamp) {
this.type = type;
this.taskId = taskId;
this.message = message;
this.timestamp = timestamp;
}
// Static factory methods
public static LogEvent createConnectionEvent(String taskId) {
LogEvent event = new LogEvent();
event.setType("CONNECTION_ESTABLISHED");
event.setTaskId(taskId);
event.setMessage("SSE连接已建立");
event.setTimestamp(java.time.LocalDateTime.now().format(
java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
return event;
}
// Getters and Setters
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
@Override
public String toString() {
return "LogEvent{" +
"type='" + type + '\'' +
", taskId='" + taskId + '\'' +
", message='" + message + '\'' +
", timestamp='" + timestamp + '\'' +
'}';
}
}

View File

@@ -1,210 +0,0 @@
package com.example.demo.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* SSE日志推送服务
* 负责将AOP日志实时推送到前端
*/
@Service
public class LogStreamService {
private static final Logger logger = LoggerFactory.getLogger(LogStreamService.class);
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 活跃的SSE连接 taskId -> SseEmitter
private final Map<String, SseEmitter> activeConnections = new ConcurrentHashMap<>();
// JSON序列化器
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 建立SSE连接
*/
public SseEmitter createConnection(String taskId) {
logger.info("🔗 建立SSE连接: taskId={}", taskId);
SseEmitter emitter = new SseEmitter(0L); // 无超时
// 设置连接事件处理
emitter.onCompletion(() -> {
logger.info("✅ SSE连接完成: taskId={}", taskId);
activeConnections.remove(taskId);
});
emitter.onTimeout(() -> {
logger.warn("⏰ SSE连接超时: taskId={}", taskId);
activeConnections.remove(taskId);
});
emitter.onError((ex) -> {
logger.error("❌ SSE连接错误: taskId={}, error={}", taskId, ex.getMessage());
activeConnections.remove(taskId);
});
// 保存连接
activeConnections.put(taskId, emitter);
// 发送连接成功消息
sendLogEvent(taskId, LogEvent.createConnectionEvent(taskId));
return emitter;
}
/**
* 关闭SSE连接
*/
public void closeConnection(String taskId) {
SseEmitter emitter = activeConnections.remove(taskId);
if (emitter != null) {
try {
emitter.complete();
logger.info("🔚 关闭SSE连接: taskId={}", taskId);
} catch (Exception e) {
logger.error("关闭SSE连接失败: taskId={}, error={}", taskId, e.getMessage());
}
}
}
/**
* 推送工具开始执行事件
*/
public void pushToolStart(String taskId, String toolName, String filePath, String message) {
ToolLogEvent event = new ToolLogEvent();
event.setType("TOOL_START");
event.setTaskId(taskId);
event.setToolName(toolName);
event.setFilePath(filePath);
event.setMessage(message);
event.setTimestamp(LocalDateTime.now().format(formatter));
event.setIcon(getToolIcon(toolName));
event.setStatus("RUNNING");
sendLogEvent(taskId, event);
}
/**
* 推送工具执行成功事件
*/
public void pushToolSuccess(String taskId, String toolName, String filePath, String message, long executionTime) {
ToolLogEvent event = new ToolLogEvent();
event.setType("TOOL_SUCCESS");
event.setTaskId(taskId);
event.setToolName(toolName);
event.setFilePath(filePath);
event.setMessage(message);
event.setTimestamp(LocalDateTime.now().format(formatter));
event.setIcon(getToolIcon(toolName));
event.setStatus("SUCCESS");
event.setExecutionTime(executionTime);
sendLogEvent(taskId, event);
}
/**
* 推送工具执行失败事件
*/
public void pushToolError(String taskId, String toolName, String filePath, String message, long executionTime) {
ToolLogEvent event = new ToolLogEvent();
event.setType("TOOL_ERROR");
event.setTaskId(taskId);
event.setToolName(toolName);
event.setFilePath(filePath);
event.setMessage(message);
event.setTimestamp(LocalDateTime.now().format(formatter));
event.setIcon("");
event.setStatus("ERROR");
event.setExecutionTime(executionTime);
sendLogEvent(taskId, event);
}
/**
* 推送任务完成事件
*/
public void pushTaskComplete(String taskId) {
LogEvent event = new LogEvent();
event.setType("TASK_COMPLETE");
event.setTaskId(taskId);
event.setMessage("任务执行完成");
event.setTimestamp(LocalDateTime.now().format(formatter));
sendLogEvent(taskId, event);
// 延迟关闭连接
new Thread(() -> {
try {
Thread.sleep(2000); // 等待2秒让前端处理完成事件
closeConnection(taskId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
/**
* 发送日志事件到前端
*/
private void sendLogEvent(String taskId, Object event) {
SseEmitter emitter = activeConnections.get(taskId);
if (emitter != null) {
try {
String jsonData = objectMapper.writeValueAsString(event);
logger.info("📤 准备推送日志事件: taskId={}, type={}, data={}", taskId,
event instanceof LogEvent ? ((LogEvent) event).getType() : "unknown", jsonData);
emitter.send(SseEmitter.event()
.name("log")
.data(jsonData));
logger.info("✅ 日志事件推送成功: taskId={}", taskId);
} catch (IOException e) {
logger.error("推送日志事件失败: taskId={}, error={}", taskId, e.getMessage());
activeConnections.remove(taskId);
}
} else {
logger.warn("⚠️ 未找到SSE连接: taskId={}, 无法推送事件", taskId);
}
}
/**
* 获取工具图标
*/
private String getToolIcon(String toolName) {
switch (toolName) {
case "readFile":
return "📖";
case "writeFile":
return "✏️";
case "editFile":
return "📝";
case "listDirectory":
return "📁";
case "analyzeProject":
return "🔍";
case "scaffoldProject":
return "🏗️";
case "smartEdit":
return "🧠";
default:
return "⚙️";
}
}
/**
* 获取活跃连接数
*/
public int getActiveConnectionCount() {
return activeConnections.size();
}
}

View File

@@ -1,354 +0,0 @@
package com.example.demo.service;
import com.example.demo.model.ProjectContext;
import com.example.demo.model.ProjectStructure;
import com.example.demo.model.ProjectType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
/**
* 项目上下文分析器
* 提供完整的项目分析功能生成AI可理解的项目上下文
*/
@Service
public class ProjectContextAnalyzer {
private static final Logger logger = LoggerFactory.getLogger(ProjectContextAnalyzer.class);
@Autowired
public ProjectTypeDetector projectTypeDetector;
@Autowired
public ProjectDiscoveryService projectDiscoveryService;
/**
* 分析项目并生成完整上下文
*
* @param projectRoot 项目根目录
* @return 项目上下文信息
*/
public ProjectContext analyzeProject(Path projectRoot) {
logger.info("Starting comprehensive project analysis for: {}", projectRoot);
ProjectContext context = new ProjectContext(projectRoot);
try {
// 1. 检测项目类型
ProjectType projectType = projectTypeDetector.detectProjectType(projectRoot);
context.setProjectType(projectType);
logger.debug("Detected project type: {}", projectType);
// 2. 分析项目结构
ProjectStructure structure = projectDiscoveryService.analyzeProjectStructure(projectRoot);
context.setProjectStructure(structure);
logger.debug("Analyzed project structure with {} directories",
structure.getDirectories().size());
// 3. 分析依赖关系
List<ProjectContext.DependencyInfo> dependencies =
projectDiscoveryService.analyzeDependencies(projectRoot);
context.setDependencies(dependencies);
logger.debug("Found {} dependencies", dependencies.size());
// 4. 查找配置文件
List<ProjectContext.ConfigFile> configFiles =
projectDiscoveryService.findConfigurationFiles(projectRoot);
context.setConfigFiles(configFiles);
logger.debug("Found {} configuration files", configFiles.size());
// 5. 分析代码统计
ProjectContext.CodeStatistics codeStats = analyzeCodeStatistics(projectRoot, projectType);
context.setCodeStatistics(codeStats);
logger.debug("Code statistics: {} total lines", codeStats.getTotalLines());
// 6. 收集项目元数据
Map<String, Object> metadata = collectProjectMetadata(projectRoot, projectType);
context.setMetadata(metadata);
// 7. 生成上下文摘要
String summary = context.generateContextSummary();
logger.debug("Generated context summary with {} characters", summary.length());
logger.info("Project analysis completed successfully for: {}", projectRoot);
return context;
} catch (Exception e) {
logger.error("Error during project analysis for: " + projectRoot, e);
// 返回部分分析结果
return context;
}
}
/**
* 分析代码统计信息
*/
private ProjectContext.CodeStatistics analyzeCodeStatistics(Path projectRoot, ProjectType projectType) {
logger.debug("Analyzing code statistics for: {}", projectRoot);
ProjectContext.CodeStatistics stats = new ProjectContext.CodeStatistics();
try {
analyzeCodeInDirectory(projectRoot, stats, projectType, 0, 3);
} catch (Exception e) {
logger.warn("Error analyzing code statistics", e);
}
return stats;
}
/**
* 递归分析目录中的代码
*/
private void analyzeCodeInDirectory(Path directory, ProjectContext.CodeStatistics stats,
ProjectType projectType, int currentDepth, int maxDepth) {
if (currentDepth > maxDepth) {
return;
}
try (Stream<Path> paths = Files.list(directory)) {
paths.forEach(path -> {
try {
if (Files.isDirectory(path)) {
String dirName = path.getFileName().toString();
// 跳过不需要分析的目录
if (!shouldSkipDirectory(dirName)) {
analyzeCodeInDirectory(path, stats, projectType, currentDepth + 1, maxDepth);
}
} else if (Files.isRegularFile(path)) {
analyzeCodeFile(path, stats, projectType);
}
} catch (Exception e) {
logger.warn("Error processing path during code analysis: " + path, e);
}
});
} catch (IOException e) {
logger.warn("Error listing directory: " + directory, e);
}
}
/**
* 分析单个代码文件
*/
private void analyzeCodeFile(Path filePath, ProjectContext.CodeStatistics stats, ProjectType projectType) {
String fileName = filePath.getFileName().toString();
String extension = getFileExtension(fileName).toLowerCase();
// 只分析代码文件
if (!isCodeFile(extension, projectType)) {
return;
}
try {
List<String> lines = Files.readAllLines(filePath);
int totalLines = lines.size();
int codeLines = 0;
int commentLines = 0;
int blankLines = 0;
for (String line : lines) {
String trimmedLine = line.trim();
if (trimmedLine.isEmpty()) {
blankLines++;
} else if (isCommentLine(trimmedLine, extension)) {
commentLines++;
} else {
codeLines++;
}
}
// 更新统计信息
stats.setTotalLines(stats.getTotalLines() + totalLines);
stats.setCodeLines(stats.getCodeLines() + codeLines);
stats.setCommentLines(stats.getCommentLines() + commentLines);
stats.setBlankLines(stats.getBlankLines() + blankLines);
// 按语言统计
String language = getLanguageByExtension(extension);
stats.addLanguageLines(language, totalLines);
// 分析类和方法(简单实现)
if (extension.equals(".java")) {
analyzeJavaFile(lines, stats);
} else if (extension.equals(".js") || extension.equals(".ts")) {
analyzeJavaScriptFile(lines, stats);
} else if (extension.equals(".py")) {
analyzePythonFile(lines, stats);
}
} catch (IOException e) {
logger.warn("Error reading file for code analysis: " + filePath, e);
}
}
/**
* 分析Java文件
*/
private void analyzeJavaFile(List<String> lines, ProjectContext.CodeStatistics stats) {
for (String line : lines) {
String trimmedLine = line.trim();
if (trimmedLine.matches(".*\\bclass\\s+\\w+.*")) {
stats.setTotalClasses(stats.getTotalClasses() + 1);
}
if (trimmedLine.matches(".*\\b(public|private|protected)\\s+.*\\s+\\w+\\s*\\(.*\\).*")) {
stats.setTotalMethods(stats.getTotalMethods() + 1);
}
}
}
/**
* 分析JavaScript文件
*/
private void analyzeJavaScriptFile(List<String> lines, ProjectContext.CodeStatistics stats) {
for (String line : lines) {
String trimmedLine = line.trim();
if (trimmedLine.matches(".*\\bfunction\\s+\\w+.*") ||
trimmedLine.matches(".*\\w+\\s*:\\s*function.*") ||
trimmedLine.matches(".*\\w+\\s*=\\s*\\(.*\\)\\s*=>.*")) {
stats.setTotalFunctions(stats.getTotalFunctions() + 1);
}
}
}
/**
* 分析Python文件
*/
private void analyzePythonFile(List<String> lines, ProjectContext.CodeStatistics stats) {
for (String line : lines) {
String trimmedLine = line.trim();
if (trimmedLine.matches("^class\\s+\\w+.*:")) {
stats.setTotalClasses(stats.getTotalClasses() + 1);
}
if (trimmedLine.matches("^def\\s+\\w+.*:")) {
stats.setTotalFunctions(stats.getTotalFunctions() + 1);
}
}
}
/**
* 收集项目元数据
*/
private Map<String, Object> collectProjectMetadata(Path projectRoot, ProjectType projectType) {
Map<String, Object> metadata = new HashMap<>();
metadata.put("projectName", projectRoot.getFileName().toString());
metadata.put("projectType", projectType.name());
metadata.put("primaryLanguage", projectType.getPrimaryLanguage());
metadata.put("packageManager", projectType.getPackageManager());
metadata.put("analysisTimestamp", System.currentTimeMillis());
// 检查版本控制
if (Files.exists(projectRoot.resolve(".git"))) {
metadata.put("versionControl", "Git");
}
// 检查CI/CD配置
if (Files.exists(projectRoot.resolve(".github"))) {
metadata.put("cicd", "GitHub Actions");
} else if (Files.exists(projectRoot.resolve(".gitlab-ci.yml"))) {
metadata.put("cicd", "GitLab CI");
}
// 检查Docker支持
if (Files.exists(projectRoot.resolve("Dockerfile"))) {
metadata.put("containerization", "Docker");
}
return metadata;
}
/**
* 生成编辑上下文
*/
public String buildEditContext(Path projectRoot, String editDescription) {
logger.debug("Building edit context for: {}", projectRoot);
ProjectContext context = analyzeProject(projectRoot);
StringBuilder contextBuilder = new StringBuilder();
contextBuilder.append("=== EDIT CONTEXT ===\n");
contextBuilder.append("Edit Request: ").append(editDescription).append("\n\n");
contextBuilder.append(context.generateContextSummary());
return contextBuilder.toString();
}
// 辅助方法
private boolean shouldSkipDirectory(String dirName) {
return dirName.equals(".git") || dirName.equals("node_modules") ||
dirName.equals("target") || dirName.equals("build") ||
dirName.equals("dist") || dirName.equals("__pycache__") ||
dirName.startsWith(".");
}
private String getFileExtension(String fileName) {
int lastDot = fileName.lastIndexOf('.');
return lastDot > 0 ? fileName.substring(lastDot) : "";
}
private boolean isCodeFile(String extension, ProjectType projectType) {
return extension.equals(".java") || extension.equals(".js") || extension.equals(".ts") ||
extension.equals(".py") || extension.equals(".html") || extension.equals(".css") ||
extension.equals(".jsx") || extension.equals(".tsx") || extension.equals(".vue") ||
extension.equals(".go") || extension.equals(".rs") || extension.equals(".php") ||
extension.equals(".cs") || extension.equals(".cpp") || extension.equals(".c");
}
private boolean isCommentLine(String line, String extension) {
switch (extension) {
case ".java":
case ".js":
case ".ts":
case ".jsx":
case ".tsx":
case ".css":
return line.startsWith("//") || line.startsWith("/*") || line.startsWith("*");
case ".py":
return line.startsWith("#");
case ".html":
return line.startsWith("<!--");
default:
return line.startsWith("#") || line.startsWith("//");
}
}
private String getLanguageByExtension(String extension) {
switch (extension) {
case ".java":
return "Java";
case ".js":
case ".jsx":
return "JavaScript";
case ".ts":
case ".tsx":
return "TypeScript";
case ".py":
return "Python";
case ".html":
return "HTML";
case ".css":
return "CSS";
case ".vue":
return "Vue";
case ".go":
return "Go";
case ".rs":
return "Rust";
case ".php":
return "PHP";
case ".cs":
return "C#";
default:
return "Other";
}
}
}

View File

@@ -1,382 +0,0 @@
package com.example.demo.service;
import com.example.demo.model.ProjectContext;
import com.example.demo.model.ProjectStructure;
import com.example.demo.model.ProjectType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* 项目发现和分析服务
* 负责分析项目结构、依赖关系和配置信息
*/
@Service
public class ProjectDiscoveryService {
private static final Logger logger = LoggerFactory.getLogger(ProjectDiscoveryService.class);
private final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private ProjectTypeDetector projectTypeDetector;
/**
* 分析项目结构
*
* @param projectRoot 项目根目录
* @return 项目结构信息
*/
public ProjectStructure analyzeProjectStructure(Path projectRoot) {
logger.debug("Analyzing project structure for: {}", projectRoot);
ProjectType projectType = projectTypeDetector.detectProjectType(projectRoot);
ProjectStructure structure = new ProjectStructure(projectRoot, projectType);
try {
analyzeDirectoryStructure(projectRoot, structure, 0, 3); // 最大深度3层
structure.markImportantDirectories();
logger.info("Project structure analysis completed for: {}", projectRoot);
return structure;
} catch (IOException e) {
logger.error("Error analyzing project structure for: " + projectRoot, e);
return structure; // 返回部分分析结果
}
}
/**
* 递归分析目录结构
*/
private void analyzeDirectoryStructure(Path currentPath, ProjectStructure structure,
int currentDepth, int maxDepth) throws IOException {
if (currentDepth > maxDepth) {
return;
}
try (Stream<Path> paths = Files.list(currentPath)) {
paths.forEach(path -> {
try {
if (Files.isDirectory(path)) {
String dirName = path.getFileName().toString();
String relativePath = structure.getProjectRoot().relativize(path).toString();
// 跳过常见的忽略目录
if (shouldIgnoreDirectory(dirName)) {
return;
}
ProjectStructure.DirectoryInfo dirInfo =
new ProjectStructure.DirectoryInfo(dirName, relativePath);
// 分析目录中的文件
analyzeDirectoryFiles(path, dirInfo);
structure.addDirectory(dirInfo);
// 递归分析子目录
if (currentDepth < maxDepth) {
analyzeDirectoryStructure(path, structure, currentDepth + 1, maxDepth);
}
} else if (Files.isRegularFile(path)) {
// 处理根目录下的文件
String fileName = path.getFileName().toString();
String extension = getFileExtension(fileName);
structure.addFileType(extension, 1);
structure.setTotalFiles(structure.getTotalFiles() + 1);
// 检查是否为关键文件
if (isKeyFile(fileName, structure.getProjectType())) {
structure.addKeyFile(fileName);
}
// 累计文件大小
try {
structure.setTotalSize(structure.getTotalSize() + Files.size(path));
} catch (IOException e) {
logger.warn("Could not get size for file: {}", path);
}
}
} catch (Exception e) {
logger.warn("Error processing path: " + path, e);
}
});
}
}
/**
* 分析目录中的文件
*/
private void analyzeDirectoryFiles(Path directory, ProjectStructure.DirectoryInfo dirInfo) {
try (Stream<Path> files = Files.list(directory)) {
files.filter(Files::isRegularFile)
.forEach(file -> {
String fileName = file.getFileName().toString();
dirInfo.addFile(fileName);
});
} catch (IOException e) {
logger.warn("Error analyzing files in directory: {}", directory);
}
}
/**
* 分析项目依赖
*/
public List<ProjectContext.DependencyInfo> analyzeDependencies(Path projectRoot) {
logger.debug("Analyzing dependencies for: {}", projectRoot);
List<ProjectContext.DependencyInfo> dependencies = new ArrayList<>();
ProjectType projectType = projectTypeDetector.detectProjectType(projectRoot);
try {
switch (projectType) {
case JAVA_MAVEN:
case SPRING_BOOT:
dependencies.addAll(analyzeMavenDependencies(projectRoot));
break;
case NODE_JS:
case REACT:
case VUE:
case ANGULAR:
case NEXT_JS:
dependencies.addAll(analyzeNpmDependencies(projectRoot));
break;
case PYTHON:
case DJANGO:
case FLASK:
case FASTAPI:
dependencies.addAll(analyzePythonDependencies(projectRoot));
break;
default:
logger.info("Dependency analysis not supported for project type: {}", projectType);
}
} catch (Exception e) {
logger.error("Error analyzing dependencies for: " + projectRoot, e);
}
logger.info("Found {} dependencies for project: {}", dependencies.size(), projectRoot);
return dependencies;
}
/**
* 分析Maven依赖
*/
private List<ProjectContext.DependencyInfo> analyzeMavenDependencies(Path projectRoot) {
List<ProjectContext.DependencyInfo> dependencies = new ArrayList<>();
Path pomFile = projectRoot.resolve("pom.xml");
if (!Files.exists(pomFile)) {
return dependencies;
}
try {
String pomContent = Files.readString(pomFile);
// 简单的XML解析 - 在实际项目中应该使用专门的XML解析器
if (pomContent.contains("spring-boot-starter-web")) {
dependencies.add(new ProjectContext.DependencyInfo(
"spring-boot-starter-web", "auto", "compile"));
}
if (pomContent.contains("spring-boot-starter-data-jpa")) {
dependencies.add(new ProjectContext.DependencyInfo(
"spring-boot-starter-data-jpa", "auto", "compile"));
}
if (pomContent.contains("spring-boot-starter-test")) {
dependencies.add(new ProjectContext.DependencyInfo(
"spring-boot-starter-test", "auto", "test"));
}
// 可以添加更多依赖检测逻辑
} catch (IOException e) {
logger.warn("Error reading pom.xml", e);
}
return dependencies;
}
/**
* 分析NPM依赖
*/
private List<ProjectContext.DependencyInfo> analyzeNpmDependencies(Path projectRoot) {
List<ProjectContext.DependencyInfo> dependencies = new ArrayList<>();
Path packageJsonPath = projectRoot.resolve("package.json");
if (!Files.exists(packageJsonPath)) {
return dependencies;
}
try {
String content = Files.readString(packageJsonPath);
JsonNode packageJson = objectMapper.readTree(content);
// 分析生产依赖
JsonNode deps = packageJson.get("dependencies");
if (deps != null) {
deps.fields().forEachRemaining(entry -> {
dependencies.add(new ProjectContext.DependencyInfo(
entry.getKey(), entry.getValue().asText(), "production"));
});
}
// 分析开发依赖
JsonNode devDeps = packageJson.get("devDependencies");
if (devDeps != null) {
devDeps.fields().forEachRemaining(entry -> {
ProjectContext.DependencyInfo depInfo = new ProjectContext.DependencyInfo(
entry.getKey(), entry.getValue().asText(), "development");
depInfo.setDirectDependency(true);
dependencies.add(depInfo);
});
}
} catch (IOException e) {
logger.warn("Error reading package.json", e);
}
return dependencies;
}
/**
* 分析Python依赖
*/
private List<ProjectContext.DependencyInfo> analyzePythonDependencies(Path projectRoot) {
List<ProjectContext.DependencyInfo> dependencies = new ArrayList<>();
Path requirementsFile = projectRoot.resolve("requirements.txt");
if (Files.exists(requirementsFile)) {
try {
List<String> lines = Files.readAllLines(requirementsFile);
for (String line : lines) {
line = line.trim();
if (!line.isEmpty() && !line.startsWith("#")) {
String[] parts = line.split("==|>=|<=|>|<");
String name = parts[0].trim();
String version = parts.length > 1 ? parts[1].trim() : "latest";
dependencies.add(new ProjectContext.DependencyInfo(name, version, "runtime"));
}
}
} catch (IOException e) {
logger.warn("Error reading requirements.txt", e);
}
}
return dependencies;
}
/**
* 查找配置文件
*/
public List<ProjectContext.ConfigFile> findConfigurationFiles(Path projectRoot) {
logger.debug("Finding configuration files for: {}", projectRoot);
List<ProjectContext.ConfigFile> configFiles = new ArrayList<>();
ProjectType projectType = projectTypeDetector.detectProjectType(projectRoot);
try {
// 通用配置文件
addConfigFileIfExists(configFiles, projectRoot, "application.properties", "properties");
addConfigFileIfExists(configFiles, projectRoot, "application.yml", "yaml");
addConfigFileIfExists(configFiles, projectRoot, "application.yaml", "yaml");
addConfigFileIfExists(configFiles, projectRoot, "config.json", "json");
// 项目类型特定的配置文件
switch (projectType) {
case JAVA_MAVEN:
case SPRING_BOOT:
addConfigFileIfExists(configFiles, projectRoot, "pom.xml", "xml");
break;
case NODE_JS:
case REACT:
case VUE:
case ANGULAR:
case NEXT_JS:
addConfigFileIfExists(configFiles, projectRoot, "package.json", "json");
addConfigFileIfExists(configFiles, projectRoot, "webpack.config.js", "javascript");
break;
case PYTHON:
case DJANGO:
case FLASK:
case FASTAPI:
addConfigFileIfExists(configFiles, projectRoot, "requirements.txt", "text");
addConfigFileIfExists(configFiles, projectRoot, "setup.py", "python");
break;
}
} catch (Exception e) {
logger.error("Error finding configuration files for: " + projectRoot, e);
}
logger.info("Found {} configuration files for project: {}", configFiles.size(), projectRoot);
return configFiles;
}
/**
* 添加配置文件(如果存在)
*/
private void addConfigFileIfExists(List<ProjectContext.ConfigFile> configFiles,
Path projectRoot, String fileName, String fileType) {
Path configPath = projectRoot.resolve(fileName);
if (Files.exists(configPath)) {
String relativePath = projectRoot.relativize(configPath).toString();
ProjectContext.ConfigFile configFile =
new ProjectContext.ConfigFile(fileName, relativePath, fileType);
// 标记主要配置文件
if (fileName.equals("pom.xml") || fileName.equals("package.json") ||
fileName.startsWith("application.")) {
configFile.setMainConfig(true);
}
configFiles.add(configFile);
}
}
/**
* 检查是否应该忽略目录
*/
private boolean shouldIgnoreDirectory(String dirName) {
return dirName.equals(".git") || dirName.equals(".svn") ||
dirName.equals("node_modules") || dirName.equals("target") ||
dirName.equals("build") || dirName.equals("dist") ||
dirName.equals("__pycache__") || dirName.equals(".idea") ||
dirName.equals(".vscode") || dirName.startsWith(".");
}
/**
* 获取文件扩展名
*/
private String getFileExtension(String fileName) {
int lastDot = fileName.lastIndexOf('.');
return lastDot > 0 ? fileName.substring(lastDot) : "";
}
/**
* 检查是否为关键文件
*/
private boolean isKeyFile(String fileName, ProjectType projectType) {
// 通用关键文件
if (fileName.equals("README.md") || fileName.equals("LICENSE") ||
fileName.equals("Dockerfile") || fileName.equals(".gitignore")) {
return true;
}
// 项目类型特定的关键文件
if (projectType != null) {
String keyFile = projectType.getKeyFile();
if (keyFile != null && !keyFile.isEmpty()) {
return fileName.equals(keyFile) || fileName.matches(keyFile);
}
}
return false;
}
}

View File

@@ -1,453 +0,0 @@
package com.example.demo.service;
import com.example.demo.model.ProjectType;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* 项目模板扩展服务
* 生成README、gitignore等通用文件模板
*/
@Service
public class ProjectTemplateExtensions {
/**
* 生成README.md内容
*/
public String generateReadmeContent(Map<String, String> variables) {
return String.format("""
# %s
%s
## 🚀 Getting Started
### Prerequisites
- Java 17 or higher (for Java projects)
- Node.js 16+ (for JavaScript projects)
- Python 3.8+ (for Python projects)
### Installation
1. Clone the repository:
```bash
git clone <repository-url>
cd %s
```
2. Install dependencies:
```bash
# For Java Maven projects
mvn clean install
# For Node.js projects
npm install
# For Python projects
pip install -r requirements.txt
```
3. Run the application:
```bash
# For Java Maven projects
mvn spring-boot:run
# For Node.js projects
npm start
# For Python projects
python main.py
```
## 📁 Project Structure
```
%s/
├── src/ # Source code
├── test/ # Test files
├── docs/ # Documentation
├── README.md # This file
└── ...
```
## 🛠️ Development
### Running Tests
```bash
# For Java projects
mvn test
# For Node.js projects
npm test
# For Python projects
python -m pytest
```
### Building
```bash
# For Java projects
mvn clean package
# For Node.js projects
npm run build
# For Python projects
python setup.py build
```
## 📝 Features
- Feature 1: Description
- Feature 2: Description
- Feature 3: Description
## 🤝 Contributing
1. Fork the project
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 👥 Authors
- **%s** - *Initial work* - [%s](mailto:%s)
## 🙏 Acknowledgments
- Hat tip to anyone whose code was used
- Inspiration
- etc
---
Created with ❤️ by %s
""",
variables.get("PROJECT_NAME_PASCAL"),
variables.get("DESCRIPTION"),
variables.get("PROJECT_NAME"),
variables.get("PROJECT_NAME"),
variables.get("AUTHOR"),
variables.get("AUTHOR"),
variables.get("EMAIL"),
variables.get("AUTHOR")
);
}
/**
* 生成.gitignore内容
*/
public String generateGitignoreContent(ProjectType projectType) {
StringBuilder gitignore = new StringBuilder();
// 通用忽略规则
gitignore.append("""
# General
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# Dependency directories
node_modules/
""");
// 项目类型特定的忽略规则
switch (projectType) {
case JAVA_MAVEN:
case JAVA_GRADLE:
case SPRING_BOOT:
gitignore.append("""
# Java
*.class
*.jar
*.war
*.ear
*.nar
hs_err_pid*
# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
# Gradle
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
# Spring Boot
spring-boot-*.log
""");
break;
case NODE_JS:
case REACT:
case VUE:
case ANGULAR:
case NEXT_JS:
gitignore.append("""
# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage
.grunt
# Bower dependency directory
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.local
.env.development.local
.env.test.local
.env.production.local
# parcel-bundler cache
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
public
# Storybook build outputs
.out
.storybook-out
# Temporary folders
tmp/
temp/
""");
break;
case PYTHON:
case DJANGO:
case FLASK:
case FASTAPI:
gitignore.append("""
# Python
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
""");
break;
default:
// 基本忽略规则已经添加
break;
}
return gitignore.toString();
}
}

View File

@@ -1,285 +0,0 @@
package com.example.demo.service;
import com.example.demo.model.ProjectType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
/**
* 项目类型检测器
* 基于文件特征自动识别项目类型
*/
@Component
public class ProjectTypeDetector {
private static final Logger logger = LoggerFactory.getLogger(ProjectTypeDetector.class);
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 检测项目类型
*
* @param projectRoot 项目根目录
* @return 检测到的项目类型
*/
public ProjectType detectProjectType(Path projectRoot) {
if (!Files.exists(projectRoot) || !Files.isDirectory(projectRoot)) {
logger.warn("Project root does not exist or is not a directory: {}", projectRoot);
return ProjectType.UNKNOWN;
}
try {
logger.debug("Detecting project type for: {}", projectRoot);
// 按优先级检测项目类型
ProjectType detectedType = detectByKeyFiles(projectRoot);
if (detectedType != ProjectType.UNKNOWN) {
logger.info("Detected project type: {} for {}", detectedType, projectRoot);
return detectedType;
}
// 如果关键文件检测失败,尝试基于目录结构检测
detectedType = detectByDirectoryStructure(projectRoot);
if (detectedType != ProjectType.UNKNOWN) {
logger.info("Detected project type by structure: {} for {}", detectedType, projectRoot);
return detectedType;
}
logger.info("Could not determine project type for: {}", projectRoot);
return ProjectType.UNKNOWN;
} catch (Exception e) {
logger.error("Error detecting project type for: " + projectRoot, e);
return ProjectType.UNKNOWN;
}
}
/**
* 基于关键文件检测项目类型
*/
private ProjectType detectByKeyFiles(Path projectRoot) throws IOException {
// Java Maven项目
if (Files.exists(projectRoot.resolve("pom.xml"))) {
// 检查是否为Spring Boot项目
if (isSpringBootProject(projectRoot)) {
return ProjectType.SPRING_BOOT;
}
return ProjectType.JAVA_MAVEN;
}
// Java Gradle项目
if (Files.exists(projectRoot.resolve("build.gradle")) ||
Files.exists(projectRoot.resolve("build.gradle.kts"))) {
return ProjectType.JAVA_GRADLE;
}
// Node.js项目
if (Files.exists(projectRoot.resolve("package.json"))) {
return analyzeNodeJsProject(projectRoot);
}
// Python项目
if (Files.exists(projectRoot.resolve("requirements.txt")) ||
Files.exists(projectRoot.resolve("setup.py")) ||
Files.exists(projectRoot.resolve("pyproject.toml"))) {
return analyzePythonProject(projectRoot);
}
// .NET项目
try (Stream<Path> files = Files.list(projectRoot)) {
if (files.anyMatch(path -> path.toString().endsWith(".csproj") ||
path.toString().endsWith(".sln"))) {
return ProjectType.DOTNET;
}
}
// Go项目
if (Files.exists(projectRoot.resolve("go.mod"))) {
return ProjectType.GO;
}
// Rust项目
if (Files.exists(projectRoot.resolve("Cargo.toml"))) {
return ProjectType.RUST;
}
// PHP项目
if (Files.exists(projectRoot.resolve("composer.json"))) {
return ProjectType.PHP;
}
// 静态HTML项目
if (Files.exists(projectRoot.resolve("index.html"))) {
return ProjectType.HTML_STATIC;
}
return ProjectType.UNKNOWN;
}
/**
* 检查是否为Spring Boot项目
*/
private boolean isSpringBootProject(Path projectRoot) {
try {
Path pomFile = projectRoot.resolve("pom.xml");
if (!Files.exists(pomFile)) {
return false;
}
String pomContent = Files.readString(pomFile);
return pomContent.contains("spring-boot-starter") ||
pomContent.contains("org.springframework.boot");
} catch (IOException e) {
logger.warn("Error reading pom.xml for Spring Boot detection", e);
return false;
}
}
/**
* 分析Node.js项目类型
*/
private ProjectType analyzeNodeJsProject(Path projectRoot) {
try {
Path packageJsonPath = projectRoot.resolve("package.json");
String content = Files.readString(packageJsonPath);
JsonNode packageJson = objectMapper.readTree(content);
// 检查依赖来确定具体的框架类型
JsonNode dependencies = packageJson.get("dependencies");
JsonNode devDependencies = packageJson.get("devDependencies");
if (hasDependency(dependencies, "react") || hasDependency(devDependencies, "react")) {
return ProjectType.REACT;
}
if (hasDependency(dependencies, "vue") || hasDependency(devDependencies, "vue")) {
return ProjectType.VUE;
}
if (hasDependency(dependencies, "@angular/core") ||
hasDependency(devDependencies, "@angular/cli")) {
return ProjectType.ANGULAR;
}
if (hasDependency(dependencies, "next") || hasDependency(devDependencies, "next")) {
return ProjectType.NEXT_JS;
}
return ProjectType.NODE_JS;
} catch (IOException e) {
logger.warn("Error analyzing package.json", e);
return ProjectType.NODE_JS;
}
}
/**
* 分析Python项目类型
*/
private ProjectType analyzePythonProject(Path projectRoot) {
// 检查Django项目
if (Files.exists(projectRoot.resolve("manage.py"))) {
return ProjectType.DJANGO;
}
// 检查Flask项目
if (Files.exists(projectRoot.resolve("app.py")) ||
Files.exists(projectRoot.resolve("application.py"))) {
return ProjectType.FLASK;
}
// 检查FastAPI项目
if (Files.exists(projectRoot.resolve("main.py"))) {
try {
String content = Files.readString(projectRoot.resolve("main.py"));
if (content.contains("from fastapi import") || content.contains("import fastapi")) {
return ProjectType.FASTAPI;
}
} catch (IOException e) {
logger.warn("Error reading main.py for FastAPI detection", e);
}
}
return ProjectType.PYTHON;
}
/**
* 基于目录结构检测项目类型
*/
private ProjectType detectByDirectoryStructure(Path projectRoot) {
try {
List<String> directories = Files.list(projectRoot)
.filter(Files::isDirectory)
.map(path -> path.getFileName().toString().toLowerCase())
.toList();
// Java项目特征目录
if (directories.contains("src") &&
(directories.contains("target") || directories.contains("build"))) {
return ProjectType.JAVA_MAVEN; // 默认为Maven
}
// Node.js项目特征目录
if (directories.contains("node_modules") ||
directories.contains("public") ||
directories.contains("dist")) {
return ProjectType.NODE_JS;
}
// Python项目特征目录
if (directories.contains("venv") ||
directories.contains("env") ||
directories.contains("__pycache__")) {
return ProjectType.PYTHON;
}
} catch (IOException e) {
logger.warn("Error analyzing directory structure", e);
}
return ProjectType.UNKNOWN;
}
/**
* 检查是否存在特定依赖
*/
private boolean hasDependency(JsonNode dependencies, String dependencyName) {
return dependencies != null && dependencies.has(dependencyName);
}
/**
* 获取项目类型的详细信息
*/
public String getProjectTypeDetails(Path projectRoot, ProjectType projectType) {
StringBuilder details = new StringBuilder();
details.append("Project Type: ").append(projectType.getDisplayName()).append("\n");
details.append("Primary Language: ").append(projectType.getPrimaryLanguage()).append("\n");
details.append("Package Manager: ").append(projectType.getPackageManager()).append("\n");
// 添加特定项目类型的详细信息
switch (projectType) {
case SPRING_BOOT:
details.append("Framework: Spring Boot\n");
details.append("Build Tool: Maven\n");
break;
case REACT:
details.append("Framework: React\n");
details.append("Runtime: Node.js\n");
break;
case DJANGO:
details.append("Framework: Django\n");
details.append("Language: Python\n");
break;
// 可以添加更多项目类型的详细信息
}
return details.toString();
}
}

View File

@@ -1,144 +0,0 @@
package com.example.demo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
public class TaskSummaryService {
private static final Logger logger = LoggerFactory.getLogger(TaskSummaryService.class);
private static final Pattern[] ACTION_PATTERNS = {
Pattern.compile("(?i)creating?\\s+(?:a\\s+)?(?:new\\s+)?(.{1,50}?)(?:\\s+file|\\s+directory|\\s+project)?", Pattern.CASE_INSENSITIVE),
Pattern.compile("(?i)writing?\\s+(?:to\\s+)?(.{1,50}?)(?:\\s+file)?", Pattern.CASE_INSENSITIVE),
Pattern.compile("(?i)reading?\\s+(?:from\\s+)?(.{1,50}?)(?:\\s+file)?", Pattern.CASE_INSENSITIVE),
Pattern.compile("(?i)editing?\\s+(.{1,50}?)(?:\\s+file)?", Pattern.CASE_INSENSITIVE),
Pattern.compile("(?i)listing?\\s+(?:the\\s+)?(.{1,50}?)(?:\\s+directory)?", Pattern.CASE_INSENSITIVE),
Pattern.compile("(?i)analyzing?\\s+(.{1,50}?)", Pattern.CASE_INSENSITIVE),
Pattern.compile("(?i)generating?\\s+(.{1,50}?)", Pattern.CASE_INSENSITIVE),
Pattern.compile("(?i)building?\\s+(.{1,50}?)", Pattern.CASE_INSENSITIVE)
};
private static final String[] ACTION_VERBS = {
"创建", "写入", "读取", "编辑", "列出", "分析", "生成", "构建",
"creating", "writing", "reading", "editing", "listing", "analyzing", "generating", "building"
};
/**
* 从AI响应中提取任务摘要
*/
public String extractTaskSummary(String aiResponse) {
if (aiResponse == null || aiResponse.trim().isEmpty()) {
return "处理中...";
}
// 清理响应文本
String cleanResponse = aiResponse.replaceAll("```[\\s\\S]*?```", "").trim();
// 尝试匹配具体操作
for (Pattern pattern : ACTION_PATTERNS) {
Matcher matcher = pattern.matcher(cleanResponse);
if (matcher.find()) {
String action = matcher.group(0).trim();
if (action.length() > 50) {
action = action.substring(0, 47) + "...";
}
return action;
}
}
// 查找动作词汇
String lowerResponse = cleanResponse.toLowerCase();
for (String verb : ACTION_VERBS) {
if (lowerResponse.contains(verb.toLowerCase())) {
// 提取包含动作词的句子
String[] sentences = cleanResponse.split("[.!?\\n]");
for (String sentence : sentences) {
if (sentence.toLowerCase().contains(verb.toLowerCase())) {
String summary = sentence.trim();
if (summary.length() > 60) {
summary = summary.substring(0, 57) + "...";
}
return summary;
}
}
}
}
// 如果没有找到具体操作,返回通用描述
if (cleanResponse.length() > 60) {
return cleanResponse.substring(0, 57) + "...";
}
return cleanResponse.isEmpty() ? "处理中..." : cleanResponse;
}
/**
* 估算任务复杂度和预期轮数
*/
public int estimateTaskComplexity(String initialMessage) {
if (initialMessage == null) return 1;
String lowerMessage = initialMessage.toLowerCase();
int complexity = 1;
// 基于关键词估算复杂度
if (lowerMessage.contains("project") || lowerMessage.contains("项目")) complexity += 3;
if (lowerMessage.contains("complete") || lowerMessage.contains("完整")) complexity += 2;
if (lowerMessage.contains("multiple") || lowerMessage.contains("多个")) complexity += 2;
if (lowerMessage.contains("full-stack") || lowerMessage.contains("全栈")) complexity += 4;
if (lowerMessage.contains("website") || lowerMessage.contains("网站")) complexity += 2;
if (lowerMessage.contains("api") || lowerMessage.contains("接口")) complexity += 2;
// 基于文件操作数量估算
long fileOperations = lowerMessage.chars()
.mapToObj(c -> String.valueOf((char) c))
.filter(s -> s.matches(".*(?:create|write|edit|file|directory).*"))
.count();
complexity += (int) Math.min(fileOperations / 2, 5);
return Math.min(complexity, 15); // 最大15轮
}
/**
* 生成当前状态的用户友好描述
*/
public String generateStatusDescription(String status, String currentAction, int currentTurn, int totalTurns) {
StringBuilder desc = new StringBuilder();
switch (status) {
case "RUNNING":
if (currentAction != null && !currentAction.trim().isEmpty()) {
desc.append("🔄 ").append(currentAction);
} else {
desc.append("🤔 AI正在思考...");
}
if (totalTurns > 1) {
desc.append(String.format(" (第%d/%d轮)", currentTurn, totalTurns));
}
break;
case "COMPLETED":
desc.append("✅ 任务完成");
if (totalTurns > 1) {
desc.append(String.format(" (共%d轮)", currentTurn));
}
break;
case "ERROR":
desc.append("❌ 执行出错");
break;
default:
desc.append("⏳ 处理中...");
}
return desc.toString();
}
}

View File

@@ -1,188 +0,0 @@
package com.example.demo.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* 工具执行日志记录服务
* 记录所有工具调用的详细信息,使用中文日志
*/
@Service
public class ToolExecutionLogger {
private static final Logger logger = LoggerFactory.getLogger(ToolExecutionLogger.class);
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 工具调用计数器
private final AtomicLong callCounter = new AtomicLong(0);
// 工具执行统计
private final Map<String, ToolStats> toolStats = new ConcurrentHashMap<>();
/**
* 记录工具调用开始
*/
public long logToolStart(String toolName, String description, Object parameters) {
long callId = callCounter.incrementAndGet();
String timestamp = LocalDateTime.now().format(formatter);
logger.info("🚀 [工具调用-{}] 开始执行工具: {}", callId, toolName);
logger.info("📝 [工具调用-{}] 工具描述: {}", callId, description);
logger.info("⚙️ [工具调用-{}] 调用参数: {}", callId, formatParameters(parameters));
logger.info("🕐 [工具调用-{}] 开始时间: {}", callId, timestamp);
// 更新统计信息
toolStats.computeIfAbsent(toolName, k -> new ToolStats()).incrementCalls();
return callId;
}
/**
* 记录工具调用成功
*/
public void logToolSuccess(long callId, String toolName, String result, long executionTimeMs) {
String timestamp = LocalDateTime.now().format(formatter);
logger.info("✅ [工具调用-{}] 工具执行成功: {}", callId, toolName);
logger.info("📊 [工具调用-{}] 执行结果: {}", callId, truncateResult(result));
logger.info("⏱️ [工具调用-{}] 执行耗时: {}ms", callId, executionTimeMs);
logger.info("🕐 [工具调用-{}] 完成时间: {}", callId, timestamp);
// 更新统计信息
ToolStats stats = toolStats.get(toolName);
if (stats != null) {
stats.incrementSuccess();
stats.addExecutionTime(executionTimeMs);
}
}
/**
* 记录工具调用失败
*/
public void logToolError(long callId, String toolName, String error, long executionTimeMs) {
String timestamp = LocalDateTime.now().format(formatter);
logger.error("❌ [工具调用-{}] 工具执行失败: {}", callId, toolName);
logger.error("🚨 [工具调用-{}] 错误信息: {}", callId, error);
logger.error("⏱️ [工具调用-{}] 执行耗时: {}ms", callId, executionTimeMs);
logger.error("🕐 [工具调用-{}] 失败时间: {}", callId, timestamp);
// 更新统计信息
ToolStats stats = toolStats.get(toolName);
if (stats != null) {
stats.incrementError();
stats.addExecutionTime(executionTimeMs);
}
}
/**
* 记录工具调用的详细步骤
*/
public void logToolStep(long callId, String toolName, String step, String details) {
logger.debug("🔄 [工具调用-{}] [{}] 执行步骤: {} - {}", callId, toolName, step, details);
}
/**
* 记录文件操作
*/
public void logFileOperation(long callId, String operation, String filePath, String details) {
logger.info("📁 [工具调用-{}] 文件操作: {} - 文件: {} - 详情: {}", callId, operation, filePath, details);
}
/**
* 记录项目分析
*/
public void logProjectAnalysis(long callId, String projectPath, String projectType, String details) {
logger.info("🔍 [工具调用-{}] 项目分析: 路径={}, 类型={}, 详情={}", callId, projectPath, projectType, details);
}
/**
* 记录项目创建
*/
public void logProjectCreation(long callId, String projectName, String projectType, String projectPath) {
logger.info("🏗️ [工具调用-{}] 项目创建: 名称={}, 类型={}, 路径={}", callId, projectName, projectType, projectPath);
}
/**
* 获取工具执行统计
*/
public void logToolStatistics() {
logger.info("📈 ========== 工具执行统计 ==========");
toolStats.forEach((toolName, stats) -> {
logger.info("🔧 工具: {} | 调用次数: {} | 成功: {} | 失败: {} | 平均耗时: {}ms",
toolName, stats.getTotalCalls(), stats.getSuccessCount(),
stats.getErrorCount(), stats.getAverageExecutionTime());
});
logger.info("📈 ================================");
}
/**
* 格式化参数显示
*/
private String formatParameters(Object parameters) {
if (parameters == null) {
return "无参数";
}
String paramStr = parameters.toString();
return paramStr.length() > 200 ? paramStr.substring(0, 200) + "..." : paramStr;
}
/**
* 截断结果显示
*/
private String truncateResult(String result) {
if (result == null) {
return "无结果";
}
return result.length() > 300 ? result.substring(0, 300) + "..." : result;
}
/**
* 工具统计信息内部类
*/
private static class ToolStats {
private long totalCalls = 0;
private long successCount = 0;
private long errorCount = 0;
private long totalExecutionTime = 0;
public void incrementCalls() {
totalCalls++;
}
public void incrementSuccess() {
successCount++;
}
public void incrementError() {
errorCount++;
}
public void addExecutionTime(long time) {
totalExecutionTime += time;
}
public long getTotalCalls() {
return totalCalls;
}
public long getSuccessCount() {
return successCount;
}
public long getErrorCount() {
return errorCount;
}
public long getAverageExecutionTime() {
return totalCalls > 0 ? totalExecutionTime / totalCalls : 0;
}
}
}

View File

@@ -1,93 +0,0 @@
package com.example.demo.service;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* 工具日志事件类
* 继承自LogEvent添加工具相关的字段
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ToolLogEvent extends LogEvent {
private String toolName;
private String filePath;
private String icon;
private String status; // RUNNING, SUCCESS, ERROR
private Long executionTime; // 执行时间(毫秒)
private String summary; // 操作摘要
// Constructors
public ToolLogEvent() {
super();
}
public ToolLogEvent(String type, String taskId, String toolName, String filePath,
String message, String timestamp, String icon, String status) {
super(type, taskId, message, timestamp);
this.toolName = toolName;
this.filePath = filePath;
this.icon = icon;
this.status = status;
}
// Getters and Setters
public String getToolName() {
return toolName;
}
public void setToolName(String toolName) {
this.toolName = toolName;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Long getExecutionTime() {
return executionTime;
}
public void setExecutionTime(Long executionTime) {
this.executionTime = executionTime;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
@Override
public String toString() {
return "ToolLogEvent{" +
"toolName='" + toolName + '\'' +
", filePath='" + filePath + '\'' +
", icon='" + icon + '\'' +
", status='" + status + '\'' +
", executionTime=" + executionTime +
", summary='" + summary + '\'' +
"} " + super.toString();
}
}

View File

@@ -1,106 +0,0 @@
package com.example.demo.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.awt.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* 浏览器工具类
* 用于跨平台打开默认浏览器
*/
public class BrowserUtil {
private static final Logger logger = LoggerFactory.getLogger(BrowserUtil.class);
/**
* 打开默认浏览器访问指定URL
*
* @param url 要访问的URL
* @return 是否成功打开
*/
public static boolean openBrowser(String url) {
if (url == null || url.trim().isEmpty()) {
logger.warn("URL is null or empty, cannot open browser");
return false;
}
try {
// 方法1: 使用Desktop API (推荐)
if (Desktop.isDesktopSupported()) {
Desktop desktop = Desktop.getDesktop();
if (desktop.isSupported(Desktop.Action.BROWSE)) {
desktop.browse(new URI(url));
logger.info("Successfully opened browser with URL: {}", url);
return true;
}
}
// 方法2: 使用系统命令 (备用方案)
return openBrowserWithCommand(url);
} catch (IOException | URISyntaxException e) {
logger.error("Failed to open browser with Desktop API for URL: {}", url, e);
// 尝试备用方案
return openBrowserWithCommand(url);
}
}
/**
* 使用系统命令打开浏览器 (备用方案)
*
* @param url 要访问的URL
* @return 是否成功打开
*/
private static boolean openBrowserWithCommand(String url) {
try {
String os = System.getProperty("os.name").toLowerCase();
ProcessBuilder processBuilder;
if (os.contains("win")) {
// Windows
processBuilder = new ProcessBuilder("rundll32", "url.dll,FileProtocolHandler", url);
} else if (os.contains("mac")) {
// macOS
processBuilder = new ProcessBuilder("open", url);
} else {
// Linux/Unix
processBuilder = new ProcessBuilder("xdg-open", url);
}
Process process = processBuilder.start();
// 等待一小段时间确保命令执行
Thread.sleep(500);
logger.info("Successfully opened browser using system command for URL: {}", url);
return true;
} catch (IOException | InterruptedException e) {
logger.error("Failed to open browser using system command for URL: {}", url, e);
return false;
}
}
/**
* 检查URL是否有效
*
* @param url 要检查的URL
* @return 是否有效
*/
public static boolean isValidUrl(String url) {
if (url == null || url.trim().isEmpty()) {
return false;
}
try {
new URI(url);
return url.startsWith("http://") || url.startsWith("https://");
} catch (URISyntaxException e) {
return false;
}
}
}

View File

@@ -1,58 +0,0 @@
package com.example.demo.utils;
import java.nio.file.Paths;
/**
* 跨平台路径处理工具类
*/
public class PathUtils {
/**
* 构建跨平台兼容的绝对路径
*
* @param basePath 基础路径
* @param relativePath 相对路径部分
* @return 跨平台兼容的绝对路径
*/
public static String buildPath(String basePath, String... relativePath) {
return Paths.get(basePath, relativePath).toString();
}
/**
* 规范化路径,确保跨平台兼容
*
* @param path 原始路径
* @return 规范化后的路径
*/
public static String normalizePath(String path) {
return Paths.get(path).normalize().toString();
}
/**
* 检查路径是否为绝对路径
*
* @param path 要检查的路径
* @return 是否为绝对路径
*/
public static boolean isAbsolute(String path) {
return Paths.get(path).isAbsolute();
}
/**
* 获取当前工作目录
*
* @return 当前工作目录的绝对路径
*/
public static String getCurrentWorkingDirectory() {
return System.getProperty("user.dir");
}
/**
* 构建工作空间路径
*
* @return 工作空间的绝对路径
*/
public static String buildWorkspacePath() {
return buildPath(getCurrentWorkingDirectory(), "workspace");
}
}

View File

@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,31 +0,0 @@
# ai-tutor-skill
A Claude skill for explaining complex AI and ML concepts in accessible, plain English. This skill transforms abstract technical ideas into clear explanations using structured narrative frameworks, making it ideal for teaching and learning technical topics.
Resources:
- [YouTube Explainer](https://youtu.be/vEvytl7wrGM)
## Requirements
- **Python**: >= 3.12
- **Package Manager**: [uv](https://github.com/astral-sh/uv)
- **Dependencies**: youtube-transcript-api (installed automatically)
## Setup
1. Clone the repository:
```bash
git clone <repository-url>
cd ai-tutor-skill
```
2. Install dependencies:
```bash
uv sync
```
## Important Note: YouTube Transcript Limitations
> **The YouTube transcript functionality only works when Claude Code is running locally.**
>
> YouTube blocks requests from Claude's servers, so transcript extraction will fail when using Claude Code in cloud/remote mode. To use this feature, ensure you're running Claude Code on your local machine.

View File

@@ -1,131 +0,0 @@
---
name: ai-tutor
description: Use when user asks to explain, break down, or help understand technical concepts (AI, ML, or other technical topics). Makes complex ideas accessible through plain English and narrative structure. Use the provided scripts to transcribe videos
---
# AI Tutor
Transform complex technical concepts into clear, accessible explanations using narrative storytelling frameworks.
## Before Responding: Think Hard
Before crafting your explanation:
1. **Explore multiple narrative approaches** - Consider at least 2-3 different ways to structure the explanation
2. **Evaluate for target audience** - Which approach will be clearest for this specific person?
3. **Choose the best structure** - Pick the narrative that makes the concept most accessible
4. **Plan your examples** - Identify concrete, specific examples before writing
Take time to think through these options. A well-chosen structure is more valuable than a quick response.
**If concept is unfamiliar or requires research:** Load `research_methodology.md` for detailed guidance.
**If user provides YouTube video:** Call `uv run scripts/get_youtube_transcript.py <video_url_or_id>` for video's transcript.
## Core Teaching Framework
Use one of three narrative structures:
### Status Quo → Problem → Solution
1. **Status Quo**: Describe the existing situation or baseline approach
2. **Problem**: Explain what's broken, inefficient, or limiting
3. **Solution**: Show how the concept solves the problem
This is the primary go-to structure.
### What → Why → How
1. **What**: Define the concept in simple terms (what it is)
2. **Why**: Explain the motivation and importance (why it matters)
3. **How**: Break down the mechanics (how it works)
### What → So What → What Now
1. **What**: State the situation or finding
2. **So What**: Explain the implications or impact
3. **What Now**: Describe next steps or actions
Use for business contexts and practical applications.
## Teaching Principles
### Plain English First
Replace technical jargon with clear, direct explanations of the core concept.
**Example:**
- ❌ "The gradient descent algorithm optimizes the loss function via backpropagation"
- ✅ "Gradient descent is a way to find the model parameters that make the best predictions based on real-world data"
Plain English means explaining the concept directly without jargon—not just using analogies.
### Concrete Examples Ground Abstract Ideas
Always provide at least one concrete example with specific details, numbers, or real instances.
**Example:**
- Abstract: "Features are things we use to make predictions"
- Concrete: "For our customer churn model, features include age of account and number of logins in the past 90 days"
### Use Analogies Judiciously
Analogies map the unfamiliar to the familiar, but use them sparingly and strategically—not as the primary explanation method.
**When to use:**
- After explaining the concept in plain English
- When the technical concept has a strong parallel to everyday experience
- To create memorable mental models
Avoid over-relying on analogies. Start with direct, plain English explanations.
### Progressive Complexity
- Start with the intuition and big picture
- Add details layer by layer
- Use concrete examples before abstractions
- Build from familiar to unfamiliar
### Less is More
Attention and mental effort are finite. Be economical with your audience's cognitive resources.
- Cut unnecessary fluff
- Every word should earn its place
- Focus attention on key information
### Use Numbered Lists Strategically
Numbers help navigate information and make it more digestible (e.g., "3 ways to fine-tune", "System 1 and System 2").
### Know Thy Audience
Adjust technical depth, terminology, and focus based on who you're talking to.
**C-Suite / Business Leaders:**
- Use high-level terms (e.g., "AI")
- Focus on what and why, emphasize business impact
- Keep it high-level, skip implementation details
**BI Analysts / Technical Adjacent:**
- Use more specific terms (e.g., "LLM")
- Cover what and why with more technical context
- Discuss workflow relevance, include moderate technical details
**Data Scientists / Technical Peers:**
- Use precise terminology (e.g., "Llama 3 8B")
- Cover what, why, AND how
- Dive into technical details, discuss specific implementation
- Still emphasize business impact (everyone wants to know why)
**If audience level is unclear:** Assume the lowest level of understanding and explain accordingly. Don't ask the user to clarify—just start with fundamentals. You can always go deeper if they ask for more detail.
## Response Style
- Start with the big picture before diving into details
- Use conversational, friendly tone
- Offer to explain subsections in more depth
- Use bullet points sparingly—prefer flowing narrative prose
- Include concrete examples with specific details
- Connect concepts to real-world applications
- Be economical with words—every sentence should add value
## Workflow Summary
1. **Think hard**: Explore 2-3 narrative structures, choose the clearest for the audience
2. **Identify audience**: Assess knowledge level (if unclear, assume beginner level)
3. **Check if research needed**:
- Can you explain this with your existing knowledge? → Proceed to step 4
- Unfamiliar/cutting-edge topic? → Load `research_methodology.md` first
4. **Craft explanation**: Plain English first, no jargon
5. **Add concrete example**: Specific details, numbers, real instances
6. **Optional analogy**: Only if it adds value beyond direct explanation
7. **Offer to dive deeper**: Invite questions on specific aspects

View File

@@ -1,9 +0,0 @@
[project]
name = "ai-tutor"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"youtube-transcript-api>=1.2.3",
]

Some files were not shown because too many files have changed in this diff Show More