diff --git a/CLAUDE.md b/CLAUDE.md index 81e5d4c..333ad2b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,6 +27,10 @@ AI 智能客服系统,基于 Spring AI Alibaba + 通义千问 + PGVector,支 **前提条件**: PostgreSQL 12+ 需运行且安装 PGVector 扩展,数据库 `support_bot` 需存在。`knowledge_category` 和 `knowledge_document` 表由 `DatabaseInitConfig` 自动创建,无需手动建表。 +**测试说明**: 所有测试均为集成测试(`@SpringBootTest`),需要运行中的 PostgreSQL 和有效的 DashScope API Key。测试类:`SupportBotApplicationTests`(对话/RAG)、`PgVectorVectorStoreConfigTest`(向量存储)、`QueryTransformerTests`(查询重写策略)。无单元测试。 + +**访问地址**: 前端管理页面 `http://localhost:9090/index.html`,API 文档 `http://localhost:9090/doc.html`(Knife4j) + ## 核心架构决策 ### 主启动类排除了 PgVectorStoreAutoConfiguration @@ -54,13 +58,18 @@ AI 智能客服系统,基于 Spring AI Alibaba + 通义千问 + PGVector,支 ## 关键配置 - `application.yml` 含 API Key,已被 `.gitignore` 排除 +- 对话模型: `qwen-turbo`,temperature: 0.7;Embedding: `text-embedding-v2`(1536维) - MyBatis Plus 逻辑删除字段: `isDelete`,主键策略: `assign_id`(雪花算法) -- **雪花 ID 精度问题**: `KnowledgeDocument.id`、`categoryId` 和 `KnowledgeCategory.id`、`parentId` 已添加 `@JsonSerialize(using = ToStringSerializer.class)`,序列化为字符串避免前端 JS 精度丢失 +- **雪花 ID 精度问题**: `KnowledgeDocument.id`、`categoryId` 和 `KnowledgeCategory.id`、`parentId` 已添加 `@JsonSerialize(using = ToStringSerializer.class)`,序列化为字符串避免前端 JS 精度丢失。新增 Long ID 字段时务必加上此注解 - PostgreSQL JSONB 字段使用自定义 `PostgresJsonTypeHandler`(期望 JSON 对象 `'{}'`,非数组 `'[]'`) - 向量维度: 1536,距离类型: COSINE_DISTANCE,索引: HNSW -- **分块配置**: `knowledge.chunk.*` 配置项(`ChunkConfig`),默认 chunkSize=200, overlap=100 -- **上传校验**: `ALLOWED_EXTENSIONS` 白名单 + 50MB 大小限制,前后端双重校验 +- **分块配置**: `knowledge.chunk.*` 配置项(`ChunkConfig`),默认 chunkSize=200, overlap=100, minChunkSizeChars=10, maxNumChunks=5000, keepSeparator=true +- **上传校验**: `ALLOWED_EXTENSIONS` 白名单 + 50MB 大小限制(`spring.servlet.multipart` 配置),前后端双重校验 - **文档去重**: `KnowledgeDocument.contentHash` 字段(SHA-256),上传时自动计算并查重 +- **数据库自动初始化**: `DatabaseInitConfig` 在启动时检查并创建 `knowledge_category`/`knowledge_document` 表,对已存在的 `knowledge_document` 表会自动补加 `content_hash` 列。注意 `knowledge-base.sql` 脚本为早期版本,缺少此列,实际以 `DatabaseInitConfig` 为准 + +### 依赖版本注意 +Spring AI 相关依赖版本混合:`spring-ai-alibaba-starter` 1.0.0-M6.1、`spring-ai-pgvector-store` 1.0.0-M6、`spring-ai-tika-document-reader` 1.0.0(正式版),可能存在 API 不兼容风险。pom.xml 中注释掉了 `spring-ai-starter-vector-store-pgvector` 1.0.0-M7(自动整合版本,未启用)。 ## 前端架构 diff --git a/src/main/resources/static/components/DocUpload.js b/src/main/resources/static/components/DocUpload.js index 8af6c3d..a27f8ad 100644 --- a/src/main/resources/static/components/DocUpload.js +++ b/src/main/resources/static/components/DocUpload.js @@ -66,13 +66,13 @@ export default {
POST/upload/file(Tika 多格式解析)
-
+
📎

点击或拖拽上传,支持多文件(PDF / Word / Excel / PPT / TXT 等)

⚠️ {{ validationErrors.file }}
- +
@@ -95,7 +95,7 @@ export default {
⚠️ {{ validationErrors.markdown }}
- +
@@ -108,7 +108,7 @@ export default {
⚠️ {{ validationErrors.jsonBasic }}
- +
@@ -122,7 +122,7 @@ export default {
⚠️ {{ validationErrors.jsonFields }}
- +
@@ -136,7 +136,7 @@ export default {
⚠️ {{ validationErrors.jsonPointer }}
- +
@@ -154,6 +154,13 @@ export default { const overlapOverride = ref(null) const uploadProgress = ref(-1) + // 文件 input 的 ref 引用(Composition API 需要声明才能在模板中使用) + const fileInput = ref(null) + const mdInput = ref(null) + const jsonBInput = ref(null) + const jsonFInput = ref(null) + const jsonPInput = ref(null) + const fileData = reactive({ file: null, markdown: null, jsonBasic: null, jsonFields: null, jsonPointer: null }) @@ -170,6 +177,14 @@ export default { file: '', markdown: '', jsonBasic: '', jsonFields: '', jsonPointer: '' }) + // 添加调试用的计算属性,检查按钮状态 + const getButtonState = (type) => { + const hasFile = !!fileData[type] + const hasError = !!validationErrors[type] + console.log(`[BUTTON STATE] type=${type}, hasFile=${hasFile}, hasError=${hasError}, disabled=${!hasFile || hasError}`) + return !hasFile || hasError + } + const subTabs = [ { key: 'file', icon: '📎', label: '通用文件' }, { key: 'string', icon: '📝', label: '文本内容' }, @@ -202,8 +217,12 @@ export default { if (!input.files || input.files.length === 0) return const files = Array.from(input.files) + console.log('[DEBUG] handleFileSelect - type:', type, 'files:', files.length) + // 前端校验 const error = validateFiles(files) + console.log('[DEBUG] validation error:', error) + if (error) { // 校验不通过:清空数据,保持按钮禁用 validationErrors[type] = error @@ -221,6 +240,10 @@ export default { ? `已选择 ${files.length} 个文件(共 ${formatBytes(totalSize)})` : `已选择:${files[0].name} (${formatBytes(files[0].size)})` fileInfo[type] = label + + console.log('[DEBUG] fileData[type] set to:', fileData[type]) + console.log('[DEBUG] validationErrors[type] set to:', validationErrors[type]) + console.log('[DEBUG] Button should be enabled:', !!fileData[type] && !validationErrors[type]) } function handleDrop(event, type) { @@ -374,7 +397,9 @@ export default { stringTitle, stringContent, jsonFieldsStr, jsonPointerStr, showAdvanced, chunkSizeOverride, overlapOverride, uploadProgress, + fileInput, mdInput, jsonBInput, jsonFInput, jsonPInput, fileData, fileInfo, results, validationErrors, store, + getButtonState, handleFileSelect, handleDrop, doUpload, formatBytes } }