feat(aftersales): 售后工单新增问题类型和严重程度字段
- 在AfterSaleTicketSaveReqVO中新增issueType字段用于标识问题类型
- 在AfterSaleTicketSaveReqVO中新增issueCategory字段用于问题分类
- 在AfterSaleTicketSaveReqVO中新增severityLevel字段用于标识严重程度
- 完善了问题类型的枚举定义:1-一般问题 2-维修问题 3-退货问题
- 完善了严重程度的枚举定义:0-轻微 1-一般 2-严重 3-致命
docs(ai): 新增AI知识库模块接口文档
- 创建了完整的AI知识库模块API文档
- 包含知识库、文档、分段三个核心功能的接口说明
- 整理了模型API的相关接口函数和使用位置
- 提供了详细的函数方法、路径和使用位置对照表
refactor(ai): 移除AI API密钥相关代码
- 删除了AiApiKeyController控制器类及其相关接口
- 移除了AiApiKeyDO数据对象和对应的数据库映射
- 删除了AiApiKeyService服务接口及其实现类
- 清理了API密钥相关的请求响应VO类
- 移除了AI自动配置中的相关组件注册
已修改67个文件
已添加53个文件
已删除174个文件
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # AI ç¥è¯åºæ¨¡å - æ¥å£ææ¡£ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | ``` |
| | | src/views/ai/knowledge/knowledge/index.vue # ç¥è¯åºå表 |
| | | src/views/ai/knowledge/knowledge/modules/form.vue # ç¥è¯åºè¡¨åå¼¹çª |
| | | src/views/ai/knowledge/knowledge/data.ts # ç¥è¯åºé
ç½® |
| | | src/views/ai/knowledge/knowledge/retrieval/index.vue # å¬åæµè¯ |
| | | src/views/ai/knowledge/document/index.vue # ææ¡£å表 |
| | | src/views/ai/knowledge/document/data.ts # ææ¡£é
ç½® |
| | | src/views/ai/knowledge/document/form/index.vue # ææ¡£å建/ç¼è¾ï¼æ¥éª¤é¡µï¼ |
| | | src/views/ai/knowledge/document/form/modules/upload-step.vue # ä¸ä¼ æ¥éª¤ |
| | | src/views/ai/knowledge/document/form/modules/split-step.vue # åæ®µæ¥éª¤ |
| | | src/views/ai/knowledge/document/form/modules/process-step.vue # å¤çæ¥éª¤ |
| | | src/views/ai/knowledge/segment/index.vue # åæ®µå表 |
| | | src/views/ai/knowledge/segment/data.ts # åæ®µé
ç½® |
| | | src/views/ai/knowledge/segment/modules/form.vue # åæ®µè¡¨åå¼¹çª |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## ç¥è¯åº APIï¼`src/api/ai/knowledge/knowledge/index.ts`ï¼ |
| | | |
| | | | 彿° | æ¹æ³ | è·¯å¾ | 使ç¨ä½ç½® | |
| | | |------|------|------|----------| |
| | | | `getKnowledgePage` | GET | `/ai/knowledge/page` | knowledge/index.vue | |
| | | | `getKnowledge` | GET | `/ai/knowledge/get?id={id}` | knowledge/form.vueãknowledge/retrieval/index.vue | |
| | | | `createKnowledge` | POST | `/ai/knowledge/create` | knowledge/form.vue | |
| | | | `updateKnowledge` | PUT | `/ai/knowledge/update` | knowledge/form.vue | |
| | | | `deleteKnowledge` | DELETE | `/ai/knowledge/delete?id={id}` | knowledge/index.vue | |
| | | |
| | | ## ææ¡£ APIï¼`src/api/ai/knowledge/document/index.ts`ï¼ |
| | | |
| | | | 彿° | æ¹æ³ | è·¯å¾ | 使ç¨ä½ç½® | |
| | | |------|------|------|----------| |
| | | | `getKnowledgeDocumentPage` | GET | `/ai/knowledge/document/page` | document/index.vue | |
| | | | `getKnowledgeDocument` | GET | `/ai/knowledge/document/get?id={id}` | document/form/index.vue | |
| | | | `createKnowledgeDocumentList` | POST | `/ai/knowledge/document/create-list` | document/form/modules/split-step.vue | |
| | | | `updateKnowledgeDocument` | PUT | `/ai/knowledge/document/update` | document/form/modules/split-step.vue | |
| | | | `updateKnowledgeDocumentStatus` | PUT | `/ai/knowledge/document/update-status` | document/index.vue | |
| | | | `deleteKnowledgeDocument` | DELETE | `/ai/knowledge/document/delete?id={id}` | document/index.vue | |
| | | | `processDocument` | POST | `/ai/knowledge/document/process/{id}` | document/index.vue | |
| | | |
| | | ## åæ®µ APIï¼`src/api/ai/knowledge/segment/index.ts`ï¼ |
| | | |
| | | | 彿° | æ¹æ³ | è·¯å¾ | 使ç¨ä½ç½® | |
| | | |------|------|------|----------| |
| | | | `getKnowledgeSegmentPage` | GET | `/ai/knowledge/segment/page` | segment/index.vue | |
| | | | `getKnowledgeSegment` | GET | `/ai/knowledge/segment/get?id={id}` | segment/modules/form.vue | |
| | | | `createKnowledgeSegment` | POST | `/ai/knowledge/segment/create` | segment/modules/form.vue | |
| | | | `updateKnowledgeSegment` | PUT | `/ai/knowledge/segment/update` | segment/modules/form.vue | |
| | | | `updateKnowledgeSegmentStatus` | PUT | `/ai/knowledge/segment/update-status` | segment/index.vue | |
| | | | `deleteKnowledgeSegment` | DELETE | `/ai/knowledge/segment/delete?id={id}` | segment/index.vue | |
| | | | `splitContent` | GET | `/ai/knowledge/segment/split` | document/form/modules/split-step.vue | |
| | | | `getKnowledgeSegmentProcessList` | GET | `/ai/knowledge/segment/get-process-list` | document/form/modules/process-step.vue | |
| | | | `searchKnowledgeSegment` | POST | `/ai/knowledge/segment/search` | knowledge/retrieval/index.vue | |
| | | |
| | | ## 模å APIï¼`src/api/ai/model/model/index.ts`ï¼ |
| | | |
| | | | 彿° | æ¹æ³ | è·¯å¾ | 使ç¨ä½ç½® | |
| | | |------|------|------|----------| |
| | | | `getModelSimpleList` | GET | `/ai/model/simple-list?type={type}` | knowledge/data.ts | |
| | | |
| | | --- |
| | | |
| | | > å
± 4 个 API æä»¶ï¼22 个æ¥å£å½æ° |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # AI ç¥è¯åº RAG åè½ - å端èè°æ¹æ¡ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - **API å¯é¥ç®¡ç**ï¼`ai/views/apikey/` â 管ç AI å¹³å° API å¯é¥ |
| | | - **模å管ç**ï¼`ai/views/model/` â 管ç AI 模åé
ç½®ï¼è天ãåéåçï¼ |
| | | - **ç¥è¯åºç®¡ç**ï¼`ai/views/knowledge/` â ç¥è¯åº CRUDãææ¡£ä¸ä¼ ãåæ®µç®¡çãRAG æ£ç´¢ |
| | | |
| | | ## ä¸å¡æµç¨ä¸æ°æ®å¸¦å
¥ |
| | | |
| | | ``` |
| | | æ¥1: é
ç½® API å¯é¥ï¼ä¸æ¬¡æ§ï¼ |
| | | ââ å¨ãAPI å¯é¥ç®¡çã页é
ç½®éä¹åé® API Key â è·å¾ apiKeyId |
| | | |
| | | æ¥2: é
ç½®åé忍¡åï¼ä¸æ¬¡æ§ï¼ |
| | | ââ å¨ã模å管çã页æ°å»º Embedding 模å â keyId 仿¥1带å
¥ |
| | | ââ æ¨¡åæ è¯å¡« "text-embedding-v3"ï¼å¹³å°é "TongYi"ï¼ç±»åé "EMBEDDING(5)" |
| | | ââ è·å¾ embeddingModelId |
| | | |
| | | æ¥3: å建ç¥è¯åº |
| | | ââ å¨ãç¥è¯åºç®¡çã页æ°å»ºç¥è¯åº â embeddingModelId 仿¥2带å
¥ |
| | | ââ å¯è®¾ç½® topK åç¸ä¼¼åº¦éå¼ |
| | | |
| | | æ¥4: åå»ºææ¡£ |
| | | ââ å¨ç¥è¯åºè¯¦æ
页ä¸ä¼ ææ¡£ï¼æä¾åç§°å URLï¼ |
| | | ââ å端èªå¨ä¸è½½ææ¡£ãæåææ¬å
容ï¼Tika è§£æï¼ |
| | | |
| | | æ¥5: å¤çææ¡£ï¼å段+åéåï¼ |
| | | ââ å¨ææ¡£å表ç¹å»ãå¤çãæé® |
| | | ââ åç«¯å°ææ¡£å
容åçãè°ç¨å鿍¡åçæ Embeddingãåå
¥ Milvus |
| | | |
| | | æ¥6: RAG æ£ç´¢ |
| | | ââ å¨ç¥è¯åºè¯¦æ
页è¾å
¥æ£ç´¢å
容 â å端åéæ£ç´¢ + ç¸ä¼¼åº¦è¿æ»¤ |
| | | ââ è¿åæå¹é
çåæ®µåç¸ä¼¼åº¦åæ° |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## API æ¥å£ |
| | | |
| | | ### 1. API å¯é¥ç®¡ç |
| | | |
| | | **åºç¡è·¯å¾**ï¼`/ai/api-key` |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | `/ai/api-key/create` | å建 API å¯é¥ | |
| | | | PUT | `/ai/api-key/update` | æ´æ° API å¯é¥ | |
| | | | DELETE | `/ai/api-key/delete` | å é¤ API å¯é¥ | |
| | | | GET | `/ai/api-key/get` | è·åå个 API å¯é¥ | |
| | | | GET | `/ai/api-key/page` | å页æ¥è¯¢ API å¯é¥ | |
| | | |
| | | **POST /create 请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | name | String | æ¯ | å¯é¥åç§°ï¼å¦"éä¹åé®" | |
| | | | apiKey | String | æ¯ | API å¯é¥å符串 | |
| | | | platform | String | æ¯ | å¹³å°ï¼`TongYi`ï¼éä¹åé®ï¼ | |
| | | | url | String | å¦ | API å°åï¼ä¸è¬çç©ºï¼ | |
| | | | status | Integer | å¦ | ç¶æï¼0=å¯ç¨ï¼1=ç¦ç¨ | |
| | | |
| | | **ååºï¼** `{ "code": 0, "data": 1 }` (è¿åå¯é¥ ID) |
| | | |
| | | --- |
| | | |
| | | ### 2. 模å管ç |
| | | |
| | | **åºç¡è·¯å¾**ï¼`/ai/model` |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | `/ai/model/create` | å建 AI 模å | |
| | | | PUT | `/ai/model/update` | æ´æ° AI 模å | |
| | | | DELETE | `/ai/model/delete` | å é¤ AI 模å | |
| | | | GET | `/ai/model/get` | è·åå个模å | |
| | | | GET | `/ai/model/page` | å页æ¥è¯¢æ¨¡å | |
| | | |
| | | **POST /create 请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | keyId | Long | æ¯ | API å¯é¥ IDï¼ä»ä¸ä¸æ¥å¸¦å
¥ï¼ | |
| | | | name | String | æ¯ | 模ååç§°ï¼å¦"éä¹åé®åéå" | |
| | | | model | String | æ¯ | æ¨¡åæ è¯ï¼åé忍¡åå¡« `text-embedding-v3` | |
| | | | platform | String | æ¯ | å¹³å°ï¼åºå® `TongYi` | |
| | | | type | Integer | æ¯ | 模åç±»åï¼1=CHATï¼5=EMBEDDING | |
| | | | sort | Integer | å¦ | æåº | |
| | | | status | Integer | å¦ | ç¶æï¼0=å¯ç¨ | |
| | | | temperature | Double | å¦ | 温度ï¼Embedding ç±»åä¸éè¦ï¼ | |
| | | | maxTokens | Integer | å¦ | æå¤§ Token | |
| | | | maxContexts | Integer | å¦ | æå¤§ä¸ä¸æ | |
| | | |
| | | **模åç±»åæä¸¾ï¼** |
| | | |
| | | | type å¼ | 说æ | |
| | | |---------|------| |
| | | | 1 | CHATï¼èå¤©ï¼ | |
| | | | 5 | EMBEDDINGï¼åéåï¼ | |
| | | |
| | | **平尿䏾ï¼** |
| | | |
| | | | platform | 说æ | |
| | | |----------|------| |
| | | | TongYi | éä¹åé® | |
| | | |
| | | --- |
| | | |
| | | ### 3. ç¥è¯åºç®¡ç |
| | | |
| | | **åºç¡è·¯å¾**ï¼`/ai/knowledge` |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | `/ai/knowledge/create` | å建ç¥è¯åº | |
| | | | PUT | `/ai/knowledge/update` | æ´æ°ç¥è¯åº | |
| | | | DELETE | `/ai/knowledge/delete` | å é¤ç¥è¯åº | |
| | | | GET | `/ai/knowledge/get` | è·åå个ç¥è¯åº | |
| | | | GET | `/ai/knowledge/page` | å页æ¥è¯¢ç¥è¯åº | |
| | | | POST | `/ai/knowledge/document/create` | åå»ºææ¡£ï¼æ¹éï¼ | |
| | | |
| | | **POST /create 请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | name | String | æ¯ | ç¥è¯åºåç§° | |
| | | | description | String | å¦ | ç¥è¯åºæè¿° | |
| | | | embeddingModelId | Long | æ¯ | å鿍¡å IDï¼ä»æ¨¡å管ç页带å
¥ï¼ | |
| | | | topK | Integer | å¦ | æ£ç´¢è¿åæ¡æ°ï¼é»è®¤ 3 | |
| | | | similarityThreshold | Double | å¦ | ç¸ä¼¼åº¦éå¼ï¼é»è®¤ 0.7 | |
| | | | status | Integer | å¦ | ç¶æï¼0=å¯ç¨ | |
| | | |
| | | **æç¤º**ï¼å建ç¥è¯åºåï¼éè¦å
æä¸ä¸ª `type=5 (EMBEDDING)` çæ¨¡åãå端åºå¨éæ©å鿍¡åæ¶è¿æ»¤åªæ¾ç¤º `type=5` çæ¨¡åã |
| | | |
| | | **POST /document/create 请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | knowledgeId | Long | æ¯ | ç¥è¯åº ID | |
| | | | names | List\<String\> | æ¯ | ææ¡£åç§°å表ï¼å¦ `["产åæå.pdf", "APIææ¡£.docx"]` | |
| | | | urls | List\<String\> | æ¯ | ææ¡£ URL å表ï¼ä¸ names ä¸ä¸å¯¹åºï¼ | |
| | | |
| | | --- |
| | | |
| | | ### 4. ææ¡£ç®¡ç |
| | | |
| | | **åºç¡è·¯å¾**ï¼`/ai/knowledge/document` |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | PUT | `/ai/knowledge/document/update` | æ´æ°ææ¡£ | |
| | | | DELETE | `/ai/knowledge/document/delete` | å é¤ææ¡£ï¼å«å段+åéï¼ | |
| | | | GET | `/ai/knowledge/document/get` | è·ååä¸ªææ¡£ | |
| | | | GET | `/ai/knowledge/document/page` | å页æ¥è¯¢ææ¡£ | |
| | | | PUT | `/ai/knowledge/document/update-status` | æ´æ°ææ¡£ç¶æ | |
| | | | POST | `/ai/knowledge/document/process/{id}` | å¤çææ¡£ï¼å段+åéåï¼ | |
| | | | GET | `/ai/knowledge/document/progress` | æ¥è¯¢ææ¡£å¤çè¿åº¦ | |
| | | |
| | | **PUT /update 请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | id | Long | æ¯ | ææ¡£ ID | |
| | | | name | String | å¦ | ææ¡£åç§° | |
| | | | url | String | å¦ | ææ¡£ URL | |
| | | |
| | | **POST /process/{id}**ï¼å°ææ¡£å
容åçå¹¶åå
¥ Milvus åéåºãå¤ç宿è½è¢«æ£ç´¢å°ã |
| | | |
| | | **GET /progress 请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | documentIds | List\<Long\> | æ¯ | ææ¡£ ID å表 | |
| | | |
| | | **GET /progress ååºæ°å¢å段ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | documentId | Long | ææ¡£ ID | |
| | | | count | Long | åæ®µæ»æ° | |
| | | | embeddingCount | Long | å·²åéåæ°é | |
| | | |
| | | --- |
| | | |
| | | ### 5. åæ®µç®¡ç & RAG æ£ç´¢ |
| | | |
| | | **åºç¡è·¯å¾**ï¼`/ai/knowledge/segment` |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | GET | `/ai/knowledge/segment/get` | è·ååä¸ªåæ®µ | |
| | | | GET | `/ai/knowledge/segment/page` | å页æ¥è¯¢å段 | |
| | | | **GET** | **`/ai/knowledge/segment/split`** | **ææ¡£ååé¢è§ï¼å¤çåé¢è§ååææï¼** | |
| | | | PUT | `/ai/knowledge/segment/update-status` | æ´æ°åæ®µç¶æ | |
| | | | **POST** | **`/ai/knowledge/segment/search`** | **RAG åéæ£ç´¢ï¼æ ¸å¿æ¥å£ï¼** | |
| | | |
| | | ### GET /ai/knowledge/segment/split |
| | | |
| | | ç¨äºå¨ææ¡£å¤çåé¢è§ååææï¼ä¸ä¿å任使°æ®ã |
| | | |
| | | **请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | url | String | æ¯ | ææ¡£ URL | |
| | | | name | String | æ¯ | ææ¡£åç§°ï¼ç¨äºå¤ææä»¶ç±»åï¼ | |
| | | | segmentMaxTokens | Integer | å¦ | åæ®µæå¤§ Token æ°ï¼é»è®¤ 800 | |
| | | |
| | | **ååºç¤ºä¾ï¼** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": [ |
| | | { |
| | | "content": "ç¬¬ä¸æ®µå
容...", |
| | | "contentLength": 256, |
| | | "tokens": 180 |
| | | }, |
| | | { |
| | | "content": "ç¬¬äºæ®µå
容...", |
| | | "contentLength": 300, |
| | | "tokens": 210 |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## RAG æ£ç´¢ï¼æ ¸å¿åè½ï¼ |
| | | |
| | | ### POST /ai/knowledge/segment/search |
| | | |
| | | **请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | knowledgeId | Long | æ¯ | ç¥è¯åº ID | |
| | | | content | String | æ¯ | æ£ç´¢å
容ï¼èªç¶è¯è¨ï¼ | |
| | | | topK | Integer | å¦ | è¿åæ¡æ°ï¼é»è®¤åç¥è¯åºé
ç½®ç topK | |
| | | | similarityThreshold | Double | å¦ | ç¸ä¼¼åº¦éå¼ï¼é»è®¤åç¥è¯åºé
ç½®çéå¼ | |
| | | |
| | | **ååºï¼** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": [ |
| | | { |
| | | "id": 100, |
| | | "documentId": 10, |
| | | "knowledgeId": 1, |
| | | "content": "åæ®µå
å®¹ææ¬...", |
| | | "contentLength": 256, |
| | | "tokens": 180, |
| | | "score": 0.85 |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | **ååºå段说æï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | Long | åæ®µ ID | |
| | | | documentId | Long | æå±ææ¡£ ID | |
| | | | knowledgeId | Long | æå±ç¥è¯åº ID | |
| | | | content | String | åæ®µå
å®¹ææ¬ | |
| | | | contentLength | Integer | å
容å符é¿åº¦ | |
| | | | tokens | Integer | ä¼°ç® Token æ° | |
| | | | score | Double | ç¸ä¼¼åº¦åæ°ï¼0~1ï¼ï¼è¶é«è¶ç¸å
³ | |
| | | |
| | | --- |
| | | |
| | | ## åæ®µå±ç¤ºè§å |
| | | |
| | | ### ç¥è¯åºå表/详æ
|
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | name | å表ã详æ
| ç¥è¯åºåç§° | |
| | | | description | 详æ
| ç¥è¯åºæè¿° | |
| | | | embeddingModel | å表ã详æ
| æ¾ç¤ºæ¨¡åæ è¯å¦ `text-embedding-v3` | |
| | | | topK | 详æ
| æ£ç´¢è¿åæå¤§æ¡æ° | |
| | | | similarityThreshold | 详æ
| ç¸ä¼¼åº¦éå¼ï¼0~1ï¼ | |
| | | | status | å表ã详æ
| å¯ç¨/ç¦ç¨ | |
| | | | createTime | å表 | å建æ¶é´ | |
| | | |
| | | ### ææ¡£å表/详æ
|
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | name | å表ã详æ
| ææ¡£åç§° | |
| | | | url | 详æ
| ææ¡£ URL | |
| | | | contentLength | 详æ
| å
容é¿åº¦ | |
| | | | tokens | 详æ
| Token æ° | |
| | | | retrievalCount | 详æ
| 被æ£ç´¢å½ä¸æ¬¡æ° | |
| | | | status | å表ï¼tagï¼ | ç¶ææ ç¾ | |
| | | |
| | | ### åæ®µå表 |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | documentId | å表 | æå±ææ¡£ ID | |
| | | | content | åè¡¨ï¼æªæï¼ | åæ®µå
容ï¼å¯æªææ¾ç¤ºå 100 åï¼ | |
| | | | contentLength | å表 | å
容é¿åº¦ | |
| | | | tokens | å表 | Token æ° | |
| | | | vectorId | å表/详æ
| åé IDï¼é空表示已åéåï¼ | |
| | | | retrievalCount | å表 | æ£ç´¢å½ä¸æ¬¡æ° | |
| | | |
| | | ### RAG æ£ç´¢ç»æ |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | content | ç»æå¡ç | åæ®µå
¨æå
容 | |
| | | | score | ç»æå¡ç | ç¸ä¼¼åº¦åæ°ï¼å»ºè®®ç¨è¿åº¦æ¡æé¢è²æ è¯ï¼>0.8 绿è²ï¼0.6~0.8 é»è²ï¼<0.6 ç°è²ï¼ | |
| | | | documentId | è¾
å©ä¿¡æ¯ | å¯ç¹å»è·³è½¬å°æ¥æºææ¡£ | |
| | | | contentLength | è¾
å©ä¿¡æ¯ | å
容é¿åº¦ | |
| | | |
| | | --- |
| | | |
| | | ## ä¸å¡è§å说æ |
| | | |
| | | | åºæ¯ | è§å | |
| | | |------|------| |
| | | | åå»ºææ¡£ | å端èªå¨ä¸è½½ URL æä»¶å¹¶æåææ¬ãæ¯æ `txt/md/json/xml/csv/yaml/yml`ï¼ç´æ¥è¯»åï¼åå
¶ä»æ ¼å¼ï¼Tika è§£æï¼ | |
| | | | ææ¡£å¤ç | å¿
é¡»å
`POST /process/{id}` å¤çåï¼ææ¡£å
容æä¼è¢«æ£ç´¢å° | |
| | | | ææ¡£å段 | åç«¯ä½¿ç¨ `TokenTextSplitter` èªå¨åçï¼é»è®¤æ¯æ®µ 800 tokens | |
| | | | åéåå
¥ | å¤çææ¡£æ¶èªå¨è°ç¨ Embedding 模åçæåéå¹¶åå
¥ Milvus | |
| | | | å é¤ææ¡£ | åæ¶å é¤ææ¡£è®°å½ãææå段记å½å Milvus ä¸å¯¹åºåé | |
| | | | æ£ç´¢ç¸ä¼¼åº¦ | è¿åç»ææç¸ä¼¼åº¦éåºæåï¼ä½äº `similarityThreshold` çç»æä¸ä¼è¢«è¿å | |
| | | | æ£ç´¢è®¡æ°å¨ | æ¯æ¬¡æ£ç´¢å½ä¸åï¼èªå¨æ´æ°å¯¹åºåæ®µåææ¡£ç `retrievalCount` | |
| | | | API å¯é¥ | å建ç¥è¯åºåï¼å¿
é¡»å
å建 API å¯é¥å Embedding 模å | |
| | | |
| | | --- |
| | | |
| | | ## æ¥å£è°ç¨æ¶åº |
| | | |
| | | ### å建ç¥è¯åºå®æ´æµç¨ |
| | | |
| | | ``` |
| | | 1. POST /ai/api-key/create â apiKeyId |
| | | 2. POST /ai/model/create â modelId (type=5, keyId=apiKeyId) |
| | | 3. POST /ai/knowledge/create â knowledgeId (embeddingModelId=modelId) |
| | | 4. POST /ai/knowledge/document/create â documentIds |
| | | 5. POST /ai/knowledge/document/process/{documentId} â åéå |
| | | 6. GET /ai/knowledge/document/progress?documentIds=... â æ£æ¥è¿åº¦ |
| | | 7. POST /ai/knowledge/segment/search â RAG æ£ç´¢ |
| | | ``` |
| | | |
| | | ### æ£ç´¢æµç¨ï¼å·²é
置好çç¥è¯åºï¼ |
| | | |
| | | ``` |
| | | 1. POST /ai/knowledge/segment/search |
| | | { knowledgeId, content, topK?, similarityThreshold? } |
| | | â |
| | | å端: æ¥è¯¢åé â Milvusç¸ä¼¼åº¦æ£ç´¢ â è¿æ»¤ä½äºéå¼çç»æ â æ´æ°æ£ç´¢è®¡æ° |
| | | â |
| | | è¿åæç´¢ç»æå表 |
| | | ``` |
| | | |
| | | --- |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - ç¥è¯åºå建æ¶å¿
须鿩 `type=5 (EMBEDDING)` çæ¨¡åï¼å端è·å模åå表åéè¦è¿æ»¤ |
| | | - ææ¡£ä¸ä¼ å**ä¸ä¼èªå¨å¤ç**ï¼éè¦å¨ææ¡£å表ç¹å»ãå¤çãæé®è°ç¨ `POST /process/{id}` |
| | | - å¤çè¿åº¦å¯éè¿ `GET /progress` 轮询æ¥è¯¢ï¼`embeddingCount >= count` 表示å¤çå®æï¼ |
| | | - æ£ç´¢ç `similarityThreshold` 建议é»è®¤ 0.7ï¼å¯æ ¹æ®å®é
ææè°æ´ |
| | | - `vectorId` 为空ï¼`""`ï¼çåæ®µè¡¨ç¤ºå°æªåå
¥åéåºï¼ä¸ä¼è¢«æ£ç´¢å° |
| | | - å é¤ç¥è¯åºæ¶ï¼å端å½åå®ç°åªå é¤ç¥è¯åºè®°å½æ¬èº«ï¼é确认æ¯å¦éè¦çº§èå é¤ææ¡£ååæ®µ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # CRM é宿¥ä»·å AI OCR è¯å« - å端èè°æ¹æ¡ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - `saleQuotation/index.vue` â æ¥ä»·ååè¡¨é¡µï¼æ°å¢"AI è¯å«å½å
¥"æé® |
| | | - `saleQuotation/form.vue` â æ¥ä»·åæ°å¢/ç¼è¾å¼¹çªï¼æ¥æ¶ OCR è¯å«ç»æé¢å¡«è¡¨å |
| | | |
| | | ## ä¸å¡æµç¨ä¸æ°æ®å¸¦å
¥ |
| | | |
| | | 1. æ¥ä»·åå表页 â ç¹å»"AI è¯å«å½å
¥"æé® â å¼¹åºä¸ä¼ å¼¹çª |
| | | 2. ä¸ä¼ å¼¹çªä¸ï¼éæ©æä»¶ä¸ä¼ â è°ç¨ `/admin-api/system/storage-blob/upload` â è·å¾ `blobId` |
| | | 3. è°ç¨ `/admin-api/crm/sale-quotation/ai/ocr` ä¼ å
¥ `blobId` â è·å¾è¯å«ç»æ |
| | | 4. é¢è§è¯å«ç»æ â ç¨æ·ç¹å»"确认并填å
¥è¡¨å" â å
³éä¸ä¼ å¼¹çª â æå¼æ°å¢æ¥ä»·åå¼¹çª |
| | | 5. æ°å¢å¼¹çªä¸ï¼è¯å«ç»æé¢å¡«å°å¯¹åºå段ï¼ç¨æ·è¡¥å
å®ååä¿å |
| | | |
| | | ## API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | `/admin-api/crm/sale-quotation/ai/ocr` | AI OCR è¯å«æ¥ä»·æä»¶ | |
| | | |
| | | **请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | blobId | Long | æ¯ | å·²ä¸ä¼ æä»¶ç blob IDï¼éè¿ `/admin-api/system/storage-blob/upload` è·åï¼ | |
| | | |
| | | **ååºå段ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | name | String | æ¥ä»·ååç§° | |
| | | | customerName | String | 客æ·åç§°ï¼ææ¬ï¼éç¨æ·æå¨å¹é
CRM 客æ·ï¼ | |
| | | | quotationTime | String (LocalDate) | æ¥ä»·æ¥æï¼æ ¼å¼ yyyy-MM-dd | |
| | | | validUntil | String (LocalDate) | æææè³ï¼æ ¼å¼ yyyy-MM-dd | |
| | | | taxRate | BigDecimal | ç¨çï¼ç¾åæ¯ï¼ï¼å¦ 13.0 | |
| | | | discountPercent | BigDecimal | ææ£çï¼ç¾åæ¯ï¼ï¼å¦ 5.0 | |
| | | | remark | String | 夿³¨ | |
| | | | items | Array\<Item\> | ç©ææç»å表 | |
| | | | items[].itemName | String | ç©æåç§°ï¼ææ¬ï¼éç¨æ·å¨ MdmItemSelect ä¸å¹é
ï¼ | |
| | | | items[].itemSpec | String | ç©æè§æ ¼åå· | |
| | | | items[].count | BigDecimal | æ°é | |
| | | | items[].quotationPrice | BigDecimal | åä»· | |
| | | | rawText | String | åå§è¯å«ææ¬ï¼å端å¯éå±ç¤ºç¨äºè°è¯/åè | |
| | | |
| | | ææå段å为å¯éï¼AI æªè¯å«å°çåæ®µä¸º nullã |
| | | |
| | | **ååºç¤ºä¾ï¼** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "name": "XXå
¬å¸æ¥ä»·å", |
| | | "customerName": "XXç§ææéå
¬å¸", |
| | | "quotationTime": "2026-07-31", |
| | | "validUntil": "2026-08-30", |
| | | "taxRate": 13.0, |
| | | "discountPercent": 5.0, |
| | | "remark": "å«ç¨å«è¿è´¹", |
| | | "items": [ |
| | | { |
| | | "itemName": "伺æçµæº", |
| | | "itemSpec": "SV-1000W", |
| | | "count": 10, |
| | | "quotationPrice": 1500.00 |
| | | } |
| | | ], |
| | | "rawText": "åå§æä»¶å
容..." |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | **é误æ
åµï¼HTTP 200ï¼ææå段为 nullï¼rawText ä¸å
å«é误æç¤ºï¼ï¼** |
| | | |
| | | | rawText å¼ | åºæ¯ | |
| | | |------------|------| |
| | | | æä»¶ä¸åå¨ | blobId æ æ | |
| | | | 䏿¯æçæä»¶ç±»åï¼è¯·ä¸ä¼ PDF/Word/Excel/å¾çæä»¶ | æä»¶åç¼ä¸å¨ç½ååå
| |
| | | | æä»¶è¯»å失败 | æä»¶ä¸è½½å¤±è´¥ | |
| | | | æä»¶å
å®¹ä¸ºç©ºï¼æ æ³è¯å« | æä»¶è§£æåºç©ºå
容 | |
| | | | AI è¯å«ææ¶ä¸å¯ç¨ï¼è¯·æå¨å½å
¥ | AI æå¡å¼å¸¸ | |
| | | |
| | | ## æ¯æçæä»¶ç±»å |
| | | |
| | | | ç±»å | åç¼ | |
| | | |------|------| |
| | | | PDF | .pdf | |
| | | | Word | .doc, .docx | |
| | | | Excel | .xls, .xlsx | |
| | | | å¾ç | .png, .jpg, .jpeg, .gif, .bmp | |
| | | | çº¯ææ¬ | .txt, .csv | |
| | | |
| | | ## æ°æ®å¡«å
¥è§å |
| | | |
| | | | OCR åæ®µ | å¡«å
¥æ°å¢è¡¨ååæ®µ | 说æ | |
| | | |----------|-----------------|------| |
| | | | name | æ¥ä»·ååç§° | ç´æ¥å¡«å
¥ | |
| | | | customerName | 客æ·åç§°ï¼ææ¬å±ç¤ºï¼ | ä¸èªå¨å¹é
CRM 客æ·ï¼ç¨æ·éæå¨éè¿ CrmCustomerSelect éæ© | |
| | | | quotationTime | æ¥ä»·æ¥æ | ç´æ¥å¡«å
¥æ¥æéæ©å¨ | |
| | | | validUntil | æææè³ | ç´æ¥å¡«å
¥æ¥æéæ©å¨ | |
| | | | taxRate | ç¨ç | ç´æ¥å¡«å
¥ï¼åä½ä¸ºç¾åæ¯ | |
| | | | discountPercent | ææ£ç | ç´æ¥å¡«å
¥ï¼åä½ä¸ºç¾åæ¯ | |
| | | | remark | 夿³¨ | ç´æ¥å¡«å
¥ | |
| | | | items[].itemName + itemSpec | ç©ææç»è¡ | å¡«å
¥ç©æåç§°åè§æ ¼ææ¬ï¼ç¨æ·éå¨ MdmItemSelect 䏿å¨å¹é
å®é
ç©æ | |
| | | | items[].count | ç©ææç»è¡ - æ°é | ç´æ¥å¡«å
¥ | |
| | | | items[].quotationPrice | ç©ææç»è¡ - åä»· | ç´æ¥å¡«å
¥ | |
| | | |
| | | ## ä¸å¡è§å说æ |
| | | |
| | | | åºæ¯ | è§å | |
| | | |------|------| |
| | | | AI è¯å«ç»æå
¨ä¸º null | å¼¹çªæç¤º"æªè¯å«å°æ¥ä»·æ°æ®"ï¼ç¨æ·å¯æå¨å½å
¥æéæ°ä¸ä¼ | |
| | | | é¨ååæ®µè¯å«æå | é¢å¡«å·²æå段ï¼ç¼ºå¤±å段ç空ç±ç¨æ·æå¨å¡«å | |
| | | | è¯å«å°å¤ä¸ªç©æä½ç¨æ·åªéé¨å | ç¨æ·å¨æ°å¢å¼¹çªä¸å¢å è°æ´ç©æè¡ | |
| | | | 客æ·åç§°æ æ³å¹é
CRM å®¢æ· | ç¨æ·éè¿ CrmCustomerSelect æç´¢å¹¶éæ©æ£ç¡®çå®¢æ· | |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - æä»¶ä¸ä¼ 使ç¨ç°æç `/admin-api/system/storage-blob/upload` æ¥å£ï¼ä¸è¦ä½¿ç¨ `/infra/file/upload` |
| | | - æä»¶å¤§å°éå¶ç± system 模åç storage-blob ä¸ä¼ æ¥å£æ§å¶ |
| | | - AI OCR è¯å«å¯è½éè¦å ç§æ¶é´ï¼åç«¯éæ¾ç¤º loading ç¶æ |
| | | - `customerName` æ¯çº¯ææ¬ï¼å端ä¸è¦å°è¯èªå¨å¹é
ï¼éè¦ç¨æ·éè¿ CrmCustomerSelect æå¨éæ© |
| | | - `itemName` åçï¼ç¨æ·éå¨ MdmItemSelect ä¸æç´¢ç©æç¼ç /åç§°æ¥å¹é
å®é
ç©æ |
| | | - è¯å«ç»æä¸çæ¥ææ ¼å¼ä¸º `yyyy-MM-dd`ï¼ç´æ¥å¯ç¨äºæ¥æéæ©å¨ |
| | | - å½ rawText ä¸å
å«é误æç¤ºï¼å¦"æä»¶ä¸åå¨"ï¼æ¶ï¼å端åºå±ç¤ºé误信æ¯ï¼ä¸è¦ç»§ç»å¡«å
¥è¡¨åæµç¨ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # ç产模å AI è¾
å©å¢å¼º - å端èè°æ¹æ¡ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - ç产工å详æ
页ï¼MesProWorkOrderï¼ |
| | | |
| | | --- |
| | | |
| | | ## åè½ä¸ï¼AI ç©æçç¼ºé¢æµ |
| | | |
| | | ### ä¸å¡æµç¨ |
| | | |
| | | ``` |
| | | å¨å·¥å详æ
页 |
| | | â ç¹å»"AI ç©æé¢æµ"æé® |
| | | â å端æ¶é BOM + åºåæ°æ®ï¼è°ç¨ AI |
| | | â å¼¹çªå±ç¤ºç©æé£é©åæï¼æ´ä½é£é©ç级 + åç©ææç»ï¼ |
| | | ``` |
| | | |
| | | ### API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | `/admin-api/mes/pro/ai/predict-material` | AI ç©æçç¼ºé¢æµ | |
| | | |
| | | **请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | workOrderId | Long | æ¯ | å·¥å ID | |
| | | |
| | | **ååºå段ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | riskLevel | Integer | æ´ä½é£é©ç级ï¼1=ä½é£é© 2=ä¸é£é© 3=é«é£é© | |
| | | | reasoning | String | AI 综ååæ | |
| | | | riskItems | List\<RiskItem\> | ç©æé£é©æç» | |
| | | |
| | | **RiskItem ååæ®µï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | itemCode | String | ç©æç¼ç | |
| | | | itemName | String | ç©æåç§° | |
| | | | requiredQuantity | BigDecimal | éæ±é | |
| | | | availableQuantity | BigDecimal | åºåå¯ç¨é | |
| | | | shortageQuantity | BigDecimal | ç缺æ°é | |
| | | | riskLevel | String | é£é©ç级ï¼å
è¶³/ç´§å¼ /ç缺 | |
| | | | suggestion | String | å»ºè®®æªæ½ | |
| | | |
| | | --- |
| | | |
| | | ## åè½äºï¼AI ç产æ¶é¿é¢æµ |
| | | |
| | | ### API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | `/admin-api/mes/pro/ai/predict-duration` | AI ç产æ¶é¿é¢æµ | |
| | | |
| | | **ååºå段ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | predictedTotalHours | BigDecimal | 颿µæ»æ¶é¿ï¼å°æ¶ï¼ | |
| | | | standardTotalHours | BigDecimal | æ åæ»æ¶é¿ï¼å°æ¶ï¼ | |
| | | | reasoning | String | AI æ¨çåæ | |
| | | | keyPoints | List\<String\> | å
³é®å
³æ³¨ç¹ | |
| | | | processBreakdown | List\<ProcessTimeBreakdown\> | åå·¥åºæ¶é¿æç» | |
| | | |
| | | **ProcessTimeBreakdown ååæ®µï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | processName | String | å·¥åºåç§° | |
| | | | sort | Integer | å·¥åºé¡ºåº | |
| | | | standardPrepareTime | Integer | æ åå夿¶é´ï¼åéï¼ | |
| | | | standardWaitTime | Integer | æ åçå¾
æ¶é´ï¼åéï¼ | |
| | | | standardProductionTime | BigDecimal | æ åç产æ¶é´ï¼å°æ¶ï¼ | |
| | | | predictedProductionTime | BigDecimal | 颿µç产æ¶é´ï¼å°æ¶ï¼ | |
| | | |
| | | --- |
| | | |
| | | ## åè½ä¸ï¼AI ç产é£é©é¢æµ |
| | | |
| | | ### API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | `/admin-api/mes/pro/ai/predict-risk` | AI ç产é£é©é¢æµ | |
| | | |
| | | **ååºå段ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | overallRiskLevel | Integer | æ´ä½é£é©ç级ï¼1=ä½é£é© 2=ä¸é£é© 3=é«é£é© | |
| | | | reasoning | String | AI 综ååæ | |
| | | | keyPoints | List\<String\> | å
³é®å
³æ³¨ç¹ | |
| | | | risks | List\<RiskItem\> | é£é©æç»å表 | |
| | | |
| | | **RiskItem ååæ®µï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | category | String | é£é©ç±»å«ï¼è´¨é/设å¤/ç©æ/å·¥èºç¶é¢ | |
| | | | description | String | é£é©æè¿° | |
| | | | severity | String | 严éç¨åº¦ï¼é«/ä¸/ä½ | |
| | | | suggestion | String | å»ºè®®æªæ½ | |
| | | |
| | | --- |
| | | |
| | | ## åè½åï¼AI æ¯å¦è½ææ¶äº¤ä» |
| | | |
| | | ### API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | `/admin-api/mes/pro/ai/predict-delivery` | AI ææ¶äº¤ä»é¢æµ | |
| | | |
| | | **ååºå段ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | onTime | Boolean | æ¯å¦è½ææ¶äº¤ä» | |
| | | | confidence | Double | 置信度ï¼0-100ï¼ | |
| | | | reasoning | String | AI æ¨çåæ | |
| | | | delayFactors | List\<String\> | å»¶æå ç´ | |
| | | | progressPercent | BigDecimal | ç产è¿åº¦ç¾åæ¯ | |
| | | | remainingDays | Integer | è·ç¦»éæ±æ¥æå©ä½å¤©æ° | |
| | | |
| | | --- |
| | | |
| | | ## åæ®µå±ç¤ºè§å |
| | | |
| | | ### ç©æé¢æµå¼¹çª |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | riskLevel | å¼¹çªé¡¶é¨éç®æ¾ç¤ºï¼é¢è²ï¼ç»¿è²=ä½é£é©ãæ©è²=ä¸é£é©ã红è²=é«é£é©ï¼ | æ´ä½é£é©ç级 | |
| | | | reasoning | å¼¹çªæ£æåºå | æ®µè½ææ¬ | |
| | | | riskItems | å¼¹çªä¸é¨è¡¨æ ¼ | ç©ææç»å表 | |
| | | |
| | | ### æ¶é¿é¢æµå¼¹çª |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | predictedTotalHours | å¼¹çªé¡¶é¨éç®æ¾ç¤ºï¼å¯¹æ¯ standardTotalHours | 颿µ vs æ å | |
| | | | reasoning | å¼¹çªæ£æåºå | æ®µè½ææ¬ | |
| | | | keyPoints | å¼¹çªåºé¨ | icon + ææ¬å表 | |
| | | | processBreakdown | å¼¹çªä¸é¨è¡¨æ ¼ | æ¯éå·¥åºçæ¶é¿åè§£ | |
| | | |
| | | ### é£é©é¢æµå¼¹çª |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | overallRiskLevel | å¼¹çªé¡¶é¨éç®æ¾ç¤º | é¢è²åºå | |
| | | | reasoning | å¼¹çªæ£æåºå | æ®µè½ææ¬ | |
| | | | keyPoints | å¼¹çªä¸é¨ | å
³æ³¨ç¹å表 | |
| | | | risks | å¼¹çªåºé¨è¡¨æ ¼ | æç±»å«åç»å±ç¤º | |
| | | |
| | | ### 交ä»é¢æµå¼¹çª |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | onTime | å¼¹çªé¡¶é¨éç®æ¾ç¤ºï¼ç»¿è²=å¯äº¤ä»ã红è²=ä¸å¯äº¤ä»ï¼ | æ¯å¦ææ¶ | |
| | | | confidence | onTime æè¾¹ | 置信度ç¾åæ¯ | |
| | | | progressPercent | è¿åº¦æ¡ | å¯è§åè¿åº¦ | |
| | | | remainingDays | è¿åº¦æ¡å³ä¾§ | å©ä½å¤©æ° | |
| | | | reasoning | å¼¹çªæ£æåºå | æ®µè½ææ¬ | |
| | | | delayFactors | å¼¹çªåºé¨ | å»¶æå ç´ å表ï¼ä»
ä¸å¯äº¤ä»æ¶æ¾ç¤ºï¼ | |
| | | |
| | | --- |
| | | |
| | | ## ä¸å¡è§å |
| | | |
| | | | åºæ¯ | è§å | |
| | | |------|------| |
| | | | ç¹å»æé® | æé®æ¾ç¤º loading ç¶æï¼ä¸å¯éå¤ç¹å» | |
| | | | AI è¿åæå | å¼¹çªå±ç¤ºç»æï¼ä»
ä½åè | |
| | | | AI è¿å失败 | å¼¹çªæ¾ç¤º"AI åæææ¶ä¸å¯ç¨ï¼è¯·ç¨åéè¯"ï¼ä¸å½±åæ£å¸¸æä½ | |
| | | | å·¥åç¶æä¸ºè稿/已忶 | ä»å¯è°ç¨ AI 颿µï¼ç¨äºç产åè¯ä¼°ï¼ | |
| | | | æ BOM æ°æ® | ç©æé¢æµè¿å空é£é©é¡¹ | |
| | | | æªæäº§ï¼æ ä»»å¡ï¼ | æ¶é¿é¢æµä»
è¿åæ åæ¶é´ä¼°ç® | |
| | | |
| | | ### æé®æ¾ç½®ä½ç½® |
| | | |
| | | å¨å·¥å详æ
页çæä½åºåæ°å¢ 4 个 AI è¾
婿é®ã建议æ¾å¨"确认/宿/åæ¶"æé®åºåç䏿¹ï¼ç¨åé线éå¼ï¼ |
| | | |
| | | ``` |
| | | [AI ç©æé¢æµ] [AI æ¶é¿é¢æµ] [AI é£é©é¢æµ] [AI 交ä»è¯ä¼°] |
| | | ââââââââââââââââââââââââââââââââââââ |
| | | [确认工å] [宿工å] [åæ¶å·¥å] |
| | | ``` |
| | | |
| | | 4 个 AI æé®å¨å·¥åç¶æä¸º"è稿"å"已确认"æ¶åå¯ç¹å»ã |
| | | |
| | | --- |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - AI åæå»ºè®®ä»
ä½åèï¼ä¸èªå¨ä¿®æ¹å·¥åæ°æ® |
| | | - AI ååºæ¶é´å¯è½è¾é¿ï¼5-15 ç§ï¼ï¼å¡å¿
æ¾ç¤º loading ç¶æ |
| | | - 4 个 AI 端ç¹ç¬ç«è°ç¨ï¼åç«¯å¯æéè°ç¨å
¶ä¸ä¸ä¸ªæå¤ä¸ª |
| | | - `workOrderId` åå½åå·¥å详æ
页ç `id` åæ®µ |
| | | - ææç«¯ç¹æé为 `mes:pro-work-order:query`ï¼å½åç¨æ·éæçäº§å·¥åæ¥è¯¢æé |
| | | - ååºä¸çåæ®µå为å¯éï¼AI è§£æå¤±è´¥æ¶å¯è½ä¸ºç©ºï¼ï¼å端éåé²å¾¡æ§å¤æ |
| | | - ç©æ/æ¶é¿/é£é©/äº¤ä» 4 个åæå¯ç¬ç«ä½¿ç¨ï¼æ å
åä¾èµ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # MES è´¨æ£ AI åè½ - å端èè°æ¹æ¡ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | - æ¥ææ£éªå详æ
页ï¼IQCï¼ |
| | | - è¿ç¨æ£éªå详æ
页ï¼IPQCï¼ |
| | | - åºè´§æ£éªå详æ
页ï¼OQCï¼ |
| | | - éè´§æ£éªå详æ
页ï¼RQCï¼ |
| | | - è´¨æ£æ åç¥è¯åºç®¡ç页ï¼å¯éï¼ |
| | | |
| | | --- |
| | | |
| | | ## åè½ä¸ï¼AI è¾
å©å¤å®æ£éªç»æ |
| | | |
| | | ### ä¸å¡æµç¨ |
| | | |
| | | ``` |
| | | æ£éªåå½å
¥ææ æ£æµå¼ + 缺é·è®°å½ |
| | | â ç¹å»"AI è¾
å©å¤å®"æé® |
| | | â è°ç¨ AI æ¥å£è·åå¤å®å»ºè®® |
| | | â å¼¹çªå±ç¤º AI 建议ï¼å¤å®ç»æ + æ¨ç便® + å
³æ³¨ç¹ï¼ |
| | | â æ£éªååèå»ºè®®ï¼æå¨éæ©æç»å¤å®ç»æ |
| | | â ç¹å»"宿æ£éª" |
| | | ``` |
| | | |
| | | ### API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | `/admin-api/mes/qc/ai/suggest-result` | AI è¾
å©å¤å® | |
| | | |
| | | **请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | qcId | Long | æ¯ | è´¨æ£å ID | |
| | | | qcType | Integer | æ¯ | è´¨æ£ç±»åï¼1=æ¥æ 2=è¿ç¨ 3=åºè´§ 4=éè´§ | |
| | | |
| | | **ååºå段ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | suggestedResult | Integer | 建议å¤å®ç¼ç ï¼1åæ ¼ 2ç¹é 3ä¸åæ ¼éè´§ 4ä¸åæ ¼æ¥åº | |
| | | | suggestedResultName | String | 建议å¤å®åç§° | |
| | | | reasoning | String | AI æ¨ç便®ï¼ææ¬ï¼ | |
| | | | keyPoints | List\<String\> | å
³é®å
³æ³¨ç¹æ¸
å | |
| | | |
| | | **ååºç¤ºä¾ï¼** |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "suggestedResult": 1, |
| | | "suggestedResultName": "åæ ¼", |
| | | "reasoning": "æææ£æµææ åå¨å
许èå´å
ï¼ç¼ºé·æ°éæªè¶
è¿ä¸éï¼å»ºè®®å¤å®ä¸ºåæ ¼ã", |
| | | "keyPoints": ["å¤è§æ£æ¥ææ æ¥è¿ä¸éï¼å»ºè®®å å¼ºæ¥æå¤è§ç®¡æ§"] |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | ### åæ®µå±ç¤ºè§å |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | suggestedResultName | å¼¹çªé¡¶é¨éç®æ¾ç¤ºï¼ç¨é¢è²åºåï¼ç»¿è²=åæ ¼ãæ©è²=ç¹éã红è²=ä¸åæ ¼ï¼ | å¤å®å»ºè®® | |
| | | | reasoning | å¼¹çªæ£æåºå | æ¨ç便®ï¼å±ç¤ºä¸ºæ®µè½ææ¬ | |
| | | | keyPoints | å¼¹çªåºé¨ï¼å表å±ç¤º | å
³æ³¨ç¹ä»¥ icon + ææ¬å±ç¤º | |
| | | |
| | | ### ä¸å¡è§å |
| | | |
| | | | åºæ¯ | è§å | |
| | | |------|------| |
| | | | ç¹å»æé® | æé®æ¾ç¤º loading ç¶æï¼**ä¸å¯éå¤ç¹å»** | |
| | | | AI è¿åæå | å¼¹çªå±ç¤ºç»æï¼æ£éªåèªè¡å³å®æ¯å¦é纳 | |
| | | | AI è¿å失败 | æç¤º"AI å¤å®ææ¶ä¸å¯ç¨ï¼è¯·äººå·¥å¤å®"ï¼ä¸å½±åæ£å¸¸æ£éªæµç¨ | |
| | | | AI å¤å®è¶
æ¶ | 设置 30 ç§è¶
æ¶ï¼è¶
æ¶åæç¤ºç¨æ·éè¯ | |
| | | | æªå½å
¥æ£æµå¼ | å
许è°ç¨ AIï¼AI ä¼åºäºå·²æç¼ºé·è®°å½ç»åºå»ºè®® | |
| | | | 已宿æ£éª | ä¸éè¦å±ç¤º AI å¤å®æé®ï¼å·²å®æç¶æéèï¼ | |
| | | |
| | | ### æé®æ¾ç½®ä½ç½® |
| | | |
| | | 卿£éªå详æ
页ç"宿æ£éª"æé®æè¾¹ï¼æ°å¢ `AI è¾
å©å¤å®` æé®ãæé®ä»
å¨è´¨æ£åç¶æä¸º"è稿"æ¶å±ç¤ºã |
| | | |
| | | --- |
| | | |
| | | ## åè½äºï¼è´¨æ£æ åç¥è¯åº RAG æ£ç´¢ |
| | | |
| | | ### ä¸å¡æµç¨ |
| | | |
| | | ``` |
| | | 管çåï¼ä¸ä¼ è´¨æ£æ åææ¡£ â ç³»ç»èªå¨å段+åéå |
| | | æ£éªåï¼å¨è´¨æ£é¡µé¢å³ä¾§"è´¨æ£å©æ"颿¿è¾å
¥é®é¢ â æ£ç´¢ç¸å
³æ åæ®µè½ â å±ç¤ºåèå
容 |
| | | ``` |
| | | |
| | | ### API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | POST | `/admin-api/ai/knowledge/segment/search` | åéæ£ç´¢ç¥è¯åºå段 | |
| | | |
| | | **请æ±åæ°ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | knowledgeId | Long | æ¯ | è´¨æ£æ åç¥è¯åº ID | |
| | | | content | String | æ¯ | æ£ç´¢æ¥è¯¢ææ¬ï¼èªç¶è¯è¨é®é¢ï¼ | |
| | | | topK | Integer | å¦ | è¿åæ°éï¼é»è®¤åç¥è¯åºé
ç½®å¼ï¼å»ºè®® 3~5ï¼ | |
| | | | similarityThreshold | Double | å¦ | ç¸ä¼¼åº¦éå¼ 0~1ï¼å»ºè®® 0.5~0.7 | |
| | | |
| | | **ååºå段ï¼** |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | id | Long | åæ®µ ID | |
| | | | content | String | å¹é
çåæ®µææ¬å
容 | |
| | | | score | Double | ç¸ä¼¼åº¦å¾å | |
| | | | documentName | String | æ¥æºææ¡£åç§° | |
| | | | documentId | Long | æ¥æºææ¡£ ID | |
| | | |
| | | **ååºç¤ºä¾ï¼** |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": [ |
| | | { |
| | | "id": 10, |
| | | "content": "å¤è§æ£éªæ åï¼è¡¨é¢åºå
æ»å¹³æ´ï¼æ è£çº¹ãæ°æ³¡ãæ¯åºãéèç缺é·...", |
| | | "score": 0.85, |
| | | "documentName": "æ¥ææ£éªä½ä¸æå¯¼ä¹¦_v2.pdf", |
| | | "documentId": 5 |
| | | } |
| | | ] |
| | | } |
| | | ``` |
| | | |
| | | ### åç½®åå¤ |
| | | |
| | | 1. å¨ AI ç¥è¯åºç®¡çä¸å建å称为"è´¨æ£æ åç¥è¯åº"çç¥è¯åº |
| | | 2. ä¸ä¼ è´¨æ£ç¸å
³ææ¡£ï¼å½æ ã伿 ãä½ä¸æå¯¼ä¹¦çï¼ |
| | | 3. è®°å½ç¥è¯åº IDï¼å端é
置为常éæéè¿æ¥å£è·å |
| | | |
| | | ### åæ®µå±ç¤ºè§å |
| | | |
| | | | åæ®µ | å±ç¤ºä½ç½® | 说æ | |
| | | |------|----------|------| |
| | | | content | ç»æå¡çæ£æ | é«äº®å½ä¸å
³é®è¯ | |
| | | | score | ç»æå¡çåºé¨å°å | æ ¼å¼å为ç¾åæ¯ï¼å¦"ç¸ä¼¼åº¦ 85%" | |
| | | | documentName | ç»æå¡çé¡¶é¨ | ç¹å»å¯è·³è½¬å°ææ¡£é¢è§é¡µ | |
| | | |
| | | --- |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | ### AI å¤å® |
| | | - AI 建议ä»
ä½**åè**ï¼æç»å¤å®ç±æ£éªåå³å®ï¼ä¸èªå¨åå
¥ |
| | | - AI ååºæ¶é´å¯è½è¾é¿ï¼5~15 ç§ï¼ï¼åç«¯éæ¾ç¤º loading ç¶æ |
| | | - `qcType` åå¼ï¼IQC 详æ
é¡µä¼ 1ãIPQC 详æ
é¡µä¼ 2ãOQC 详æ
é¡µä¼ 3ãRQC 详æ
é¡µä¼ 4 |
| | | - `qcId` åå½åè´¨æ£åç `id` åæ®µ |
| | | |
| | | ### ç¥è¯åºæ£ç´¢ |
| | | - ç¥è¯åº ID éå
å建ç¥è¯åºå¹¶ä¸ä¼ ææ¡£åæè½ä½¿ç¨ |
| | | - 妿æ£ç´¢æ ç»æï¼ç©ºæ°ç»ï¼ï¼å±ç¤º"æªæ¾å°ç¸å
³æ åï¼è¯·å°è¯å
¶ä»å
³é®è¯" |
| | | - 建议å¨è´¨æ£å½å
¥é¡µé¢å³ä¾§ç¨å¯æå 颿¿å±ç¤ºï¼ä¸å½±å主æä½åºå |
| | | - æ£ç´¢è¾å
¥æ¡å»ºè®® placeholderï¼"è¾å
¥é®é¢ï¼å¦ï¼å¤è§æ£éªæ 忝ä»ä¹ï¼" |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # OA éç¥å
¬å - å端èè°æ¹æ¡ |
| | | |
| | | ## æ¶åé¡µé¢ |
| | | |
| | | | é¡µé¢ | è·¯å¾ | 说æ | |
| | | |------|------|------| |
| | | | éç¥å
¬åå表 | `/bpm/oa/notice` | æ°å»ºï¼`src/views/bpm/oa/notice/index.vue` | |
| | | | éç¥å
¬å详æ
| `/bpm/oa/notice/detail` | æ°å»ºï¼`src/views/bpm/oa/notice/detail.vue` | |
| | | |
| | | ## API |
| | | |
| | | | æ¹æ³ | è·¯å¾ | æé | 说æ | |
| | | |------|------|------|------| |
| | | | POST | `/bpm/oa/notice/create` | `bpm:oa-notice:create` | å建 | |
| | | | PUT | `/bpm/oa/notice/update` | `bpm:oa-notice:update` | ä¿®æ¹ | |
| | | | DELETE | `/bpm/oa/notice/delete` | `bpm:oa-notice:delete` | å é¤ | |
| | | | GET | `/bpm/oa/notice/page` | `bpm:oa-notice:query` | å页å表 | |
| | | | GET | `/bpm/oa/notice/get` | `bpm:oa-notice:query` | 详æ
ï¼è°ç¨å³æ è®°å·²è¯»ï¼ | |
| | | | GET | `/bpm/oa/notice/my-unread-count` | - | æçæªè¯»æ°é | |
| | | |
| | | ### 请æ±åæ° |
| | | |
| | | **POST `/bpm/oa/notice/create` & PUT `/bpm/oa/notice/update`ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | id | Long | å¦ï¼æ´æ°æ¶å¿
å¡«ï¼ | éç¥å
ŒID | |
| | | | title | String | æ¯ | æ é¢ï¼æé¿50å符 | |
| | | | content | String | æ¯ | å
容 | |
| | | | type | Integer | æ¯ | ç±»åï¼1=éç¥ 2=å
Œ | |
| | | | status | Integer | æ¯ | ç¶æï¼0=è稿 1=å·²åå¸ 2=å·²æ¤é | |
| | | | sendScope | Integer | æ¯ | åéèå´ï¼1=éç¥ææäºº 2=æå®äºº | |
| | | | isTop | Integer | å¦ | æ¯å¦ç½®é¡¶ï¼0=å¦ 1=æ¯ | |
| | | | userIds | List\<Long\> | å¦ï¼sendScope=2æ¶å¿
å¡«ï¼ | æå®ç¨æ·IDå表 | |
| | | |
| | | **GET `/bpm/oa/notice/page`ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | title | String | å¦ | æ é¢ï¼æ¨¡ç³å¹é
| |
| | | | type | Integer | å¦ | ç±»å | |
| | | | status | Integer | å¦ | ç¶æ | |
| | | | sendScope | Integer | å¦ | åéèå´ | |
| | | | pageNo | Integer | å¦ | 页ç ï¼é»è®¤1 | |
| | | | pageSize | Integer | å¦ | æ¯é¡µæ¡æ°ï¼é»è®¤10 | |
| | | |
| | | **GET `/bpm/oa/notice/get`ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | id | Long | æ¯ | éç¥å
ŒID | |
| | | |
| | | **DELETE `/bpm/oa/notice/delete`ï¼** |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | id | Long | æ¯ | éç¥å
ŒID | |
| | | |
| | | ### ååºæ ¼å¼ |
| | | |
| | | **å页å表 `GET /bpm/oa/notice/page` ååºï¼** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": { |
| | | "list": [ |
| | | { |
| | | "id": 1, |
| | | "title": "å
³äºæ¾åçéç¥", |
| | | "content": "æ ¹æ®å½å®¶è§å®...", |
| | | "type": 1, |
| | | "status": 1, |
| | | "sendScope": 1, |
| | | "isTop": 0, |
| | | "publishUserId": 1, |
| | | "publishTime": "2025-01-01 10:00:00", |
| | | "createTime": "2025-01-01 10:00:00" |
| | | } |
| | | ], |
| | | "total": 100 |
| | | } |
| | | } |
| | | ``` |
| | | |
| | | **详æ
`GET /bpm/oa/notice/get` ååºï¼** |
| | | |
| | | ä¸å表项ç¸åç»æï¼é¢å¤è¿åï¼ |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | |------|------|------| |
| | | | userIds | List\<Long\> | æå®ç¨æ·IDå表ï¼ä»
sendScope=2 æ¶æå¼ï¼ | |
| | | |
| | | **æçæªè¯»æ°é `GET /bpm/oa/notice/my-unread-count` ååºï¼** |
| | | |
| | | ```json |
| | | { |
| | | "code": 0, |
| | | "data": 5 |
| | | } |
| | | ``` |
| | | |
| | | ## åæ®µå±ç¤ºè§å |
| | | |
| | | | åæ®µ | å表 | 详æ
| 说æ | |
| | | |------|------|------|------| |
| | | | title | â
| â
| éç¥å
¬åæ é¢ï¼ç½®é¡¶é¡¹åå `[置顶]` æ è¯ | |
| | | | type | â
| â
| 1=éç¥ 2=å
¬åï¼ç¨ Tag åºåé¢è² | |
| | | | status | â
| â
| 0=è稿ï¼ç°è²ï¼1=å·²åå¸ï¼ç»¿è²ï¼2=å·²æ¤éï¼çº¢è²ï¼| |
| | | | sendScope | â
| â
| 1=éç¥ææäºº 2=æå®äºº | |
| | | | isTop | â
| â
| 置顶项æå¨åé¢ | |
| | | | publishUserId | - | â
| åå¸äººå§åï¼éå
³èç¨æ·è¡¨å±ç¤ºï¼ | |
| | | | publishTime | â
| â
| å叿¶é´ | |
| | | | createTime | â
| â
| å建æ¶é´ | |
| | | | content | - | â
| 坿æ¬å
容 | |
| | | | userIds | - | â
| ä»
sendScope=2 æ¶å±ç¤ºæå®äººåå表 | |
| | | |
| | | ## ä¸å¡è§å说æ |
| | | |
| | | | åºæ¯ | è§å | |
| | | |------|------| |
| | | | å建æ¶éæ©ãéç¥ææäººã | ææç»å½ç¨æ·å¨å表页å¯è§æ¤å
Œ | |
| | | | å建æ¶éæ©ãæå®äººãä¸ç¶æä¸ºãå·²åå¸ã | ä»
å建è
+被æå®ç¨æ·å¯è§ï¼ç³»ç»èªå¨ç»æå®äººåéç«å
ä¿¡ | |
| | | | å建æ¶ç¶æä¸ºãè稿ã | ä»
å建è
æ¬äººå¯è§ï¼ä¸åééç¥ | |
| | | | ç¹å»è¯¦æ
| èªå¨æ è®°å½åç¨æ·ã已读ãï¼å次æå¼ä¸éå¤è®°å½ | |
| | | | å表æåº | æ isTop éåºï¼åæ id éåº | |
| | | | ä¿®æ¹å
¬å | 妿忥æ¯è稿æ¹ä¸ºå·²åå¸ï¼ä¼è§¦åå叿¶é´æ´æ°åéç¥åé | |
| | | | æ¤éå
¬å | å°ç¶ææ¹ä¸ºãå·²æ¤éãåï¼å表ä»å¯è§ä½æ¾ç¤ºä¸ºæ¤éç¶æ | |
| | | | éééç¥ | æå®äººæ¨¡å¼å叿¶åç«å
ä¿¡ï¼æ¨¡æ¿ code: `oa-notice-publish`ï¼ï¼å¤´é¨éé坿¾ç¤ºæªè¯»æ°ï¼éç¥ææäººæ¨¡å¼ä¸éæ¡æ¨é | |
| | | |
| | | ## API å°è£
åè |
| | | |
| | | æ°å»º `src/api/bpm/oa/notice/index.ts`ï¼åç
§ `src/api/bpm/oa/leave/index.ts` 模å¼ï¼ |
| | | |
| | | ```typescript |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace OaNoticeApi { |
| | | export interface NoticeVO { |
| | | id: number; |
| | | title: string; |
| | | content: string; |
| | | type: number; |
| | | status: number; |
| | | sendScope: number; |
| | | isTop: number; |
| | | publishUserId: number; |
| | | publishTime: string; |
| | | createTime: string; |
| | | userIds?: number[]; |
| | | } |
| | | |
| | | export interface NoticeSaveReqVO { |
| | | id?: number; |
| | | title: string; |
| | | content: string; |
| | | type: number; |
| | | status: number; |
| | | sendScope: number; |
| | | isTop?: number; |
| | | userIds?: number[]; |
| | | } |
| | | } |
| | | |
| | | const BASE = '/bpm/oa/notice'; |
| | | |
| | | export function getNoticePage(params: any) { |
| | | return requestClient.get(`${BASE}/page`, { params }); |
| | | } |
| | | |
| | | export function getNotice(id: number) { |
| | | return requestClient.get(`${BASE}/get`, { params: { id } }); |
| | | } |
| | | |
| | | export function createNotice(data: OaNoticeApi.NoticeSaveReqVO) { |
| | | return requestClient.post(`${BASE}/create`, data); |
| | | } |
| | | |
| | | export function updateNotice(data: OaNoticeApi.NoticeSaveReqVO) { |
| | | return requestClient.put(`${BASE}/update`, data); |
| | | } |
| | | |
| | | export function deleteNotice(id: number) { |
| | | return requestClient.delete(`${BASE}/delete`, { params: { id } }); |
| | | } |
| | | |
| | | export function getMyUnreadNoticeCount() { |
| | | return requestClient.get(`${BASE}/my-unread-count`); |
| | | } |
| | | ``` |
| | | |
| | | ## é¡µé¢æä»¶å»ºè®® |
| | | |
| | | | æä»¶ | è·¯å¾ | |
| | | |------|------| |
| | | | å表页 | `src/views/bpm/oa/notice/index.vue` | |
| | | | 详æ
页 | `src/views/bpm/oa/notice/detail.vue` | |
| | | | 表åå¼¹çª | `src/views/bpm/oa/notice/modules/form.vue` | |
| | | | å表é
ç½® | `src/views/bpm/oa/notice/data.ts` | |
| | | |
| | | ## 注æäºé¡¹ |
| | | |
| | | - å表页çå¯è§æ°æ®ç±å端æ sendScope + å½åç¨æ·èªå¨è¿æ»¤ï¼å端æ éé¢å¤å¤ç |
| | | - å建/ç¼è¾è¡¨åä¸ï¼ãæå®äººãéæ©å¨ä»
å¨ sendScope=2 æ¶æ¾ç¤º |
| | | - 表åæäº¤æ¶ status å¯ç´æ¥éæ©ãå·²åå¸ãï¼æ¤æ¶ä¼ç«å³è§¦åéç¥åé |
| | | - å·²è¯»ç¶æç±å端å¨è°ç¨ get æ¥å£æ¶èªå¨æ è®°ï¼å端æ éè°ç¨æ 记已读æ¥å£ |
| | | - 头é¨ééçæªè¯»æ°å¦æè½è·å `getUnreadNotifyMessageCount`ï¼å¯ä»¥å å `getMyUnreadNoticeCount` çç»æå±ç¤º |
| | | - å端èåéè¦é
ç½® `component` åæ®µä¸º `bpm/oa/notice/index`ï¼æéæ è¯åå«ä¸º `bpm:oa-notice:create/update/delete/query` |
| | | - åèç°æé¡µé¢ï¼å表页åç
§ `src/views/system/notice/index.vue`ï¼è¡¨åå¼¹çªåç
§ `src/views/bpm/oa/leave/create.vue` |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | æ¥ä»·ååç§°: åé客æ·é¢è¯éç»çº¿éè´æ¥ä»· |
| | | 客æ·åç§°: åéå®¢æ· |
| | | æ¥ä»·æ¥æ: 2026-07-31 |
| | | æææè³: 2026-08-30 |
| | | ç¨ç: 13 |
| | | ææ£ç: 5 |
| | | 夿³¨: å«ç¨å«è¿è´¹ï¼30天账æ |
| | | |
| | | åºå·,ç©æåç§°,è§æ ¼åå·,æ°é,åä»·(å
) |
| | | 1,éæ,1A60,500,25.5 |
| | | 2,éå线,L-1.72,300,18.2 |
| | | 3,é¢è¯éç»çº¿,JL/L1-40/10-6/1,2000,8.8 |
| | | 4,ééé¢çº¿,JG3A-1*1.5,800,6.5 |
| | | 5,éå导ä½,LH8030JY-10,1500,15.3 |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | =========================================== |
| | | çµçº¿çµç¼æ¥ä»·å |
| | | =========================================== |
| | | |
| | | æ¥ä»·æ¥æï¼2026å¹´7æ31æ¥ |
| | | æææè³ï¼2026å¹´9æ15æ¥ |
| | | |
| | | 客æ·åç§°ï¼æµè¯ |
| | | è系人çµè¯ï¼13598585685 |
| | | |
| | | ------------------------------------------- |
| | | ä¸ãæ¥ä»·è¯´æ |
| | | ------------------------------------------- |
| | | 1. æ¬æ¥ä»·å«13%å¢å¼ç¨ |
| | | 2. æ¥ä»·æææ45天 |
| | | 3. 仿¬¾æ¹å¼ï¼æç»30天 |
| | | 4. 交货æï¼10ä¸ªå·¥ä½æ¥ |
| | | |
| | | ------------------------------------------- |
| | | äºãæ¥ä»·æç» |
| | | ------------------------------------------- |
| | | ç©æåç§° è§æ ¼åå· æ°é åä»·(å
) |
| | | ------------------------------------------------------ |
| | | éæ 1A60 1000åå
25.50 |
| | | éå线 L-1.72 500åå
18.20 |
| | | é¢è¯éç»çº¿ JL/L1-40/10-6/1 3000åå
8.80 |
| | | ééé¢çº¿ JG3A-1*1.5 1200åå
6.50 |
| | | éåå¯¼ä½ LH8030JY-10 800åç±³ 15.30 |
| | | |
| | | ------------------------------------------- |
| | | ä¸ã夿³¨ |
| | | ------------------------------------------- |
| | | 1. æ´ä½ææ£çï¼5% |
| | | 2. 以ä¸ä»·æ ¼å«ç¨å«è¿è´¹ |
| | | 3. è´¨ä¿æ12个æ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | çæä¸å¼ 微信è天æªå¾ï¼å
容å¦ä¸ï¼ |
| | | |
| | | 对æ¹ï¼å®¢æ·æ²éï¼ï¼ |
| | | "æç»çï¼è¿æ¯è¿æ¬¡è¦éè´çæ¸
åï¼éº»ç¦ç»ä¸ªæ¥ä»·å§" |
| | | "ä¸å
±5ç§" |
| | | "éæï¼è§æ ¼1A60ï¼è¦1000åå
" |
| | | "éå线ï¼è§æ ¼L-1.72ï¼è¦500åå
" |
| | | "é¢è¯éç»çº¿ï¼è§æ ¼JL/L1-40/10-6/1ï¼è¦3000åå
" |
| | | "ééé¢çº¿ï¼è§æ ¼JG3A-1*1.5ï¼è¦1200åå
" |
| | | "éå导ä½ï¼è§æ ¼LH8030JY-10ï¼è¦800åç±³" |
| | | |
| | | èªå·±ï¼ææ¹éå®ï¼ï¼ |
| | | "æ¶å°å¼ æ»ï¼ææ´çä¸ä¸æå¤©ç»æ¨åºæ£å¼æ¥ä»·å" |
| | | "ç¨ç13%ï¼æ´ä½ç»æ¨æ95æï¼å«ç¨å«è¿è´¹" |
| | | "æææå°9æ15å·" |
| | |
| | | <module>yudao-module-aftersales-api</module> |
| | | <module>yudao-module-aftersales</module> |
| | | <!-- 请åè https://doc.iocoder.cn/ai/build/ ææ¡£ï¼å®æ AI 模åçå¯å¨ï¼ï¼ï¼ --> |
| | | <!-- <module>yudao-module-ai</module>--> |
| | | <module>yudao-module-ai-api</module> |
| | | <module>yudao-module-ai</module> |
| | | </modules> |
| | | |
| | | <name>${project.artifactId}</name> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ============================================= |
| | | -- OA éç¥å
¬å模ååå§å |
| | | -- ============================================= |
| | | |
| | | CREATE TABLE IF NOT EXISTS oa_notice ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主é®ID', |
| | | title VARCHAR(50) NOT NULL COMMENT 'å
¬åæ é¢', |
| | | content TEXT NOT NULL COMMENT 'å
¬åå
容', |
| | | type TINYINT NOT NULL COMMENT 'å
¬åç±»åï¼1éç¥ 2å
¬åï¼', |
| | | status TINYINT NOT NULL DEFAULT 0 COMMENT 'ç¶æï¼0è稿 1å·²åå¸ 2å·²æ¤éï¼', |
| | | send_scope TINYINT NOT NULL COMMENT 'åéèå´ï¼1éç¥ææäºº 2æå®äººï¼', |
| | | is_top TINYINT NOT NULL DEFAULT 0 COMMENT 'æ¯å¦ç½®é¡¶ï¼0å¦ 1æ¯ï¼', |
| | | publish_user_id BIGINT COMMENT 'åå¸äººID', |
| | | publish_time DATETIME COMMENT 'å叿¶é´', |
| | | creator VARCHAR(64) DEFAULT '' COMMENT 'å建è
', |
| | | create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | updater VARCHAR(64) DEFAULT '' COMMENT 'æ´æ°è
', |
| | | update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | deleted BIT DEFAULT 0 COMMENT 'æ¯å¦å é¤', |
| | | INDEX idx_type (type), |
| | | INDEX idx_status (status), |
| | | INDEX idx_publish_time (publish_time), |
| | | INDEX idx_is_top (is_top) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='OAéç¥å
¬å表'; |
| | | |
| | | CREATE TABLE IF NOT EXISTS oa_notice_user ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主é®ID', |
| | | notice_id BIGINT NOT NULL COMMENT 'éç¥å
ŒID', |
| | | user_id BIGINT NOT NULL COMMENT 'æå®ç¨æ·ID', |
| | | creator VARCHAR(64) DEFAULT '' COMMENT 'å建è
', |
| | | create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | updater VARCHAR(64) DEFAULT '' COMMENT 'æ´æ°è
', |
| | | update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | deleted BIT DEFAULT 0 COMMENT 'æ¯å¦å é¤', |
| | | INDEX idx_notice_id (notice_id), |
| | | INDEX idx_user_id (user_id) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='OAéç¥å
¬åæå®äººå
³è表'; |
| | | |
| | | CREATE TABLE IF NOT EXISTS oa_notice_read ( |
| | | id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主é®ID', |
| | | notice_id BIGINT NOT NULL COMMENT 'éç¥å
ŒID', |
| | | user_id BIGINT NOT NULL COMMENT 'ç¨æ·ID', |
| | | read_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'é
读æ¶é´', |
| | | creator VARCHAR(64) DEFAULT '' COMMENT 'å建è
', |
| | | create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'å建æ¶é´', |
| | | updater VARCHAR(64) DEFAULT '' COMMENT 'æ´æ°è
', |
| | | update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'æ´æ°æ¶é´', |
| | | deleted BIT DEFAULT 0 COMMENT 'æ¯å¦å é¤', |
| | | UNIQUE KEY uk_notice_user (notice_id, user_id), |
| | | INDEX idx_notice_id (notice_id), |
| | | INDEX idx_user_id (user_id) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='OAéç¥å
¬å已读记å½è¡¨'; |
| | | |
| | | -- ç³»ç»éç¥æ¨¡æ¿ï¼ç¨äºOAéç¥å
¬åå叿¶åéç«å
ä¿¡ï¼ |
| | | INSERT INTO system_notify_template (name, code, nickname, content, type, params, status, creator, create_time, updater, update_time, deleted) |
| | | VALUES ('OAéç¥å
¬åæé', 'oa-notice-publish', 'ç³»ç»éç¥', 'æ¨æä¸æ¡æ°çéç¥å
¬åï¼{title}', 1, '["title"]', 0, '', NOW(), '', NOW(), 0) |
| | | ON DUPLICATE KEY UPDATE id = id; |
| | |
| | | @Schema(description = "ååç¼å·", example = "1") |
| | | private Long contractId; |
| | | |
| | | @Schema(description = "é®é¢ç±»å: 1-ä¸è¬é®é¢ 2-ç»´ä¿®é®é¢ 3-éè´§é®é¢", example = "1") |
| | | private Integer issueType; |
| | | |
| | | @Schema(description = "é®é¢åç±»", example = "å¤è§ç¼ºé·") |
| | | private String issueCategory; |
| | | |
| | | @Schema(description = "é®é¢æè¿°", example = "æ¶å°è´§ååç°äº§åè¡¨é¢æåç") |
| | | private String issueDescription; |
| | | |
| | | @Schema(description = "严éç¨åº¦: 0-轻微 1-ä¸è¬ 2-严é 3-è´å½", example = "1") |
| | | private Integer severityLevel; |
| | | |
| | | @Schema(description = "夿³¨", example = "客æ·è¦æ±å°½å¿«å¤ç") |
| | | private String remark; |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <project xmlns="http://maven.apache.org/POM/4.0.0" |
| | | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| | | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
| | | <parent> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao</artifactId> |
| | | <version>${revision}</version> |
| | | </parent> |
| | | <modelVersion>4.0.0</modelVersion> |
| | | <artifactId>yudao-module-ai-api</artifactId> |
| | | <packaging>jar</packaging> |
| | | |
| | | <name>${project.artifactId}</name> |
| | | <description> |
| | | ai 模åä¸ï¼AI è½åç API æ¥å£å®ä¹ï¼ä¾å
¶ä»æ¨¡åå¼ç¨ |
| | | </description> |
| | | |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao-common</artifactId> |
| | | </dependency> |
| | | |
| | | <!-- Swagger 注解 --> |
| | | <dependency> |
| | | <groupId>org.springdoc</groupId> |
| | | <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> |
| | | <scope>provided</scope> |
| | | </dependency> |
| | | </dependencies> |
| | | |
| | | </project> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ai.api.chat; |
| | | |
| | | /** |
| | | * AI å¯¹è¯ APIï¼ä¾å
¶ä»æ¨¡åè°ç¨ |
| | | */ |
| | | public interface AiChatApi { |
| | | |
| | | /** |
| | | * åé对è¯è¯·æ±ï¼çº¯ææ¬ï¼ |
| | | * |
| | | * @param systemPrompt ç³»ç»æç¤ºè¯ï¼è§è²è®¾å®ï¼ |
| | | * @param userMessage ç¨æ·æ¶æ¯ |
| | | * @return AI åå¤å
容 |
| | | */ |
| | | String chat(String systemPrompt, String userMessage); |
| | | |
| | | /** |
| | | * åé带å¾çç对è¯è¯·æ±ï¼å¤æ¨¡æè§è§è¯å«ï¼ |
| | | * |
| | | * @param systemPrompt ç³»ç»æç¤ºè¯ï¼è§è²è®¾å®ï¼ |
| | | * @param userMessage ç¨æ·æ¶æ¯ |
| | | * @param imageBase64 å¾ç base64 ç¼ç |
| | | * @param mimeType å¾ç MIME ç±»åï¼å¦ image/pngï¼ |
| | | * @return AI åå¤å
容 |
| | | */ |
| | | String chatWithImage(String systemPrompt, String userMessage, String imageBase64, String mimeType); |
| | | |
| | | } |
| | |
| | | <dependencies> |
| | | <dependency> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao-module-ai-api</artifactId> |
| | | <version>${revision}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao-module-system</artifactId> |
| | | <version>${revision}</version> |
| | | </dependency> |
| | |
| | | <artifactId>spring-ai-starter-model-stability-ai</artifactId> |
| | | <version>${spring-ai.version}</version> |
| | | </dependency> |
| | | <dependency> |
| | | <!-- éä¹åé® --> |
| | | <groupId>com.alibaba.cloud.ai</groupId> |
| | | <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> |
| | | <version>${alibaba-ai.version}</version> |
| | | </dependency> |
| | | |
| | | <!-- åéåå¨ï¼https://db-engines.com/en/ranking/vector+dbms --> |
| | | <dependency> |
| | | <!-- Qdrantï¼https://qdrant.tech/ --> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ai.api.chat; |
| | | |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; |
| | | import cn.iocoder.yudao.module.ai.enums.model.AiModelTypeEnum; |
| | | import cn.iocoder.yudao.module.ai.service.model.AiModelService; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.ai.chat.messages.SystemMessage; |
| | | import org.springframework.ai.chat.messages.UserMessage; |
| | | import org.springframework.ai.chat.model.ChatModel; |
| | | import org.springframework.ai.chat.prompt.Prompt; |
| | | import org.springframework.ai.content.Media; |
| | | import org.springframework.core.io.ByteArrayResource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.MimeTypeUtils; |
| | | |
| | | import java.util.Base64; |
| | | import java.util.List; |
| | | |
| | | @Service |
| | | @Slf4j |
| | | public class AiChatApiImpl implements AiChatApi { |
| | | |
| | | @Resource |
| | | private AiModelService modelService; |
| | | |
| | | @Override |
| | | public String chat(String systemPrompt, String userMessage) { |
| | | AiModelDO defaultModel = modelService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType()); |
| | | ChatModel chatModel = modelService.getChatModel(defaultModel.getId()); |
| | | Prompt prompt = new Prompt(List.of( |
| | | new SystemMessage(systemPrompt), |
| | | new UserMessage(userMessage))); |
| | | String result = chatModel.call(prompt).getResult().getOutput().getText(); |
| | | log.info("AI Chat è°ç¨å®æï¼æ¨¡å: {}, åå¤é¿åº¦: {}", defaultModel.getModel(), |
| | | result != null ? result.length() : 0); |
| | | return result; |
| | | } |
| | | |
| | | @Override |
| | | public String chatWithImage(String systemPrompt, String userMessage, String imageBase64, String mimeType) { |
| | | // ä¼å
éæ©è§è§æ¨¡åï¼æ¨¡ååå« vlï¼ï¼å¦ååéå°é»è®¤å¯¹è¯æ¨¡å |
| | | List<AiModelDO> chatModels = modelService.getModelListByStatusAndType( |
| | | CommonStatusEnum.ENABLE.getStatus(), AiModelTypeEnum.CHAT.getType(), null); |
| | | AiModelDO visionModel = chatModels.stream() |
| | | .filter(m -> m.getModel() != null && m.getModel().contains("vl")) |
| | | .findFirst() |
| | | .orElseGet(() -> modelService.getRequiredDefaultModel(AiModelTypeEnum.CHAT.getType())); |
| | | |
| | | ChatModel chatModel = modelService.getChatModel(visionModel.getId()); |
| | | byte[] imageBytes = Base64.getDecoder().decode(imageBase64); |
| | | Media media = new Media(MimeTypeUtils.parseMimeType(mimeType), |
| | | new ByteArrayResource(imageBytes)); |
| | | UserMessage imageMessage = UserMessage.builder() |
| | | .text(userMessage) |
| | | .media(List.of(media)) |
| | | .build(); |
| | | Prompt prompt = new Prompt(List.of( |
| | | new SystemMessage(systemPrompt), |
| | | imageMessage)); |
| | | String result = chatModel.call(prompt).getResult().getOutput().getText(); |
| | | log.info("AI Chat(夿¨¡æ) è°ç¨å®æï¼æ¨¡å: {}, åå¤é¿åº¦: {}", visionModel.getModel(), |
| | | result != null ? result.length() : 0); |
| | | return result; |
| | | } |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.knowledge; |
| | | |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentCreateReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeRespVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; |
| | | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService; |
| | | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| | | |
| | | @Tag(name = "管çåå° - AI ç¥è¯åº") |
| | | @RestController |
| | | @RequestMapping("/ai/knowledge") |
| | | @Validated |
| | | public class AiKnowledgeController { |
| | | |
| | | @Resource |
| | | private AiKnowledgeService knowledgeService; |
| | | @Resource |
| | | private AiKnowledgeDocumentService documentService; |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "å建ç¥è¯åº") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:create')") |
| | | public CommonResult<Long> createKnowledge(@Valid @RequestBody AiKnowledgeSaveReqVO createReqVO) { |
| | | return success(knowledgeService.createKnowledge(createReqVO)); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Operation(summary = "æ´æ°ç¥è¯åº") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:update')") |
| | | public CommonResult<Boolean> updateKnowledge(@Valid @RequestBody AiKnowledgeSaveReqVO updateReqVO) { |
| | | knowledgeService.updateKnowledge(updateReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "å é¤ç¥è¯åº") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:delete')") |
| | | public CommonResult<Boolean> deleteKnowledge(@RequestParam("id") Long id) { |
| | | knowledgeService.deleteKnowledge(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·åç¥è¯åº") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<AiKnowledgeRespVO> getKnowledge(@RequestParam("id") Long id) { |
| | | return success(BeanUtils.toBean(knowledgeService.getKnowledge(id), AiKnowledgeRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·åç¥è¯åºå页") |
| | |
| | | return success(BeanUtils.toBean(pageResult, AiKnowledgeRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·å¾ç¥è¯åº") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true, example = "1024") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<AiKnowledgeRespVO> getKnowledge(@RequestParam("id") Long id) { |
| | | AiKnowledgeDO knowledge = knowledgeService.getKnowledge(id); |
| | | return success(BeanUtils.toBean(knowledge, AiKnowledgeRespVO.class)); |
| | | } |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "å建ç¥è¯åº") |
| | | @PostMapping("/document/create") |
| | | @Operation(summary = "åå»ºææ¡£") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:create')") |
| | | public CommonResult<Long> createKnowledge(@RequestBody @Valid AiKnowledgeSaveReqVO createReqVO) { |
| | | return success(knowledgeService.createKnowledge(createReqVO)); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Operation(summary = "æ´æ°ç¥è¯åº") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:update')") |
| | | public CommonResult<Boolean> updateKnowledge(@RequestBody @Valid AiKnowledgeSaveReqVO updateReqVO) { |
| | | knowledgeService.updateKnowledge(updateReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "å é¤ç¥è¯åº") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true, example = "1024") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:delete')") |
| | | public CommonResult<Boolean> deleteKnowledge(@RequestParam("id") Long id) { |
| | | knowledgeService.deleteKnowledge(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/simple-list") |
| | | @Operation(summary = "è·å¾ç¥è¯åºçç²¾ç®å表") |
| | | public CommonResult<List<AiKnowledgeRespVO>> getKnowledgeSimpleList() { |
| | | List<AiKnowledgeDO> list = knowledgeService.getKnowledgeSimpleListByStatus(CommonStatusEnum.ENABLE.getStatus()); |
| | | return success(convertList(list, knowledge -> new AiKnowledgeRespVO() |
| | | .setId(knowledge.getId()).setName(knowledge.getName()))); |
| | | public CommonResult<List<Long>> createDocument(@Valid @RequestBody AiKnowledgeDocumentCreateReqVO createReqVO) { |
| | | return success(documentService.createDocuments(createReqVO)); |
| | | } |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.*; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; |
| | | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.List; |
| | |
| | | @Tag(name = "管çåå° - AI ç¥è¯åºææ¡£") |
| | | @RestController |
| | | @RequestMapping("/ai/knowledge/document") |
| | | @Validated |
| | | public class AiKnowledgeDocumentController { |
| | | |
| | | @Resource |
| | | private AiKnowledgeDocumentService documentService; |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·åææ¡£å页") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getKnowledgeDocumentPage( |
| | | @Valid AiKnowledgeDocumentPageReqVO pageReqVO) { |
| | | PageResult<AiKnowledgeDocumentDO> pageResult = documentService.getKnowledgeDocumentPage(pageReqVO); |
| | | return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·åææ¡£è¯¦æ
") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<AiKnowledgeDocumentRespVO> getKnowledgeDocument(@RequestParam("id") Long id) { |
| | | AiKnowledgeDocumentDO document = documentService.getKnowledgeDocument(id); |
| | | return success(BeanUtils.toBean(document, AiKnowledgeDocumentRespVO.class)); |
| | | } |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "æ°å»ºææ¡£ï¼å个ï¼") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:create')") |
| | | public CommonResult<Long> createKnowledgeDocument(@RequestBody @Valid AiKnowledgeDocumentCreateReqVO reqVO) { |
| | | Long id = documentService.createKnowledgeDocument(reqVO); |
| | | return success(id); |
| | | } |
| | | |
| | | @PostMapping("/create-list") |
| | | @Operation(summary = "æ°å»ºææ¡£ï¼å¤ä¸ªï¼") |
| | | @Operation(summary = "åå»ºææ¡£ï¼æ¹éï¼") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:create')") |
| | | public CommonResult<List<Long>> createKnowledgeDocumentList( |
| | | @RequestBody @Valid AiKnowledgeDocumentCreateListReqVO reqVO) { |
| | | List<Long> ids = documentService.createKnowledgeDocumentList(reqVO); |
| | | return success(ids); |
| | | public CommonResult<List<Long>> createDocumentList(@Valid @RequestBody AiKnowledgeDocumentCreateReqVO createReqVO) { |
| | | return success(documentService.createDocuments(createReqVO)); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Operation(summary = "æ´æ°ææ¡£") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:update')") |
| | | public CommonResult<Boolean> updateKnowledgeDocument(@Valid @RequestBody AiKnowledgeDocumentUpdateReqVO reqVO) { |
| | | documentService.updateKnowledgeDocument(reqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/update-status") |
| | | @Operation(summary = "æ´æ°ææ¡£ç¶æ") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:update')") |
| | | public CommonResult<Boolean> updateKnowledgeDocumentStatus( |
| | | @Valid @RequestBody AiKnowledgeDocumentUpdateStatusReqVO reqVO) { |
| | | documentService.updateKnowledgeDocumentStatus(reqVO); |
| | | public CommonResult<Boolean> updateDocument(@Valid @RequestBody AiKnowledgeDocumentUpdateReqVO updateReqVO) { |
| | | documentService.updateDocument(updateReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "å é¤ææ¡£") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:delete')") |
| | | public CommonResult<Boolean> deleteKnowledgeDocument(@RequestParam("id") Long id) { |
| | | documentService.deleteKnowledgeDocument(id); |
| | | public CommonResult<Boolean> deleteDocument(@RequestParam("id") Long id) { |
| | | documentService.deleteDocument(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·åææ¡£") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<AiKnowledgeDocumentRespVO> getDocument(@RequestParam("id") Long id) { |
| | | return success(BeanUtils.toBean(documentService.getDocument(id), AiKnowledgeDocumentRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·åææ¡£å页") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<PageResult<AiKnowledgeDocumentRespVO>> getDocumentPage(@Valid AiKnowledgeDocumentPageReqVO pageReqVO) { |
| | | PageResult<AiKnowledgeDocumentDO> pageResult = documentService.getDocumentPage(pageReqVO); |
| | | return success(BeanUtils.toBean(pageResult, AiKnowledgeDocumentRespVO.class)); |
| | | } |
| | | |
| | | @PutMapping("/update-status") |
| | | @Operation(summary = "æ´æ°ææ¡£ç¶æ") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:update')") |
| | | public CommonResult<Boolean> updateDocumentStatus(@Valid @RequestBody AiKnowledgeDocumentUpdateStatusReqVO updateStatusReqVO) { |
| | | documentService.updateDocumentStatus(updateStatusReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @PostMapping("/process/{id}") |
| | | @Operation(summary = "å¤çææ¡£ï¼å段+åéåï¼") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:update')") |
| | | public CommonResult<Boolean> processDocument(@PathVariable("id") Long id) { |
| | | documentService.processDocumentSegments(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/progress") |
| | | @Operation(summary = "è·åææ¡£å¤çè¿åº¦") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<List<AiKnowledgeSegmentProcessRespVO>> getProcessingProgress(@RequestParam("documentIds") List<Long> documentIds) { |
| | | return success(documentService.getDocumentProcessingProgress(documentIds)); |
| | | } |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.knowledge; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.collection.MapUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.*; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; |
| | | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService; |
| | | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; |
| | | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO; |
| | | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.Parameters; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.hibernate.validator.constraints.URL; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| | | |
| | | @Tag(name = "管çåå° - AI ç¥è¯åºæ®µè½") |
| | | @Tag(name = "管çåå° - AI ç¥è¯åºå段") |
| | | @RestController |
| | | @RequestMapping("/ai/knowledge/segment") |
| | | @Validated |
| | | public class AiKnowledgeSegmentController { |
| | | |
| | | @Resource |
| | |
| | | @Resource |
| | | private AiKnowledgeDocumentService documentService; |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·å段è½è¯¦æ
") |
| | | @Parameter(name = "id", description = "段è½ç¼å·", required = true, example = "1024") |
| | | @GetMapping("/split") |
| | | @Operation(summary = "é¢è§ææ¡£ååç»æ") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<AiKnowledgeSegmentRespVO> getKnowledgeSegment(@RequestParam("id") Long id) { |
| | | AiKnowledgeSegmentDO segment = segmentService.getKnowledgeSegment(id); |
| | | return success(BeanUtils.toBean(segment, AiKnowledgeSegmentRespVO.class)); |
| | | public CommonResult<List<AiKnowledgeSegmentSplitRespVO>> previewSplit(@Valid AiKnowledgeSegmentSplitReqVO reqVO) { |
| | | String fileUrl = reqVO.getUrl(); |
| | | if (fileUrl == null || fileUrl.isEmpty()) { |
| | | throw new IllegalArgumentException("ææ¡£ URL ä¸è½ä¸ºç©º"); |
| | | } |
| | | // ä» URL è·¯å¾ä¸æåæä»¶å |
| | | String name = fileUrl; |
| | | int queryIdx = name.indexOf('?'); |
| | | if (queryIdx > 0) name = name.substring(0, queryIdx); |
| | | int slashIdx = name.lastIndexOf('/'); |
| | | name = slashIdx >= 0 ? name.substring(slashIdx + 1) : name; |
| | | List<String> segments = documentService.previewSplit(fileUrl, name, reqVO.getSegmentMaxTokens()); |
| | | List<AiKnowledgeSegmentSplitRespVO> result = new ArrayList<>(); |
| | | for (String content : segments) { |
| | | result.add(new AiKnowledgeSegmentSplitRespVO() |
| | | .setContent(content) |
| | | .setContentLength(content.length()) |
| | | .setTokens(estimateTokens(content))); |
| | | } |
| | | return success(result); |
| | | } |
| | | |
| | | private int estimateTokens(String text) { |
| | | if (text == null || text.isEmpty()) return 0; |
| | | int chineseChars = 0, englishWords = 0; |
| | | for (char c : text.toCharArray()) { |
| | | if (c >= 0x4E00 && c <= 0x9FA5) chineseChars++; |
| | | } |
| | | for (String word : text.split("\\s+")) { |
| | | if (word.matches(".*[a-zA-Z].*")) englishWords++; |
| | | } |
| | | return chineseChars + (int) (englishWords * 1.3); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·ååæ®µ") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<AiKnowledgeSegmentRespVO> getSegment(@RequestParam("id") Long id) { |
| | | return success(BeanUtils.toBean(segmentService.getSegment(id), AiKnowledgeSegmentRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·å段è½å页") |
| | | @Operation(summary = "è·ååæ®µå页") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage( |
| | | @Valid AiKnowledgeSegmentPageReqVO pageReqVO) { |
| | | PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO); |
| | | public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getSegmentPage(@Valid AiKnowledgeSegmentPageReqVO pageReqVO) { |
| | | PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getSegmentPage(pageReqVO); |
| | | return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class)); |
| | | } |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "å建段è½") |
| | | @Operation(summary = "åå»ºåæ®µ") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:create')") |
| | | public CommonResult<Long> createKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO createReqVO) { |
| | | return success(segmentService.createKnowledgeSegment(createReqVO)); |
| | | public CommonResult<Long> createSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO saveReqVO) { |
| | | return success(segmentService.createSegment(saveReqVO)); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Operation(summary = "æ´æ°æ®µè½å
容") |
| | | @Operation(summary = "æ´æ°å段") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:update')") |
| | | public CommonResult<Boolean> updateKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO reqVO) { |
| | | segmentService.updateKnowledgeSegment(reqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/update-status") |
| | | @Operation(summary = "å¯ç¦ç¨æ®µè½å
容") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:update')") |
| | | public CommonResult<Boolean> updateKnowledgeSegmentStatus( |
| | | @Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) { |
| | | segmentService.updateKnowledgeSegmentStatus(reqVO); |
| | | public CommonResult<Boolean> updateSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO saveReqVO) { |
| | | segmentService.updateSegment(saveReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "å 餿®µè½") |
| | | @Parameter(name = "id", description = "段è½ç¼å·", required = true, example = "1024") |
| | | @Operation(summary = "å é¤å段") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:delete')") |
| | | public CommonResult<Boolean> deleteKnowledgeSegment(@RequestParam("id") Long id) { |
| | | segmentService.deleteKnowledgeSegment(id); |
| | | public CommonResult<Boolean> deleteSegment(@RequestParam("id") Long id) { |
| | | segmentService.deleteSegment(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/split") |
| | | @Operation(summary = "åçå
容") |
| | | @Parameters({ |
| | | @Parameter(name = "url", description = "ææ¡£ URL", required = true), |
| | | @Parameter(name = "segmentMaxTokens", description = "åæ®µçæå¤§ Token æ°", required = true) |
| | | }) |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<List<AiKnowledgeSegmentRespVO>> splitContent( |
| | | @RequestParam("url") @URL String url, |
| | | @RequestParam(value = "segmentMaxTokens") Integer segmentMaxTokens) { |
| | | List<AiKnowledgeSegmentDO> segments = segmentService.splitContent(url, segmentMaxTokens); |
| | | return success(BeanUtils.toBean(segments, AiKnowledgeSegmentRespVO.class)); |
| | | @PutMapping("/update-status") |
| | | @Operation(summary = "æ´æ°åæ®µç¶æ") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:update')") |
| | | public CommonResult<Boolean> updateSegmentStatus(@Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO updateStatusReqVO) { |
| | | segmentService.updateSegmentStatus(updateStatusReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/get-process-list") |
| | | @Operation(summary = "è·åææ¡£å¤çå表") |
| | | @Parameter(name = "documentIds", description = "ææ¡£ç¼å·å表", required = true, example = "1,2,3") |
| | | @Operation(summary = "è·åææ¡£å¤çè¿åº¦") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<List<AiKnowledgeSegmentProcessRespVO>> getKnowledgeSegmentProcessList( |
| | | @RequestParam("documentIds") List<Long> documentIds) { |
| | | List<AiKnowledgeSegmentProcessRespVO> list = segmentService.getKnowledgeSegmentProcessList(documentIds); |
| | | return success(list); |
| | | public CommonResult<List<AiKnowledgeSegmentProcessRespVO>> getProcessList(@RequestParam("documentIds") List<Long> documentIds) { |
| | | return success(documentService.getDocumentProcessingProgress(documentIds)); |
| | | } |
| | | |
| | | @GetMapping("/search") |
| | | @Operation(summary = "æç´¢æ®µè½å
容") |
| | | @PostMapping("/search") |
| | | @Operation(summary = "æ£ç´¢å段ï¼RAG åéæ£ç´¢ï¼") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<List<AiKnowledgeSegmentSearchRespVO>> searchKnowledgeSegment( |
| | | @Valid AiKnowledgeSegmentSearchReqVO reqVO) { |
| | | // 1. æç´¢æ®µè½ |
| | | List<AiKnowledgeSegmentSearchRespBO> segments = segmentService |
| | | .searchKnowledgeSegment(BeanUtils.toBean(reqVO, AiKnowledgeSegmentSearchReqBO.class)); |
| | | if (CollUtil.isEmpty(segments)) { |
| | | return success(Collections.emptyList()); |
| | | } |
| | | |
| | | // 2. æ¼æ¥ VO |
| | | Map<Long, AiKnowledgeDocumentDO> documentMap = documentService.getKnowledgeDocumentMap(convertSet( |
| | | segments, AiKnowledgeSegmentSearchRespBO::getDocumentId)); |
| | | return success(BeanUtils.toBean(segments, AiKnowledgeSegmentSearchRespVO.class, |
| | | segment -> MapUtils.findAndThen(documentMap, segment.getDocumentId(), |
| | | document -> segment.setDocumentName(document.getName())))); |
| | | public CommonResult<List<AiKnowledgeSegmentSearchRespVO>> searchSegments(@Valid @RequestBody AiKnowledgeSegmentSearchReqVO searchReqVO) { |
| | | AiKnowledgeSegmentSearchReqBO searchReqBO = BeanUtils.toBean(searchReqVO, AiKnowledgeSegmentSearchReqBO.class); |
| | | List<AiKnowledgeSegmentSearchRespBO> results = segmentService.searchSegments(searchReqBO); |
| | | return success(results.stream() |
| | | .map(bo -> BeanUtils.toBean(bo, AiKnowledgeSegmentSearchRespVO.class)) |
| | | .collect(Collectors.toList())); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.Valid; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºææ¡£å建 Request VO") |
| | | @Data |
| | | public class AiKnowledgeDocumentCreateReqVO { |
| | | |
| | | @Schema(description = "ç¥è¯åºç¼å·", example = "1") |
| | | @NotNull(message = "ç¥è¯åºä¸è½ä¸ºç©º") |
| | | private Long knowledgeId; |
| | | |
| | | @Schema(description = "åæ®µæå¤§ Token æ°", example = "500") |
| | | private Integer segmentMaxTokens; |
| | | |
| | | @Schema(description = "ææ¡£å表") |
| | | @NotEmpty(message = "ææ¡£å表ä¸è½ä¸ºç©º") |
| | | private List<@Valid DocumentItem> list; |
| | | |
| | | @Data |
| | | public static class DocumentItem { |
| | | |
| | | @Schema(description = "ææ¡£åç§°", example = "产åæå.pdf") |
| | | @NotEmpty(message = "ææ¡£åç§°ä¸è½ä¸ºç©º") |
| | | private String name; |
| | | |
| | | @Schema(description = "ææ¡£ URL ä¿¡æ¯") |
| | | private UrlInfo url; |
| | | } |
| | | |
| | | @Data |
| | | public static class UrlInfo { |
| | | |
| | | @Schema(description = "ææ¡£ URL") |
| | | private String url; |
| | | |
| | | @Schema(description = "ææ¡£åç§°") |
| | | private String name; |
| | | |
| | | @Schema(description = "æä»¶ ID") |
| | | private Long id; |
| | | } |
| | | } |
| | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºææ¡£çå页 Request VO") |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºææ¡£å页 Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class AiKnowledgeDocumentPageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "ç¥è¯åºç¼å·", example = "1") |
| | | private Long knowledgeId; |
| | | |
| | | @Schema(description = "ææ¡£åç§°", example = "Java å¼åæå") |
| | | @Schema(description = "ææ¡£åç§°") |
| | | private String name; |
| | | |
| | | } |
| | |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºææ¡£ Response VO") |
| | | @Data |
| | | public class AiKnowledgeDocumentRespVO { |
| | | |
| | | @Schema(description = "ææ¡£ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") |
| | | @Schema(description = "ç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "ç¥è¯åºç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") |
| | | @Schema(description = "ç¥è¯åºç¼å·", example = "1") |
| | | private Long knowledgeId; |
| | | |
| | | @Schema(description = "ææ¡£åç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java å¼åæå") |
| | | @Schema(description = "ææ¡£åç§°", example = "产åæå") |
| | | private String name; |
| | | |
| | | @Schema(description = "ææ¡£ URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://doc.iocoder.cn") |
| | | @Schema(description = "ææ¡£ URL") |
| | | private String url; |
| | | |
| | | @Schema(description = "ææ¡£å
容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java æ¯ä¸é¨é¢å对象çè¯è¨.....") |
| | | private String content; |
| | | |
| | | @Schema(description = "ææ¡£å
容é¿åº¦", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") |
| | | @Schema(description = "å
容é¿åº¦") |
| | | private Integer contentLength; |
| | | |
| | | @Schema(description = "ææ¡£ Token æ°é", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | @Schema(description = "Token æ°") |
| | | private Integer tokens; |
| | | |
| | | @Schema(description = "åçæå¤§ Token æ°", requiredMode = Schema.RequiredMode.REQUIRED, example = "512") |
| | | @Schema(description = "åæ®µæå¤§ Token æ°") |
| | | private Integer segmentMaxTokens; |
| | | |
| | | @Schema(description = "å¬å次æ°", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| | | @Schema(description = "æ£ç´¢æ¬¡æ°") |
| | | private Integer retrievalCount; |
| | | |
| | | @Schema(description = "ææ¡£ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| | | @Schema(description = "ç¶æ", example = "0") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "å建æ¶é´", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | @Schema(description = "å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | } |
| | |
| | | @Data |
| | | public class AiKnowledgeDocumentUpdateReqVO { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") |
| | | @Schema(description = "ç¼å·", example = "1") |
| | | @NotNull(message = "ç¼å·ä¸è½ä¸ºç©º") |
| | | private Long id; |
| | | |
| | | @Schema(description = "åç§°", example = "Java å¼åæå") |
| | | @Schema(description = "ææ¡£åç§°", example = "产åæå") |
| | | private String name; |
| | | |
| | | @Schema(description = "åçæå¤§ Token æ°", example = "1000") |
| | | private Integer segmentMaxTokens; |
| | | @Schema(description = "ææ¡£ URL") |
| | | private String url; |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document; |
| | | |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | |
| | | @Data |
| | | public class AiKnowledgeDocumentUpdateStatusReqVO { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "15583") |
| | | @Schema(description = "ç¼å·", example = "1") |
| | | @NotNull(message = "ç¼å·ä¸è½ä¸ºç©º") |
| | | private Long id; |
| | | |
| | | @Schema(description = "ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| | | @Schema(description = "ç¶æ", example = "0") |
| | | @NotNull(message = "ç¶æä¸è½ä¸ºç©º") |
| | | @InEnum(CommonStatusEnum.class) |
| | | private Integer status; |
| | | |
| | | } |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge; |
| | | |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| | | |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºçå页 Request VO") |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºå页 Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class AiKnowledgePageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "ç¥è¯åºåç§°", example = "èè¿") |
| | | @Schema(description = "ç¥è¯åºåç§°") |
| | | private String name; |
| | | |
| | | @Schema(description = "æ¯å¦å¯ç¨", example = "1") |
| | | @InEnum(CommonStatusEnum.class) |
| | | @Schema(description = "ç¶æ") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "å建æ¶é´") |
| | |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Schema(description = "管çåå° - AI ç¥è¯åº Response VO") |
| | | @Data |
| | | public class AiKnowledgeRespVO { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") |
| | | @Schema(description = "ç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "ç¥è¯åºåç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro ç¨æ·æå") |
| | | @Schema(description = "ç¥è¯åºåç§°", example = "äº§åææ¡£") |
| | | private String name; |
| | | |
| | | @Schema(description = "ç¥è¯åºæè¿°", example = "帮å©ä½ å¿«éæå»ºç³»ç»") |
| | | @Schema(description = "ç¥è¯åºæè¿°", example = "产åç¸å
³ææ¡£") |
| | | private String description; |
| | | |
| | | @Schema(description = "å鿍¡åç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "14") |
| | | @Schema(description = "å鿍¡åç¼å·", example = "1") |
| | | private Long embeddingModelId; |
| | | |
| | | @Schema(description = "å鿍¡åæ è¯", requiredMode = Schema.RequiredMode.REQUIRED, example = "qwen-72b-chat") |
| | | @Schema(description = "å鿍¡åæ è¯", example = "text-embedding-v3") |
| | | private String embeddingModel; |
| | | |
| | | @Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3") |
| | | @Schema(description = "TopK", example = "3") |
| | | private Integer topK; |
| | | |
| | | @Schema(description = "ç¸ä¼¼åº¦éå¼", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.7") |
| | | @Schema(description = "ç¸ä¼¼åº¦éå¼", example = "0.7") |
| | | private Double similarityThreshold; |
| | | |
| | | @Schema(description = "ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @Schema(description = "ç¶æ", example = "0") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "å建æ¶é´", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | @Schema(description = "å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge; |
| | | |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | |
| | | @Data |
| | | public class AiKnowledgeSaveReqVO { |
| | | |
| | | @Schema(description = "对è¯ç¼å·", example = "1204") |
| | | @Schema(description = "ç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "ç¥è¯åºåç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "ruoyi-vue-pro ç¨æ·æå") |
| | | @NotBlank(message = "ç¥è¯åºåç§°ä¸è½ä¸ºç©º") |
| | | @Schema(description = "ç¥è¯åºåç§°", example = "äº§åææ¡£") |
| | | @NotEmpty(message = "ç¥è¯åºåç§°ä¸è½ä¸ºç©º") |
| | | private String name; |
| | | |
| | | @Schema(description = "ç¥è¯åºæè¿°", requiredMode = Schema.RequiredMode.REQUIRED, example = "åå¨ ruoyi-vue-pro æä½ææ¡£") |
| | | @Schema(description = "ç¥è¯åºæè¿°", example = "产åç¸å
³ææ¡£") |
| | | private String description; |
| | | |
| | | @Schema(description = "å鿍¡åç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @Schema(description = "å鿍¡åç¼å·", example = "1") |
| | | @NotNull(message = "å鿍¡åä¸è½ä¸ºç©º") |
| | | private Long embeddingModelId; |
| | | |
| | | @Schema(description = "topK", requiredMode = Schema.RequiredMode.REQUIRED, example = "3") |
| | | @NotNull(message = "topK ä¸è½ä¸ºç©º") |
| | | @Schema(description = "TopK", example = "3") |
| | | private Integer topK; |
| | | |
| | | @Schema(description = "ç¸ä¼¼æ§éå¼", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5") |
| | | @NotNull(message = "ç¸ä¼¼æ§éå¼ä¸è½ä¸ºç©º") |
| | | @Schema(description = "ç¸ä¼¼åº¦éå¼", example = "0.7") |
| | | private Double similarityThreshold; |
| | | |
| | | @Schema(description = "æ¯å¦å¯ç¨", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "æ¯å¦å¯ç¨ä¸è½ä¸ºç©º") |
| | | @InEnum(CommonStatusEnum.class) |
| | | @Schema(description = "ç¶æ", example = "0") |
| | | private Integer status; |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment; |
| | | |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºå段çå页 Request VO") |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºå段å页 Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class AiKnowledgeSegmentPageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "ææ¡£ç¼å·", example = "1") |
| | | private Long documentId; |
| | | |
| | | @Schema(description = "åæ®µå
容å
³é®å", example = "Java å¼å") |
| | | @Schema(description = "åæ®µå
容") |
| | | private String content; |
| | | |
| | | @Schema(description = "åæ®µç¶æ", example = "1") |
| | | @InEnum(CommonStatusEnum.class) |
| | | @Schema(description = "ç¶æ") |
| | | private Integer status; |
| | | |
| | | } |
| | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºæ®µè½åéè¿åº¦ Response VO") |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºå段å¤çè¿åº¦ Response VO") |
| | | @Data |
| | | public class AiKnowledgeSegmentProcessRespVO { |
| | | |
| | | @Schema(description = "ææ¡£ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @Schema(description = "ææ¡£ç¼å·", example = "1") |
| | | private Long documentId; |
| | | |
| | | @Schema(description = "æ»æ®µè½æ°é", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| | | @Schema(description = "åæ®µæ»æ°") |
| | | private Long count; |
| | | |
| | | @Schema(description = "å·²åéåæ®µè½æ°é", requiredMode = Schema.RequiredMode.REQUIRED, example = "5") |
| | | @Schema(description = "å·²åéåæ°é") |
| | | private Long embeddingCount; |
| | | |
| | | } |
| | | } |
| | |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºææ¡£åç Response VO") |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºå段 Response VO") |
| | | @Data |
| | | public class AiKnowledgeSegmentRespVO { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") |
| | | @Schema(description = "ç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "ææ¡£ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") |
| | | private Long documentId; |
| | | |
| | | @Schema(description = "ç¥è¯åºç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") |
| | | @Schema(description = "ç¥è¯åºç¼å·", example = "1") |
| | | private Long knowledgeId; |
| | | |
| | | @Schema(description = "åéåºç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1858496a-1dde-4edf-a43e-0aed08f37f8c") |
| | | private String vectorId; |
| | | @Schema(description = "ææ¡£ç¼å·", example = "1") |
| | | private Long documentId; |
| | | |
| | | @Schema(description = "åçå
容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java å¼åæå") |
| | | @Schema(description = "åæ®µå
容") |
| | | private String content; |
| | | |
| | | @Schema(description = "åçå
容é¿åº¦", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | @Schema(description = "å
容é¿åº¦") |
| | | private Integer contentLength; |
| | | |
| | | @Schema(description = "token æ°é", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | @Schema(description = "åé ID") |
| | | private String vectorId; |
| | | |
| | | @Schema(description = "Token æ°") |
| | | private Integer tokens; |
| | | |
| | | @Schema(description = "å¬å次æ°", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") |
| | | @Schema(description = "æ£ç´¢æ¬¡æ°") |
| | | private Integer retrievalCount; |
| | | |
| | | @Schema(description = "ææ¡£ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @Schema(description = "ç¶æ", example = "0") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "å建æ¶é´", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private Long createTime; |
| | | @Schema(description = "å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | } |
| | |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - AI æ°å¢/ä¿®æ¹ç¥è¯åºæ®µè½ request VO") |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºå段æ°å¢/ä¿®æ¹ Request VO") |
| | | @Data |
| | | public class AiKnowledgeSegmentSaveReqVO { |
| | | |
| | | @Schema(description = "ç¼å·", example = "24790") |
| | | @Schema(description = "ç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "ç¥è¯åºææ¡£ç¼å·", example = "1024") |
| | | @Schema(description = "ç¥è¯åºç¼å·ï¼å建æ¶å¿
å¡«ï¼æ´æ°æ¶å¯ä»å·²æå段è·åï¼", example = "1") |
| | | private Long knowledgeId; |
| | | |
| | | @Schema(description = "ææ¡£ç¼å·ï¼å建æ¶å¿
å¡«ï¼æ´æ°æ¶å¯ä»å·²æå段è·åï¼", example = "1") |
| | | private Long documentId; |
| | | |
| | | @Schema(description = "åçå
容", requiredMode = Schema.RequiredMode.REQUIRED, example = "Java å¼åæå") |
| | | @NotEmpty(message = "åçå
容ä¸è½ä¸ºç©º") |
| | | @Schema(description = "åæ®µå
容") |
| | | @NotEmpty(message = "åæ®µå
容ä¸è½ä¸ºç©º") |
| | | private String content; |
| | | |
| | | @Schema(description = "ç¶æ", example = "0") |
| | | private Integer status; |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºæ®µè½æç´¢ Request VO") |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºå段æ£ç´¢ Request VO") |
| | | @Data |
| | | public class AiKnowledgeSegmentSearchReqVO { |
| | | |
| | | @Schema(description = "ç¥è¯åºç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | @Schema(description = "ç¥è¯åºç¼å·", example = "1") |
| | | @NotNull(message = "ç¥è¯åºç¼å·ä¸è½ä¸ºç©º") |
| | | private Long knowledgeId; |
| | | |
| | | @Schema(description = "å
容", requiredMode = Schema.RequiredMode.REQUIRED, example = "å¦ä½ä½¿ç¨è¿ä¸ªäº§å") |
| | | @NotEmpty(message = "å
容ä¸è½ä¸ºç©º") |
| | | @Schema(description = "æ£ç´¢å
容") |
| | | @NotEmpty(message = "æ£ç´¢å
容ä¸è½ä¸ºç©º") |
| | | private String content; |
| | | |
| | | @Schema(description = "æå¤§è¿åæ°é", example = "5") |
| | | @Schema(description = "è¿åæ°é", example = "3") |
| | | private Integer topK; |
| | | |
| | | @Schema(description = "ç¸ä¼¼åº¦éå¼", example = "0.7") |
| | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºæ®µè½æç´¢ Response VO") |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºå段æ£ç´¢ Response VO") |
| | | @Data |
| | | public class AiKnowledgeSegmentSearchRespVO extends AiKnowledgeSegmentRespVO { |
| | | public class AiKnowledgeSegmentSearchRespVO { |
| | | |
| | | @Schema(description = "ææ¡£åç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "产åä½¿ç¨æå") |
| | | @Schema(description = "åæ®µç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "ææ¡£ç¼å·", example = "1") |
| | | private Long documentId; |
| | | |
| | | @Schema(description = "ææ¡£åç§°", example = "xxx.pdf") |
| | | private String documentName; |
| | | |
| | | @Schema(description = "ç¸ä¼¼åº¦åæ°", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.95") |
| | | @Schema(description = "ç¥è¯åºç¼å·", example = "1") |
| | | private Long knowledgeId; |
| | | |
| | | @Schema(description = "åæ®µå
容") |
| | | private String content; |
| | | |
| | | @Schema(description = "å
容é¿åº¦") |
| | | private Integer contentLength; |
| | | |
| | | @Schema(description = "Token æ°") |
| | | private Integer tokens; |
| | | |
| | | @Schema(description = "ç¸ä¼¼åº¦åæ°", example = "0.85") |
| | | private Double score; |
| | | |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºææ¡£ååé¢è§ Request VO") |
| | | @Data |
| | | public class AiKnowledgeSegmentSplitReqVO { |
| | | |
| | | @Schema(description = "åæ®µæå¤§ Token æ°", example = "500") |
| | | private Integer segmentMaxTokens; |
| | | |
| | | @Schema(description = "ææ¡£ä¸è½½ URLï¼å®æ´çæä»¶å°åï¼") |
| | | private String url; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºææ¡£ååé¢è§ Response VO") |
| | | @Data |
| | | public class AiKnowledgeSegmentSplitRespVO { |
| | | |
| | | @Schema(description = "åæ®µææ¬") |
| | | private String content; |
| | | |
| | | @Schema(description = "å
容é¿åº¦") |
| | | private Integer contentLength; |
| | | |
| | | @Schema(description = "ä¼°ç® Token æ°") |
| | | private Integer tokens; |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment; |
| | | |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºæ®µè½çæ´æ°ç¶æ Request VO") |
| | | @Schema(description = "管çåå° - AI ç¥è¯åºåæ®µæ´æ°ç¶æ Request VO") |
| | | @Data |
| | | public class AiKnowledgeSegmentUpdateStatusReqVO { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "24790") |
| | | @Schema(description = "ç¼å·", example = "1") |
| | | @NotNull(message = "ç¼å·ä¸è½ä¸ºç©º") |
| | | private Long id; |
| | | |
| | | @Schema(description = "æ¯å¦å¯ç¨", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "æ¯å¦å¯ç¨ä¸è½ä¸ºç©º") |
| | | @InEnum(CommonStatusEnum.class) |
| | | @Schema(description = "ç¶æ", example = "0") |
| | | @NotNull(message = "ç¶æä¸è½ä¸ºç©º") |
| | | private Integer status; |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyRespVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelRespVO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; |
| | | import cn.iocoder.yudao.module.ai.service.model.AiApiKeyService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| | | |
| | | @Tag(name = "管çåå° - AI API å¯é¥") |
| | | @RestController |
| | | @RequestMapping("/ai/api-key") |
| | | @Validated |
| | | public class AiApiKeyController { |
| | | |
| | | @Resource |
| | |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "å é¤ API å¯é¥") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('ai:api-key:delete')") |
| | | public CommonResult<Boolean> deleteApiKey(@RequestParam("id") Long id) { |
| | | apiKeyService.deleteApiKey(id); |
| | |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·å¾ API å¯é¥") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true, example = "1024") |
| | | @Operation(summary = "è·å API å¯é¥") |
| | | @PreAuthorize("@ss.hasPermission('ai:api-key:query')") |
| | | public CommonResult<AiApiKeyRespVO> getApiKey(@RequestParam("id") Long id) { |
| | | AiApiKeyDO apiKey = apiKeyService.getApiKey(id); |
| | | return success(BeanUtils.toBean(apiKey, AiApiKeyRespVO.class)); |
| | | return success(BeanUtils.toBean(apiKeyService.getApiKey(id), AiApiKeyRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·å¾ API å¯é¥å页") |
| | | @Operation(summary = "è·å API å¯é¥å页") |
| | | @PreAuthorize("@ss.hasPermission('ai:api-key:query')") |
| | | public CommonResult<PageResult<AiApiKeyRespVO>> getApiKeyPage(@Valid AiApiKeyPageReqVO pageReqVO) { |
| | | PageResult<AiApiKeyDO> pageResult = apiKeyService.getApiKeyPage(pageReqVO); |
| | | return success(BeanUtils.toBean(pageResult, AiApiKeyRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/simple-list") |
| | | @Operation(summary = "è·å¾ API å¯é¥å页å表") |
| | | public CommonResult<List<AiModelRespVO>> getApiKeySimpleList() { |
| | | List<AiApiKeyDO> list = apiKeyService.getApiKeyList(); |
| | | return success(convertList(list, key -> new AiModelRespVO().setId(key.getId()).setName(key.getName()))); |
| | | } |
| | | |
| | | } |
| | | } |
| | |
| | | private AiModelService modelService; |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "å建模å") |
| | | @Operation(summary = "å建 AI 模å") |
| | | @PreAuthorize("@ss.hasPermission('ai:model:create')") |
| | | public CommonResult<Long> createModel(@Valid @RequestBody AiModelSaveReqVO createReqVO) { |
| | | return success(modelService.createModel(createReqVO)); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Operation(summary = "æ´æ°æ¨¡å") |
| | | @Operation(summary = "æ´æ° AI 模å") |
| | | @PreAuthorize("@ss.hasPermission('ai:model:update')") |
| | | public CommonResult<Boolean> updateModel(@Valid @RequestBody AiModelSaveReqVO updateReqVO) { |
| | | modelService.updateModel(updateReqVO); |
| | |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "å 餿¨¡å") |
| | | @Operation(summary = "å é¤ AI 模å") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('ai:model:delete')") |
| | | public CommonResult<Boolean> deleteModel(@RequestParam("id") Long id) { |
| | |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·å¾æ¨¡å") |
| | | @Operation(summary = "è·å AI 模å") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true, example = "1024") |
| | | @PreAuthorize("@ss.hasPermission('ai:model:query')") |
| | | public CommonResult<AiModelRespVO> getModel(@RequestParam("id") Long id) { |
| | | AiModelDO model = modelService.getModel(id); |
| | | return success(BeanUtils.toBean(model, AiModelRespVO.class)); |
| | | return success(BeanUtils.toBean(modelService.getModel(id), AiModelRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·å¾æ¨¡åå页") |
| | | @Operation(summary = "è·å AI 模åå页") |
| | | @PreAuthorize("@ss.hasPermission('ai:model:query')") |
| | | public CommonResult<PageResult<AiModelRespVO>> getModelPage(@Valid AiModelPageReqVO pageReqVO) { |
| | | PageResult<AiModelDO> pageResult = modelService.getModelPage(pageReqVO); |
| | |
| | | } |
| | | |
| | | @GetMapping("/simple-list") |
| | | @Operation(summary = "è·å¾æ¨¡åå表") |
| | | @Parameter(name = "type", description = "ç±»å", required = true, example = "1") |
| | | @Parameter(name = "platform", description = "å¹³å°", example = "midjourney") |
| | | @Operation(summary = "è·å¾æ¨¡åç®åå表", description = "ç¨äºä¸æéæ©å¨çåºæ¯") |
| | | @Parameter(name = "type", description = "模åç±»å", required = true, example = "1") |
| | | @Parameter(name = "platform", description = "模åå¹³å°", example = "TongYi") |
| | | public CommonResult<List<AiModelRespVO>> getModelSimpleList( |
| | | @RequestParam("type") Integer type, |
| | | @RequestParam(value = "platform", required = false) String platform) { |
| | | List<AiModelDO> list = modelService.getModelListByStatusAndType( |
| | | CommonStatusEnum.ENABLE.getStatus(), type, platform); |
| | | return success(convertList(list, model -> new AiModelRespVO().setId(model.getId()) |
| | | .setName(model.getName()).setModel(model.getModel()).setPlatform(model.getPlatform()))); |
| | | return success(convertList(list, model -> new AiModelRespVO() |
| | | .setId(model.getId()) |
| | | .setName(model.getName()) |
| | | .setModel(model.getModel()) |
| | | .setPlatform(model.getPlatform()))); |
| | | } |
| | | |
| | | } |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey; |
| | | |
| | | import lombok.*; |
| | | import java.util.*; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import org.springframework.format.annotation.DateTimeFormat; |
| | | import java.time.LocalDateTime; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | @Schema(description = "管çåå° - AI API å¯é¥å页 Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class AiApiKeyPageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "åç§°", example = "æå¿ä¸è¨") |
| | | @Schema(description = "å¯é¥åç§°") |
| | | private String name; |
| | | |
| | | @Schema(description = "å¹³å°", example = "OpenAI") |
| | | @Schema(description = "å¹³å°") |
| | | private String platform; |
| | | |
| | | @Schema(description = "ç¶æ", example = "1") |
| | | @Schema(description = "ç¶æ") |
| | | private Integer status; |
| | | |
| | | } |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.*; |
| | | import lombok.Data; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Schema(description = "管çåå° - AI API å¯é¥ Response VO") |
| | | @Data |
| | | public class AiApiKeyRespVO { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "23538") |
| | | @Schema(description = "ç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "åç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "æå¿ä¸è¨") |
| | | @Schema(description = "å¯é¥åç§°", example = "éä¹åé®") |
| | | private String name; |
| | | |
| | | @Schema(description = "å¯é¥", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC") |
| | | private String apiKey; |
| | | |
| | | @Schema(description = "å¹³å°", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI") |
| | | @Schema(description = "å¹³å°", example = "TongYi") |
| | | private String platform; |
| | | |
| | | @Schema(description = "èªå®ä¹ API å°å", example = "https://aip.baidubce.com") |
| | | @Schema(description = "API å°å") |
| | | private String url; |
| | | |
| | | @Schema(description = "ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @Schema(description = "ç¶æ", example = "0") |
| | | private Integer status; |
| | | |
| | | } |
| | | @Schema(description = "å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.*; |
| | | import java.util.*; |
| | | import jakarta.validation.constraints.*; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - AI API å¯é¥æ°å¢/ä¿®æ¹ Request VO") |
| | | @Data |
| | | public class AiApiKeySaveReqVO { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "23538") |
| | | @Schema(description = "ç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "åç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "æå¿ä¸è¨") |
| | | @NotEmpty(message = "åç§°ä¸è½ä¸ºç©º") |
| | | @Schema(description = "å¯é¥åç§°", example = "éä¹åé®") |
| | | @NotEmpty(message = "å¯é¥åç§°ä¸è½ä¸ºç©º") |
| | | private String name; |
| | | |
| | | @Schema(description = "å¯é¥", requiredMode = Schema.RequiredMode.REQUIRED, example = "ABC") |
| | | @NotEmpty(message = "å¯é¥ä¸è½ä¸ºç©º") |
| | | @Schema(description = "API å¯é¥", example = "sk-xxx") |
| | | @NotEmpty(message = "API å¯é¥ä¸è½ä¸ºç©º") |
| | | private String apiKey; |
| | | |
| | | @Schema(description = "å¹³å°", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI") |
| | | @Schema(description = "å¹³å°", example = "TongYi") |
| | | @NotEmpty(message = "å¹³å°ä¸è½ä¸ºç©º") |
| | | private String platform; |
| | | |
| | | @Schema(description = "èªå®ä¹ API å°å", example = "https://aip.baidubce.com") |
| | | @Schema(description = "API å°å") |
| | | private String url; |
| | | |
| | | @Schema(description = "ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "ç¶æä¸è½ä¸ºç©º") |
| | | @Schema(description = "ç¶æ", example = "0") |
| | | private Integer status; |
| | | |
| | | } |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.model.vo.model; |
| | | |
| | | import lombok.*; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | @Schema(description = "管çåå° - API 模åå页 Request VO") |
| | | @Schema(description = "管çåå° - AI 模åå页 Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class AiModelPageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "模ååå", example = "å¼ ä¸") |
| | | @Schema(description = "模ååç§°") |
| | | private String name; |
| | | |
| | | @Schema(description = "æ¨¡åæ è¯", example = "gpt-3.5-turbo-0125") |
| | | private String model; |
| | | @Schema(description = "模åç±»å") |
| | | private Integer type; |
| | | |
| | | @Schema(description = "模åå¹³å°", example = "OpenAI") |
| | | @Schema(description = "å¹³å°") |
| | | private String platform; |
| | | |
| | | } |
| | | @Schema(description = "ç¶æ") |
| | | private Integer status; |
| | | |
| | | } |
| | |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import lombok.experimental.Accessors; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Schema(description = "管çåå° - AI 模å Response VO") |
| | | @Data |
| | | @Accessors(chain = true) |
| | | public class AiModelRespVO { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "2630") |
| | | @Schema(description = "ç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "API ç§é¥ç¼å·", example = "22042") |
| | | @Schema(description = "API å¯é¥ç¼å·", example = "1") |
| | | private Long keyId; |
| | | |
| | | @Schema(description = "模ååå", example = "å¼ ä¸") |
| | | @Schema(description = "模ååå", example = "éä¹åé®") |
| | | private String name; |
| | | |
| | | @Schema(description = "æ¨¡åæ è¯", example = "gpt-3.5-turbo-0125") |
| | | @Schema(description = "æ¨¡åæ è¯", example = "qwen-plus") |
| | | private String model; |
| | | |
| | | @Schema(description = "模åå¹³å°", example = "OpenAI") |
| | | @Schema(description = "模åå¹³å°", example = "TongYi") |
| | | private String platform; |
| | | |
| | | @Schema(description = "模åç±»å", example = "1") |
| | | private Integer type; |
| | | |
| | | @Schema(description = "æåº", example = "1") |
| | | @Schema(description = "æåº", example = "0") |
| | | private Integer sort; |
| | | |
| | | @Schema(description = "ç¶æ", example = "2") |
| | | @Schema(description = "ç¶æ", example = "0") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "æ¸©åº¦åæ°", example = "1") |
| | | @Schema(description = "温度") |
| | | private Double temperature; |
| | | |
| | | @Schema(description = "åæ¡åå¤çæå¤§ Token æ°é", example = "4096") |
| | | @Schema(description = "æå¤§ Token æ°") |
| | | private Integer maxTokens; |
| | | |
| | | @Schema(description = "ä¸ä¸æçæå¤§ Message æ°é", example = "8192") |
| | | @Schema(description = "æå¤§ä¸ä¸ææ°") |
| | | private Integer maxContexts; |
| | | |
| | | @Schema(description = "å建æ¶é´", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | @Schema(description = "å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | } |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.model.vo.model; |
| | | |
| | | import cn.iocoder.yudao.module.ai.enums.model.AiModelTypeEnum; |
| | | import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - API æ¨¡åæ°å¢/ä¿®æ¹ Request VO") |
| | | @Schema(description = "管çåå° - AI æ¨¡åæ°å¢/ä¿®æ¹ Request VO") |
| | | @Data |
| | | public class AiModelSaveReqVO { |
| | | |
| | | @Schema(description = "ç¼å·", example = "2630") |
| | | @Schema(description = "ç¼å·", example = "1") |
| | | private Long id; |
| | | |
| | | @Schema(description = "API ç§é¥ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "22042") |
| | | @NotNull(message = "API ç§é¥ç¼å·ä¸è½ä¸ºç©º") |
| | | @Schema(description = "API å¯é¥ç¼å·", example = "1") |
| | | @NotNull(message = "API å¯é¥ä¸è½ä¸ºç©º") |
| | | private Long keyId; |
| | | |
| | | @Schema(description = "模ååå", requiredMode = Schema.RequiredMode.REQUIRED, example = "å¼ ä¸") |
| | | @Schema(description = "模ååå", example = "éä¹åé®") |
| | | @NotEmpty(message = "模åååä¸è½ä¸ºç©º") |
| | | private String name; |
| | | |
| | | @Schema(description = "æ¨¡åæ è¯", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-3.5-turbo-0125") |
| | | @Schema(description = "æ¨¡åæ è¯", example = "qwen-plus") |
| | | @NotEmpty(message = "æ¨¡åæ è¯ä¸è½ä¸ºç©º") |
| | | private String model; |
| | | |
| | | @Schema(description = "模åå¹³å°", requiredMode = Schema.RequiredMode.REQUIRED, example = "OpenAI") |
| | | @Schema(description = "模åå¹³å°", example = "TongYi") |
| | | @NotEmpty(message = "模åå¹³å°ä¸è½ä¸ºç©º") |
| | | @InEnum(AiPlatformEnum.class) |
| | | private String platform; |
| | | |
| | | @Schema(description = "模åç±»å", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @Schema(description = "模åç±»å", example = "1") |
| | | @NotNull(message = "模åç±»åä¸è½ä¸ºç©º") |
| | | @InEnum(AiModelTypeEnum.class) |
| | | private Integer type; |
| | | |
| | | @Schema(description = "æåº", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "æåºä¸è½ä¸ºç©º") |
| | | @Schema(description = "æåº", example = "0") |
| | | private Integer sort; |
| | | |
| | | @Schema(description = "ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @InEnum(CommonStatusEnum.class) |
| | | @NotNull(message = "ç¶æä¸è½ä¸ºç©º") |
| | | @Schema(description = "ç¶æ", example = "0") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "æ¸©åº¦åæ°", example = "1") |
| | | @Schema(description = "温度") |
| | | private Double temperature; |
| | | |
| | | @Schema(description = "åæ¡åå¤çæå¤§ Token æ°é", example = "4096") |
| | | @Schema(description = "æå¤§ Token æ°") |
| | | private Integer maxTokens; |
| | | |
| | | @Schema(description = "ä¸ä¸æçæå¤§ Message æ°é", example = "8192") |
| | | @Schema(description = "æå¤§ä¸ä¸ææ°") |
| | | private Integer maxContexts; |
| | | |
| | | } |
| | | } |
| | |
| | | * @author xiaoxin |
| | | */ |
| | | @TableName(value = "ai_knowledge", autoResultMap = true) |
| | | @KeySequence("ai_knowledge_seq") // ç¨äº OracleãPostgreSQLãKingbaseãDB2ãH2 æ°æ®åºç主é®èªå¢ãå¦ææ¯ MySQL çæ°æ®åºï¼å¯ä¸åã |
| | | @KeySequence("ai_knowledge_seq") |
| | | @Data |
| | | public class AiKnowledgeDO extends BaseDO { |
| | | |
| | | /** |
| | | * ç¼å· |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | /** |
| | | * ç¥è¯åºåç§° |
| | | */ |
| | | |
| | | private String name; |
| | | /** |
| | | * ç¥è¯åºæè¿° |
| | | */ |
| | | |
| | | private String description; |
| | | |
| | | /** |
| | | * å鿍¡åç¼å· |
| | | * |
| | | * å
³è {@link AiModelDO#getId()} |
| | | * å鿍¡åç¼å·ï¼å
³è {@link AiModelDO#getId()} |
| | | */ |
| | | private Long embeddingModelId; |
| | | |
| | | /** |
| | | * æ¨¡åæ è¯ |
| | | * |
| | | * åä½ {@link AiModelDO#getModel()} |
| | | * æ¨¡åæ è¯ï¼åä½ {@link AiModelDO#getModel()} |
| | | */ |
| | | private String embeddingModel; |
| | | |
| | | /** |
| | | * topK |
| | | */ |
| | | private Integer topK; |
| | | /** |
| | | * ç¸ä¼¼åº¦éå¼ |
| | | */ |
| | | |
| | | private Double similarityThreshold; |
| | | |
| | | /** |
| | | * ç¶æ |
| | | * <p> |
| | | * æä¸¾ {@link CommonStatusEnum} |
| | | * ç¶æï¼æä¸¾ {@link CommonStatusEnum} |
| | | */ |
| | | private Integer status; |
| | | |
| | |
| | | * @author xiaoxin |
| | | */ |
| | | @TableName(value = "ai_knowledge_document") |
| | | @KeySequence("ai_knowledge_document_seq") // ç¨äº OracleãPostgreSQLãKingbaseãDB2ãH2 æ°æ®åºç主é®èªå¢ãå¦ææ¯ MySQL çæ°æ®åºï¼å¯ä¸åã |
| | | @KeySequence("ai_knowledge_document_seq") |
| | | @Data |
| | | public class AiKnowledgeDocumentDO extends BaseDO { |
| | | |
| | | /** |
| | | * ç¼å· |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | /** |
| | | * ç¥è¯åºç¼å· |
| | | * <p> |
| | | * å
³è {@link AiKnowledgeDO#getId()} |
| | | */ |
| | | |
| | | private Long knowledgeId; |
| | | /** |
| | | * ææ¡£åç§° |
| | | */ |
| | | |
| | | private String name; |
| | | /** |
| | | * æä»¶ URL |
| | | */ |
| | | |
| | | private String url; |
| | | /** |
| | | * å
容 |
| | | */ |
| | | |
| | | private String content; |
| | | /** |
| | | * ææ¡£é¿åº¦ |
| | | */ |
| | | |
| | | private Integer contentLength; |
| | | |
| | | /** |
| | | * ææ¡£ token æ°é |
| | | */ |
| | | private Integer tokens; |
| | | /** |
| | | * åçæå¤§ Token æ° |
| | | */ |
| | | |
| | | private Integer segmentMaxTokens; |
| | | |
| | | /** |
| | | * å¬åæ¬¡æ° |
| | | */ |
| | | private Integer retrievalCount; |
| | | |
| | | /** |
| | | * ç¶æ |
| | | * <p> |
| | | * æä¸¾ {@link CommonStatusEnum} |
| | | * ç¶æï¼æä¸¾ {@link CommonStatusEnum} |
| | | */ |
| | | private Integer status; |
| | | |
| | |
| | | * @author xiaoxin |
| | | */ |
| | | @TableName(value = "ai_knowledge_segment") |
| | | @KeySequence("ai_knowledge_segment_seq") // ç¨äº OracleãPostgreSQLãKingbaseãDB2ãH2 æ°æ®åºç主é®èªå¢ãå¦ææ¯ MySQL çæ°æ®åºï¼å¯ä¸åã |
| | | @KeySequence("ai_knowledge_segment_seq") |
| | | @Data |
| | | public class AiKnowledgeSegmentDO extends BaseDO { |
| | | |
| | | /** |
| | | * åéåºçç¼å· - ç©ºå¼ |
| | | */ |
| | | public static final String VECTOR_ID_EMPTY = ""; |
| | | |
| | | /** |
| | | * ç¼å· |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | /** |
| | | * ç¥è¯åºç¼å· |
| | | * <p> |
| | | * å
³è {@link AiKnowledgeDO#getId()} |
| | | */ |
| | | |
| | | private Long knowledgeId; |
| | | /** |
| | | * ææ¡£ç¼å· |
| | | * <p> |
| | | * å
³è {@link AiKnowledgeDocumentDO#getId()} |
| | | */ |
| | | |
| | | private Long documentId; |
| | | /** |
| | | * åçå
容 |
| | | */ |
| | | |
| | | private String content; |
| | | /** |
| | | * åçå
容é¿åº¦ |
| | | */ |
| | | |
| | | private Integer contentLength; |
| | | |
| | | /** |
| | | * åéåºçç¼å· |
| | | */ |
| | | private String vectorId; |
| | | /** |
| | | * token æ°é |
| | | */ |
| | | |
| | | private Integer tokens; |
| | | |
| | | /** |
| | | * å¬åæ¬¡æ° |
| | | */ |
| | | private Integer retrievalCount; |
| | | |
| | | /** |
| | | * ç¶æ |
| | | * <p> |
| | | * æä¸¾ {@link CommonStatusEnum} |
| | | * ç¶æï¼æä¸¾ {@link CommonStatusEnum} |
| | | */ |
| | | private Integer status; |
| | | |
| | |
| | | package cn.iocoder.yudao.module.ai.dal.dataobject.model; |
| | | |
| | | import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.*; |
| | | |
| | | /** |
| | | * AI API ç§é¥ DO |
| | | * AI API å¯é¥ DO |
| | | * |
| | | * @author èéæºç |
| | | * @author fansili |
| | | */ |
| | | @TableName("ai_api_key") |
| | | @KeySequence("ai_api_key_seq") // ç¨äº OracleãPostgreSQLãKingbaseãDB2ãH2 æ°æ®åºç主é®èªå¢ãå¦ææ¯ MySQL çæ°æ®åºï¼å¯ä¸åã |
| | | @KeySequence("ai_api_key_seq") |
| | | @Data |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class AiApiKeyDO extends BaseDO { |
| | | |
| | | /** |
| | | * ç¼å· |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | /** |
| | | * åç§° |
| | | */ |
| | | |
| | | private String name; |
| | | /** |
| | | * å¯é¥ |
| | | */ |
| | | |
| | | private String apiKey; |
| | | |
| | | /** |
| | | * å¹³å° |
| | | * |
| | | * æä¸¾ {@link AiPlatformEnum} |
| | | * å¹³å°ï¼æä¸¾ {@link AiPlatformEnum} |
| | | */ |
| | | private String platform; |
| | | /** |
| | | * API å°å |
| | | */ |
| | | |
| | | private String url; |
| | | |
| | | /** |
| | | * ç¶æ |
| | | * |
| | | * æä¸¾ {@link CommonStatusEnum} |
| | | * ç¶æï¼æä¸¾ {@link CommonStatusEnum} |
| | | */ |
| | | private Integer status; |
| | | |
| | |
| | | package cn.iocoder.yudao.module.ai.dal.dataobject.model; |
| | | |
| | | import cn.iocoder.yudao.module.ai.enums.model.AiModelTypeEnum; |
| | | import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import cn.iocoder.yudao.module.ai.enums.model.AiModelTypeEnum; |
| | | import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | |
| | | /** |
| | | * AI 模å DO |
| | | * |
| | | * é»è®¤æ¨¡åï¼{@link #status} 为å¼å¯ï¼å¹¶ä¸ {@link #sort} æåºç¬¬ä¸ |
| | | * |
| | | * @author fansili |
| | | * @since 2024/4/24 19:39 |
| | | */ |
| | | @TableName("ai_model") |
| | | @KeySequence("ai_model_seq") // ç¨äº OracleãPostgreSQLãKingbaseãDB2ãH2 æ°æ®åºç主é®èªå¢ãå¦ææ¯ MySQL çæ°æ®åºï¼å¯ä¸åã |
| | | @KeySequence("ai_model_seq") |
| | | @Data |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public class AiModelDO extends BaseDO { |
| | | |
| | | /** |
| | | * ç¼å· |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | |
| | | /** |
| | | * API ç§é¥ç¼å· |
| | | * |
| | | * å
³è {@link AiApiKeyDO#getId()} |
| | | * API ç§é¥ç¼å·ï¼å
³è {@link AiApiKeyDO#getId()} |
| | | */ |
| | | private Long keyId; |
| | | /** |
| | | * 模ååç§° |
| | | */ |
| | | |
| | | private String name; |
| | | /** |
| | | * æ¨¡åæ å¿ |
| | | */ |
| | | |
| | | private String model; |
| | | |
| | | /** |
| | | * å¹³å° |
| | | * |
| | | * æä¸¾ {@link AiPlatformEnum} |
| | | * å¹³å°ï¼æä¸¾ {@link AiPlatformEnum} |
| | | */ |
| | | private String platform; |
| | | |
| | | /** |
| | | * ç±»å |
| | | * |
| | | * æä¸¾ {@link AiModelTypeEnum} |
| | | * ç±»åï¼æä¸¾ {@link AiModelTypeEnum} |
| | | */ |
| | | private Integer type; |
| | | |
| | | /** |
| | | * æåºå¼ |
| | | */ |
| | | private Integer sort; |
| | | |
| | | /** |
| | | * ç¶æ |
| | | * |
| | | * æä¸¾ {@link CommonStatusEnum} |
| | | * ç¶æï¼æä¸¾ {@link CommonStatusEnum} |
| | | */ |
| | | private Integer status; |
| | | |
| | | // ========== 对è¯é
ç½® ========== |
| | | |
| | | /** |
| | | * æ¸©åº¦åæ° |
| | | * |
| | | * ç¨äºè°æ´çæåå¤çéæºæ§å夿 ·æ§ç¨åº¦ï¼è¾ä½ç温度å¼ä¼ä½¿è¾åºæ´æ¶æäºé«é¢è¯æ±ï¼è¾é«çåå¢å 夿 ·æ§ |
| | | */ |
| | | private Double temperature; |
| | | /** |
| | | * åæ¡åå¤çæå¤§ Token æ°é |
| | | */ |
| | | |
| | | private Integer maxTokens; |
| | | /** |
| | | * ä¸ä¸æçæå¤§ Message æ°é |
| | | */ |
| | | |
| | | private Integer maxContexts; |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * AI ç¥è¯åºææ¡£ Mapper |
| | | * |
| | | * @author xiaoxin |
| | | */ |
| | | @Mapper |
| | | public interface AiKnowledgeDocumentMapper extends BaseMapperX<AiKnowledgeDocumentDO> { |
| | | |
| | |
| | | update(new LambdaUpdateWrapper<AiKnowledgeDocumentDO>() |
| | | .setSql(" retrieval_count = retrieval_count + 1") |
| | | .in(AiKnowledgeDocumentDO::getId, ids)); |
| | | } |
| | | |
| | | default List<AiKnowledgeDocumentDO> selectListByStatus(Integer status) { |
| | | return selectList(AiKnowledgeDocumentDO::getStatus, status); |
| | | } |
| | | |
| | | default List<AiKnowledgeDocumentDO> selectListByKnowledgeId(Long knowledgeId) { |
| | |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * AI ç¥è¯åº Mapper |
| | | * |
| | | * @author xiaoxin |
| | | */ |
| | | @Mapper |
| | | public interface AiKnowledgeMapper extends BaseMapperX<AiKnowledgeDO> { |
| | | |
| | |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import com.github.yulichang.wrapper.MPJLambdaWrapper; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * AI ç¥è¯åºåç Mapper |
| | | * |
| | | * @author xiaoxin |
| | | */ |
| | | @Mapper |
| | | public interface AiKnowledgeSegmentMapper extends BaseMapperX<AiKnowledgeSegmentDO> { |
| | | |
| | | default PageResult<AiKnowledgeSegmentDO> selectPage(AiKnowledgeSegmentPageReqVO reqVO) { |
| | | return selectPage(reqVO, new LambdaQueryWrapperX<AiKnowledgeSegmentDO>() |
| | | .eq(AiKnowledgeSegmentDO::getDocumentId, reqVO.getDocumentId()) |
| | | .eqIfPresent(AiKnowledgeSegmentDO::getDocumentId, reqVO.getDocumentId()) |
| | | .likeIfPresent(AiKnowledgeSegmentDO::getContent, reqVO.getContent()) |
| | | .eqIfPresent(AiKnowledgeSegmentDO::getStatus, reqVO.getStatus()) |
| | | .orderByDesc(AiKnowledgeSegmentDO::getId)); |
| | |
| | | |
| | | default List<AiKnowledgeSegmentDO> selectListByDocumentId(Long documentId) { |
| | | return selectList(new LambdaQueryWrapperX<AiKnowledgeSegmentDO>() |
| | | .eq(AiKnowledgeSegmentDO::getDocumentId, documentId) |
| | | .orderByDesc(AiKnowledgeSegmentDO::getId)); |
| | | .eq(AiKnowledgeSegmentDO::getDocumentId, documentId)); |
| | | } |
| | | |
| | | default List<AiKnowledgeSegmentDO> selectListByKnowledgeIdAndStatus(Long knowledgeId, Integer status) { |
| | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | /** |
| | | * AI API å¯é¥ Mapper |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | @Mapper |
| | | public interface AiApiKeyMapper extends BaseMapperX<AiApiKeyDO> { |
| | | |
| | | default PageResult<AiApiKeyDO> selectPage(AiApiKeyPageReqVO reqVO) { |
| | | return selectPage(reqVO, new LambdaQueryWrapperX<AiApiKeyDO>() |
| | | .likeIfPresent(AiApiKeyDO::getName, reqVO.getName()) |
| | | .eqIfPresent(AiApiKeyDO::getPlatform, reqVO.getPlatform()) |
| | | .eqIfPresent(AiApiKeyDO::getStatus, reqVO.getStatus()) |
| | | default AiApiKeyDO selectFirstByPlatformAndStatus(String platform, Integer status) { |
| | | return selectOne(new LambdaQueryWrapperX<AiApiKeyDO>() |
| | | .eq(AiApiKeyDO::getPlatform, platform) |
| | | .eq(AiApiKeyDO::getStatus, status) |
| | | .last("LIMIT 1")); |
| | | } |
| | | |
| | | default PageResult<AiApiKeyDO> selectPage(AiApiKeyPageReqVO pageReqVO) { |
| | | return selectPage(pageReqVO, new LambdaQueryWrapperX<AiApiKeyDO>() |
| | | .likeIfPresent(AiApiKeyDO::getName, pageReqVO.getName()) |
| | | .eqIfPresent(AiApiKeyDO::getPlatform, pageReqVO.getPlatform()) |
| | | .eqIfPresent(AiApiKeyDO::getStatus, pageReqVO.getStatus()) |
| | | .orderByDesc(AiApiKeyDO::getId)); |
| | | } |
| | | |
| | | default AiApiKeyDO selectFirstByPlatformAndStatus(String platform, Integer status) { |
| | | return selectOne(new QueryWrapperX<AiApiKeyDO>() |
| | | .eq("platform", platform) |
| | | .eq("status", status) |
| | | .limitN(1) |
| | | .orderByAsc("id")); |
| | | } |
| | | |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.ai.dal.mysql.model; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import java.util.List; |
| | | |
| | | @Mapper |
| | | public interface AiModelMapper extends BaseMapperX<AiModelDO> { |
| | | |
| | | default AiModelDO selectFirstByStatus(Integer type, Integer status) { |
| | | return selectOne(new LambdaQueryWrapperX<AiModelDO>() |
| | | .eq(AiModelDO::getType, type) |
| | | .eq(AiModelDO::getStatus, status) |
| | | .orderByAsc(AiModelDO::getSort) |
| | | .last("LIMIT 1")); |
| | | } |
| | | |
| | | default PageResult<AiModelDO> selectPage(AiModelPageReqVO pageReqVO) { |
| | | return selectPage(pageReqVO, new LambdaQueryWrapperX<AiModelDO>() |
| | | .eqIfPresent(AiModelDO::getType, pageReqVO.getType()) |
| | | .eqIfPresent(AiModelDO::getStatus, pageReqVO.getStatus()) |
| | | .eqIfPresent(AiModelDO::getPlatform, pageReqVO.getPlatform()) |
| | | .likeIfPresent(AiModelDO::getName, pageReqVO.getName()) |
| | | .orderByAsc(AiModelDO::getSort)); |
| | | } |
| | | |
| | | default List<AiModelDO> selectListByStatusAndType(Integer status, Integer type, String platform) { |
| | | return selectList(new LambdaQueryWrapperX<AiModelDO>() |
| | | .eqIfPresent(AiModelDO::getStatus, status) |
| | | .eqIfPresent(AiModelDO::getType, type) |
| | | .eqIfPresent(AiModelDO::getPlatform, platform)); |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | /** |
| | | * AI ç¥è¯åºææ¡£åççç¥æä¸¾ |
| | | * |
| | | * @author runzhen |
| | | */ |
| | | @AllArgsConstructor |
| | | @Getter |
| | | public enum AiDocumentSplitStrategyEnum { |
| | | |
| | | /** |
| | | * èªå¨è¯å«ææ¡£ç±»åå¹¶éæ©æä½³åççç¥ |
| | | */ |
| | | AUTO("auto", "èªå¨è¯å«"), |
| | | |
| | | /** |
| | | * åºäº Token æ°éæºæ¢°ååï¼é»è®¤çç¥ï¼ |
| | | */ |
| | | TOKEN("token", "Token åå"), |
| | | |
| | | /** |
| | | * ææ®µè½ååï¼ä»¥åæ¢è¡ç¬¦ä¸ºåéï¼ |
| | | */ |
| | | PARAGRAPH("paragraph", "段è½åå"), |
| | | |
| | | /** |
| | | * Markdown QA æ ¼å¼ä¸ç¨åçå¨ |
| | | * è¯å«äºçº§æ é¢ä½ä¸ºé®é¢ï¼ä¿æé®ç坹宿´æ§ |
| | | * é¿çæ¡æºè½ååä½ä¿çé®é¢ä½ä¸ºä¸ä¸æ |
| | | */ |
| | | MARKDOWN_QA("markdown_qa", "Markdown QA åå"), |
| | | |
| | | /** |
| | | * è¯ä¹åååï¼ä¿çå¥å宿´æ§ |
| | | * 卿®µè½åå¥åè¾¹çå¤ååï¼é¿å
æªæ |
| | | */ |
| | | SEMANTIC("semantic", "è¯ä¹åå"); |
| | | |
| | | /** |
| | | * çç¥ä»£ç |
| | | */ |
| | | private final String code; |
| | | |
| | | /** |
| | | * çç¥åç§° |
| | | */ |
| | | private final String name; |
| | | |
| | | } |
| | |
| | | |
| | | /** |
| | | * AI éè¯¯ç æä¸¾ç±» |
| | | * <p> |
| | | * ai ç³»ç»ï¼ä½¿ç¨ 1-040-000-000 段 |
| | | */ |
| | | public interface ErrorCodeConstants { |
| | | |
| | | // ========== API å¯é¥ 1-040-000-000 ========== |
| | | // ========== API å¯é¥ ========== |
| | | ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API å¯é¥ä¸åå¨"); |
| | | ErrorCode API_KEY_DISABLE = new ErrorCode(1_040_000_001, "API å¯é¥å·²ç¦ç¨ï¼"); |
| | | ErrorCode API_CONFIG_PLACEHOLDER_NOT_RESOLVED = new ErrorCode(1_040_000_002, "AI é
ç½®({})æ æ³è§£æï¼è¯·æ£æ¥ç¯å¢åéæé
置项"); |
| | | |
| | | // ========== API 模å 1-040-001-000 ========== |
| | | // ========== API 模å ========== |
| | | ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "模åä¸åå¨!"); |
| | | ErrorCode MODEL_DISABLE = new ErrorCode(1_040_001_001, "模å({})å·²ç¦ç¨!"); |
| | | ErrorCode MODEL_DEFAULT_NOT_EXISTS = new ErrorCode(1_040_001_002, "æä½å¤±è´¥ï¼æ¾ä¸å°é»è®¤æ¨¡å"); |
| | | ErrorCode MODEL_USE_TYPE_ERROR = new ErrorCode(1_040_001_003, "æä½å¤±è´¥ï¼è¯¥æ¨¡åçæ¨¡åç±»å䏿£ç¡®"); |
| | | |
| | | // ========== API è天è§è² 1-040-002-000 ========== |
| | | ErrorCode CHAT_ROLE_NOT_EXISTS = new ErrorCode(1_040_002_000, "è天è§è²ä¸åå¨"); |
| | | ErrorCode CHAT_ROLE_DISABLE = new ErrorCode(1_040_001_001, "è天è§è²({})å·²ç¦ç¨!"); |
| | | |
| | | // ========== API è天ä¼è¯ 1-040-003-000 ========== |
| | | ErrorCode CHAT_CONVERSATION_NOT_EXISTS = new ErrorCode(1_040_003_000, "对è¯ä¸åå¨!"); |
| | | ErrorCode CHAT_CONVERSATION_MODEL_ERROR = new ErrorCode(1_040_003_001, "æä½å¤±è´¥ï¼è¯¥è天模åçé
ç½®ä¸å®æ´"); |
| | | |
| | | // ========== API èå¤©æ¶æ¯ 1-040-004-000 ========== |
| | | ErrorCode CHAT_MESSAGE_NOT_EXIST = new ErrorCode(1_040_004_000, "æ¶æ¯ä¸åå¨!"); |
| | | ErrorCode CHAT_STREAM_ERROR = new ErrorCode(1_040_004_001, "对è¯çæå¼å¸¸!"); |
| | | |
| | | // ========== API ç»ç» 1-040-005-000 ========== |
| | | ErrorCode IMAGE_NOT_EXISTS = new ErrorCode(1_040_005_000, "å¾çä¸åå¨!"); |
| | | ErrorCode IMAGE_MIDJOURNEY_SUBMIT_FAIL = new ErrorCode(1_040_005_001, "Midjourney æäº¤å¤±è´¥!åå ï¼{}"); |
| | | ErrorCode IMAGE_CUSTOM_ID_NOT_EXISTS = new ErrorCode(1_040_005_002, "Midjourney æé® customId ä¸åå¨! {}"); |
| | | |
| | | // ========== API é³ä¹ 1-040-006-000 ========== |
| | | ErrorCode MUSIC_NOT_EXISTS = new ErrorCode(1_040_006_000, "é³ä¹ä¸åå¨!"); |
| | | |
| | | // ========== API åä½ 1-040-007-000 ========== |
| | | ErrorCode WRITE_NOT_EXISTS = new ErrorCode(1_040_007_000, "使ä¸åå¨!"); |
| | | ErrorCode WRITE_STREAM_ERROR = new ErrorCode(1_040_07_001, "åä½çæå¼å¸¸!"); |
| | | |
| | | // ========== API æç»´å¯¼å¾ 1-040-008-000 ========== |
| | | ErrorCode MIND_MAP_NOT_EXISTS = new ErrorCode(1_040_008_000, "æç»´å¯¼å¾ä¸åå¨!"); |
| | | |
| | | // ========== API ç¥è¯åº 1-040-009-000 ========== |
| | | // ========== API ç¥è¯åº ========== |
| | | ErrorCode KNOWLEDGE_NOT_EXISTS = new ErrorCode(1_040_009_000, "ç¥è¯åºä¸åå¨!"); |
| | | |
| | | ErrorCode KNOWLEDGE_DOCUMENT_NOT_EXISTS = new ErrorCode(1_040_009_101, "ææ¡£ä¸åå¨!"); |
| | | ErrorCode KNOWLEDGE_DOCUMENT_FILE_EMPTY = new ErrorCode(1_040_009_102, "ææ¡£å
容为空!"); |
| | | ErrorCode KNOWLEDGE_DOCUMENT_FILE_DOWNLOAD_FAIL = new ErrorCode(1_040_009_102, "æä»¶ä¸è½½å¤±è´¥!"); |
| | | ErrorCode KNOWLEDGE_DOCUMENT_FILE_READ_FAIL = new ErrorCode(1_040_009_102, "ææ¡£å 载失败!"); |
| | | |
| | | ErrorCode KNOWLEDGE_SEGMENT_NOT_EXISTS = new ErrorCode(1_040_009_202, "段è½ä¸åå¨!"); |
| | | ErrorCode KNOWLEDGE_SEGMENT_CONTENT_TOO_LONG = new ErrorCode(1_040_009_203, "å
容 Token æ°ä¸º {}ï¼è¶
è¿æå¤§éå¶ {}"); |
| | | |
| | | // ========== AI å·¥å
· 1-040-010-000 ========== |
| | | ErrorCode TOOL_NOT_EXISTS = new ErrorCode(1_040_010_000, "å·¥å
·ä¸åå¨"); |
| | | ErrorCode TOOL_NAME_NOT_EXISTS = new ErrorCode(1_040_010_001, "å·¥å
·({})æ¾ä¸å° Bean"); |
| | | |
| | | // ========== AI 工使µ 1-040-011-000 ========== |
| | | ErrorCode WORKFLOW_NOT_EXISTS = new ErrorCode(1_040_011_000, "工使µä¸åå¨"); |
| | | ErrorCode WORKFLOW_CODE_EXISTS = new ErrorCode(1_040_011_001, "工使µæ è¯å·²åå¨"); |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.Getter; |
| | | import lombok.RequiredArgsConstructor; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * AI 模åç±»åçæä¸¾ |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | @Getter |
| | | @RequiredArgsConstructor |
| | |
| | | EMBEDDING(5, "åé"), |
| | | RERANK(6, "éæåº"); |
| | | |
| | | /** |
| | | * ç±»å |
| | | */ |
| | | private final Integer type; |
| | | /** |
| | | * ç±»åå |
| | | */ |
| | | private final String name; |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()).map(AiModelTypeEnum::getType).toArray(Integer[]::new); |
| | | |
| | | @Override |
| | | public Integer[] array() { |
| | | return ARRAYS; |
| | | } |
| | | public Integer[] array() { return ARRAYS; } |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.framework.common.core.ArrayValuable; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | import java.util.Arrays; |
| | | |
| | | /** |
| | | * AI 模åå¹³å° |
| | | * |
| | | * @author fansili |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum AiPlatformEnum implements ArrayValuable<String> { |
| | | |
| | | // ========== å½å
å¹³å° ========== |
| | | TONG_YI("TongYi", "éä¹åé®"), |
| | | DEEP_SEEK("DeepSeek", "DeepSeek"), |
| | | OPENAI("OpenAI", "OpenAI"), |
| | | OLLAMA("Ollama", "Ollama"); |
| | | |
| | | TONG_YI("TongYi", "éä¹åé®"), // é¿é |
| | | YI_YAN("YiYan", "æå¿ä¸è¨"), // ç¾åº¦ |
| | | DEEP_SEEK("DeepSeek", "DeepSeek"), // DeepSeek |
| | | ZHI_PU("ZhiPu", "æºè°±"), // æºè°± AI |
| | | XING_HUO("XingHuo", "æç«"), // è®¯é£ |
| | | DOU_BAO("DouBao", "è±å
"), // åè |
| | | HUN_YUAN("HunYuan", "æ··å
"), // è
¾è®¯ |
| | | SILICON_FLOW("SiliconFlow", "ç¡
åºæµå¨"), // ç¡
åºæµå¨ |
| | | MINI_MAX("MiniMax", "MiniMax"), // ç¨å®ç§æ |
| | | MOONSHOT("Moonshot", "æä¹æé¢"), // KIMI |
| | | BAI_CHUAN("BaiChuan", "ç¾å·æºè½"), // ç¾å·æºè½ |
| | | STEP_FUN("StepFun", "é¶è·æè¾°"), // é¶è·æè¾° |
| | | |
| | | // ========== å½å¤å¹³å° ========== |
| | | |
| | | OPENAI("OpenAI", "OpenAI"), // OpenAI 宿¹ |
| | | AZURE_OPENAI("AzureOpenAI", "AzureOpenAI"), // OpenAI 微软 |
| | | ANTHROPIC("Anthropic", "Anthropic"), // Anthropic Claude |
| | | GEMINI("Gemini", "Gemini"), // è°·æ Gemini |
| | | OLLAMA("Ollama", "Ollama"), |
| | | |
| | | STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI |
| | | MIDJOURNEY("Midjourney", "Midjourney"), // Midjourney |
| | | SUNO("Suno", "Suno"), // Suno AI |
| | | GROK("Grok", "Grok"), // Grok |
| | | |
| | | ; |
| | | |
| | | /** |
| | | * å¹³å° |
| | | */ |
| | | private final String platform; |
| | | /** |
| | | * å¹³å°å |
| | | */ |
| | | private final String name; |
| | | |
| | | public static final String[] ARRAYS = Arrays.stream(values()).map(AiPlatformEnum::getPlatform).toArray(String[]::new); |
| | | |
| | | public static AiPlatformEnum validatePlatform(String platform) { |
| | | for (AiPlatformEnum platformEnum : AiPlatformEnum.values()) { |
| | | if (platformEnum.getPlatform().equals(platform)) { |
| | | return platformEnum; |
| | | } |
| | | for (AiPlatformEnum e : values()) { |
| | | if (e.getPlatform().equals(platform)) return e; |
| | | } |
| | | throw new IllegalArgumentException("鿳平å°ï¼ " + platform); |
| | | throw new IllegalArgumentException("鿳平å°ï¼" + platform); |
| | | } |
| | | |
| | | @Override |
| | | public String[] array() { |
| | | return ARRAYS; |
| | | } |
| | | public String[] array() { return ARRAYS; } |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.framework.ai.config; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.hutool.extra.spring.SpringUtil; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactory; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactoryImpl; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.stepfun.StepFunChatModel; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient; |
| | | import cn.iocoder.yudao.module.ai.tool.method.PersonService; |
| | | import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; |
| | | import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi; |
| | | import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; |
| | | import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; |
| | | import com.alibaba.cloud.ai.dashscope.embedding.text.DashScopeEmbeddingModel; |
| | | import com.alibaba.cloud.ai.dashscope.embedding.text.DashScopeEmbeddingOptions; |
| | | import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel; |
| | | import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions; |
| | | import io.micrometer.observation.ObservationRegistry; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.ai.chat.model.ChatModel; |
| | | import org.springframework.ai.deepseek.DeepSeekChatModel; |
| | | import org.springframework.ai.deepseek.DeepSeekChatOptions; |
| | | import org.springframework.ai.deepseek.api.DeepSeekApi; |
| | | import org.springframework.ai.document.MetadataMode; |
| | | import org.springframework.ai.embedding.BatchingStrategy; |
| | | import org.springframework.ai.embedding.TokenCountBatchingStrategy; |
| | | import org.springframework.ai.model.tool.ToolCallingManager; |
| | | import org.springframework.ai.openai.OpenAiChatModel; |
| | | import org.springframework.ai.openai.OpenAiChatOptions; |
| | | import org.springframework.ai.openai.OpenAiEmbeddingModel; |
| | | import org.springframework.ai.openai.OpenAiEmbeddingOptions; |
| | | import org.springframework.ai.retry.RetryUtils; |
| | | import org.springframework.ai.support.ToolCallbacks; |
| | | import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; |
| | | import org.springframework.ai.tokenizer.TokenCountEstimator; |
| | | import org.springframework.ai.tool.ToolCallback; |
| | | import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClientProperties; |
| | | import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreProperties; |
| | | import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreProperties; |
| | | import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties; |
| | | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
| | | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| | | import org.springframework.boot.context.properties.EnableConfigurationProperties; |
| | | import org.springframework.beans.factory.annotation.Value; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | | import java.util.List; |
| | | import org.springframework.boot.context.properties.EnableConfigurationProperties; |
| | | |
| | | /** |
| | | * èé AI èªå¨é
ç½® |
| | | * |
| | | * @author fansili |
| | | * ä½¿ç¨ OpenAI å
¼å®¹æ¥å£å¯¹æ¥éä¹åé® DashScope |
| | | */ |
| | | @Configuration |
| | | @EnableConfigurationProperties({ YudaoAiProperties.class, |
| | | QdrantVectorStoreProperties.class, // è§£æ Qdrant é
ç½® |
| | | RedisVectorStoreProperties.class, // è§£æ Redis é
ç½® |
| | | MilvusVectorStoreProperties.class, MilvusServiceClientProperties.class // è§£æ Milvus é
ç½® |
| | | @EnableConfigurationProperties({ |
| | | YudaoAiProperties.class, |
| | | QdrantVectorStoreProperties.class, |
| | | RedisVectorStoreProperties.class, |
| | | MilvusVectorStoreProperties.class, |
| | | MilvusServiceClientProperties.class |
| | | }) |
| | | @Slf4j |
| | | public class AiAutoConfiguration { |
| | | |
| | | private static final String DASHSCOPE_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"; |
| | | |
| | | @Bean |
| | | public AiModelFactory aiModelFactory() { |
| | |
| | | @Bean |
| | | @ConditionalOnMissingBean |
| | | public ObservationRegistry observationRegistry() { |
| | | // ç¹æ®ï¼å
åºæ ObservationRegistry Beanï¼é¿å
ç¸å
³ç ChatModel å建æ¥éãç¸å
³ issueï¼https://t.zsxq.com/CuPu4 |
| | | return ObservationRegistry.NOOP; |
| | | } |
| | | |
| | | // ========== åç§ AI Client å建 ========== |
| | | |
| | | @Bean |
| | | @ConditionalOnMissingBean |
| | | @ConditionalOnProperty(value = "spring.ai.dashscope.api-key") |
| | | public DashScopeChatModel dashScopeChatModel(@Value("${spring.ai.dashscope.api-key}") String apiKey, |
| | | ToolCallingManager toolCallingManager, |
| | | ObservationRegistry observationRegistry) { |
| | | return buildTongYiChatModel(apiKey, toolCallingManager, observationRegistry); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnMissingBean |
| | | @ConditionalOnProperty(value = "spring.ai.dashscope.api-key") |
| | | public DashScopeImageModel dashScopeImageModel(@Value("${spring.ai.dashscope.api-key}") String apiKey, |
| | | ObservationRegistry observationRegistry) { |
| | | return buildTongYiImagesModel(apiKey, observationRegistry); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnMissingBean |
| | | @ConditionalOnProperty(value = "spring.ai.dashscope.api-key") |
| | | public DashScopeEmbeddingModel dashScopeEmbeddingModel(@Value("${spring.ai.dashscope.api-key}") String apiKey, |
| | | ObservationRegistry observationRegistry) { |
| | | return buildTongYiEmbeddingModel(apiKey, null, observationRegistry); |
| | | } |
| | | |
| | | public static DashScopeChatModel buildTongYiChatModel(String apiKey) { |
| | | return buildTongYiChatModel(apiKey, getToolCallingManager(), getObservationRegistry()); |
| | | } |
| | | |
| | | private static DashScopeChatModel buildTongYiChatModel(String apiKey, ToolCallingManager toolCallingManager, |
| | | ObservationRegistry observationRegistry) { |
| | | DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build(); |
| | | DashScopeChatOptions options = DashScopeChatOptions.builder() |
| | | .model(DashScopeApi.DEFAULT_CHAT_MODEL) |
| | | .temperature(0.7) |
| | | .build(); |
| | | return new DashScopeChatModel(dashScopeApi, options, toolCallingManager, RetryUtils.DEFAULT_RETRY_TEMPLATE, |
| | | observationRegistry); |
| | | } |
| | | |
| | | public static DashScopeImageModel buildTongYiImagesModel(String apiKey) { |
| | | return buildTongYiImagesModel(apiKey, getObservationRegistry()); |
| | | } |
| | | |
| | | private static DashScopeImageModel buildTongYiImagesModel(String apiKey, ObservationRegistry observationRegistry) { |
| | | DashScopeImageApi dashScopeImageApi = DashScopeImageApi.builder().apiKey(apiKey).build(); |
| | | DashScopeImageOptions options = DashScopeImageOptions.builder() |
| | | .model(DashScopeImageApi.DEFAULT_IMAGE_MODEL) |
| | | .build(); |
| | | return new DashScopeImageModel(dashScopeImageApi, options, RetryUtils.DEFAULT_RETRY_TEMPLATE, |
| | | observationRegistry); |
| | | } |
| | | |
| | | public static DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) { |
| | | return buildTongYiEmbeddingModel(apiKey, model, getObservationRegistry()); |
| | | } |
| | | |
| | | private static DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model, |
| | | ObservationRegistry observationRegistry) { |
| | | DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build(); |
| | | DashScopeEmbeddingOptions options = DashScopeEmbeddingOptions.builder() |
| | | .model(StrUtil.blankToDefault(model, DashScopeApi.DEFAULT_EMBEDDING_MODEL)) |
| | | .build(); |
| | | return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, options, RetryUtils.DEFAULT_RETRY_TEMPLATE, |
| | | observationRegistry); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true") |
| | | public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) { |
| | | YudaoAiProperties.DouBao properties = yudaoAiProperties.getDoubao(); |
| | | return buildDouBaoChatClient(properties); |
| | | } |
| | | |
| | | public DouBaoChatModel buildDouBaoChatClient(YudaoAiProperties.DouBao properties) { |
| | | if (StrUtil.isEmpty(properties.getModel())) { |
| | | properties.setModel(DouBaoChatModel.MODEL_DEFAULT); |
| | | } |
| | | DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() |
| | | .deepSeekApi(DeepSeekApi.builder() |
| | | .baseUrl(DouBaoChatModel.BASE_URL) |
| | | .completionsPath(DouBaoChatModel.COMPLETE_PATH) |
| | | .apiKey(properties.getApiKey()) |
| | | .build()) |
| | | .options(DeepSeekChatOptions.builder() |
| | | .model(properties.getModel()) |
| | | .temperature(properties.getTemperature()) |
| | | .maxTokens(properties.getMaxTokens()) |
| | | .topP(properties.getTopP()) |
| | | .build()) |
| | | .build(); |
| | | return new DouBaoChatModel(openAiChatModel); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.siliconflow.enable", havingValue = "true") |
| | | public SiliconFlowChatModel siliconFlowChatClient(YudaoAiProperties yudaoAiProperties) { |
| | | YudaoAiProperties.SiliconFlow properties = yudaoAiProperties.getSiliconflow(); |
| | | return buildSiliconFlowChatClient(properties); |
| | | } |
| | | |
| | | public SiliconFlowChatModel buildSiliconFlowChatClient(YudaoAiProperties.SiliconFlow properties) { |
| | | if (StrUtil.isEmpty(properties.getModel())) { |
| | | properties.setModel(SiliconFlowApiConstants.MODEL_DEFAULT); |
| | | } |
| | | DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() |
| | | .deepSeekApi(DeepSeekApi.builder() |
| | | .baseUrl(SiliconFlowApiConstants.DEFAULT_BASE_URL) |
| | | .apiKey(properties.getApiKey()) |
| | | .build()) |
| | | .options(DeepSeekChatOptions.builder() |
| | | .model(properties.getModel()) |
| | | .temperature(properties.getTemperature()) |
| | | .maxTokens(properties.getMaxTokens()) |
| | | .topP(properties.getTopP()) |
| | | .build()) |
| | | .build(); |
| | | return new SiliconFlowChatModel(openAiChatModel); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.hunyuan.enable", havingValue = "true") |
| | | public HunYuanChatModel hunYuanChatClient(YudaoAiProperties yudaoAiProperties) { |
| | | YudaoAiProperties.HunYuan properties = yudaoAiProperties.getHunyuan(); |
| | | return buildHunYuanChatClient(properties); |
| | | } |
| | | |
| | | public HunYuanChatModel buildHunYuanChatClient(YudaoAiProperties.HunYuan properties) { |
| | | if (StrUtil.isEmpty(properties.getModel())) { |
| | | properties.setModel(HunYuanChatModel.MODEL_DEFAULT); |
| | | } |
| | | if (StrUtil.isEmpty(properties.getBaseUrl())) { |
| | | properties.setBaseUrl(HunYuanChatModel.BASE_URL); |
| | | } |
| | | // å建 DeepSeekChatModelãHunYuanChatModel 对象 |
| | | DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder() |
| | | .deepSeekApi(DeepSeekApi.builder() |
| | | .baseUrl(properties.getBaseUrl()) |
| | | .completionsPath(HunYuanChatModel.COMPLETE_PATH) |
| | | .apiKey(properties.getApiKey()) |
| | | .build()) |
| | | .options(DeepSeekChatOptions.builder() |
| | | .model(properties.getModel()) |
| | | .temperature(properties.getTemperature()) |
| | | .maxTokens(properties.getMaxTokens()) |
| | | .topP(properties.getTopP()) |
| | | .build()) |
| | | .build(); |
| | | return new HunYuanChatModel(openAiChatModel); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true") |
| | | public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) { |
| | | YudaoAiProperties.XingHuo properties = yudaoAiProperties.getXinghuo(); |
| | | return buildXingHuoChatClient(properties); |
| | | } |
| | | |
| | | public XingHuoChatModel buildXingHuoChatClient(YudaoAiProperties.XingHuo properties) { |
| | | if (StrUtil.isEmpty(properties.getModel())) { |
| | | properties.setModel(XingHuoChatModel.MODEL_DEFAULT); |
| | | } |
| | | return XingHuoChatModel.builder() |
| | | .apiKey(properties.getApiKey()) |
| | | .options(DeepSeekChatOptions.builder() |
| | | .model(properties.getModel()) |
| | | .temperature(properties.getTemperature()) |
| | | .maxTokens(properties.getMaxTokens()) |
| | | .topP(properties.getTopP()) |
| | | .build()) |
| | | .build(); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.baichuan.enable", havingValue = "true") |
| | | public BaiChuanChatModel baiChuanChatClient(YudaoAiProperties yudaoAiProperties) { |
| | | YudaoAiProperties.BaiChuan properties = yudaoAiProperties.getBaichuan(); |
| | | return buildBaiChuanChatClient(properties); |
| | | } |
| | | |
| | | public BaiChuanChatModel buildBaiChuanChatClient(YudaoAiProperties.BaiChuan properties) { |
| | | if (StrUtil.isEmpty(properties.getModel())) { |
| | | properties.setModel(BaiChuanChatModel.MODEL_DEFAULT); |
| | | } |
| | | DeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder() |
| | | .deepSeekApi(DeepSeekApi.builder() |
| | | .baseUrl(BaiChuanChatModel.BASE_URL) |
| | | .apiKey(properties.getApiKey()) |
| | | .completionsPath(BaiChuanChatModel.COMPLETE_PATH) |
| | | .build()) |
| | | .options(DeepSeekChatOptions.builder() |
| | | .model(properties.getModel()) |
| | | .temperature(properties.getTemperature()) |
| | | .maxTokens(properties.getMaxTokens()) |
| | | .topP(properties.getTopP()) |
| | | .build()) |
| | | .build(); |
| | | return new BaiChuanChatModel(deepSeekChatModel); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.yiyan.enable", havingValue = "true") |
| | | public YiYanChatModel yiYanChatClient(YudaoAiProperties yudaoAiProperties) { |
| | | YudaoAiProperties.YiYan properties = yudaoAiProperties.getYiyan(); |
| | | return buildYiYanChatClient(properties); |
| | | } |
| | | |
| | | public YiYanChatModel buildYiYanChatClient(YudaoAiProperties.YiYan properties) { |
| | | if (StrUtil.isEmpty(properties.getModel())) { |
| | | properties.setModel(YiYanChatModel.MODEL_DEFAULT); |
| | | } |
| | | return new YiYanChatModel(buildDeepSeekCompatibleChatModel( |
| | | StrUtil.blankToDefault(properties.getBaseUrl(), YiYanChatModel.BASE_URL), |
| | | null, properties.getApiKey(), properties.getModel(), properties.getTemperature(), |
| | | properties.getMaxTokens(), properties.getTopP())); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.zhipu.enable", havingValue = "true") |
| | | public ZhiPuChatModel zhiPuChatClient(YudaoAiProperties yudaoAiProperties) { |
| | | YudaoAiProperties.ZhiPu properties = yudaoAiProperties.getZhipu(); |
| | | return buildZhiPuChatClient(properties); |
| | | } |
| | | |
| | | public ZhiPuChatModel buildZhiPuChatClient(YudaoAiProperties.ZhiPu properties) { |
| | | if (StrUtil.isEmpty(properties.getModel())) { |
| | | properties.setModel(ZhiPuChatModel.MODEL_DEFAULT); |
| | | } |
| | | DeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder() |
| | | .deepSeekApi(DeepSeekApi.builder() |
| | | .baseUrl(StrUtil.blankToDefault(properties.getBaseUrl(), ZhiPuChatModel.BASE_URL)) |
| | | .apiKey(properties.getApiKey()) |
| | | .build()) |
| | | .options(DeepSeekChatOptions.builder() |
| | | .model(properties.getModel()) |
| | | .temperature(properties.getTemperature()) |
| | | .maxTokens(properties.getMaxTokens()) |
| | | .topP(properties.getTopP()) |
| | | .build()) |
| | | .build(); |
| | | return new ZhiPuChatModel(deepSeekChatModel); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.minimax.enable", havingValue = "true") |
| | | public MiniMaxChatModel miniMaxChatClient(YudaoAiProperties yudaoAiProperties) { |
| | | YudaoAiProperties.MiniMax properties = yudaoAiProperties.getMinimax(); |
| | | return buildMiniMaxChatClient(properties); |
| | | } |
| | | |
| | | public MiniMaxChatModel buildMiniMaxChatClient(YudaoAiProperties.MiniMax properties) { |
| | | if (StrUtil.isEmpty(properties.getModel())) { |
| | | properties.setModel(MiniMaxChatModel.MODEL_DEFAULT); |
| | | } |
| | | return new MiniMaxChatModel(buildDeepSeekCompatibleChatModel( |
| | | StrUtil.blankToDefault(properties.getBaseUrl(), MiniMaxChatModel.BASE_URL), |
| | | null, properties.getApiKey(), properties.getModel(), properties.getTemperature(), |
| | | properties.getMaxTokens(), properties.getTopP())); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.moonshot.enable", havingValue = "true") |
| | | public MoonshotChatModel moonshotChatClient(YudaoAiProperties yudaoAiProperties) { |
| | | YudaoAiProperties.Moonshot properties = yudaoAiProperties.getMoonshot(); |
| | | return buildMoonshotChatClient(properties); |
| | | } |
| | | |
| | | public MoonshotChatModel buildMoonshotChatClient(YudaoAiProperties.Moonshot properties) { |
| | | if (StrUtil.isEmpty(properties.getModel())) { |
| | | properties.setModel(MoonshotChatModel.MODEL_DEFAULT); |
| | | } |
| | | return new MoonshotChatModel(buildDeepSeekCompatibleChatModel( |
| | | StrUtil.blankToDefault(properties.getBaseUrl(), MoonshotChatModel.BASE_URL), |
| | | MoonshotChatModel.COMPLETE_PATH, properties.getApiKey(), properties.getModel(), |
| | | properties.getTemperature(), properties.getMaxTokens(), properties.getTopP())); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.stepfun.enable", havingValue = "true") |
| | | public StepFunChatModel stepFunChatClient(YudaoAiProperties yudaoAiProperties) { |
| | | YudaoAiProperties.StepFun properties = yudaoAiProperties.getStepfun(); |
| | | return buildStepFunChatClient(properties); |
| | | } |
| | | |
| | | public StepFunChatModel buildStepFunChatClient(YudaoAiProperties.StepFun properties) { |
| | | if (StrUtil.isEmpty(properties.getModel())) { |
| | | properties.setModel(StepFunChatModel.MODEL_DEFAULT); |
| | | } |
| | | return new StepFunChatModel(buildDeepSeekCompatibleChatModel( |
| | | StrUtil.blankToDefault(properties.getBaseUrl(), StepFunChatModel.BASE_URL), |
| | | StepFunChatModel.COMPLETE_PATH, properties.getApiKey(), properties.getModel(), |
| | | properties.getTemperature(), properties.getMaxTokens(), properties.getTopP())); |
| | | } |
| | | |
| | | private static DeepSeekChatModel buildDeepSeekCompatibleChatModel(String baseUrl, String completionsPath, |
| | | String apiKey, String model, |
| | | Double temperature, Integer maxTokens, |
| | | Double topP) { |
| | | DeepSeekApi.Builder apiBuilder = DeepSeekApi.builder() |
| | | .baseUrl(baseUrl) |
| | | .apiKey(apiKey); |
| | | if (StrUtil.isNotEmpty(completionsPath)) { |
| | | apiBuilder.completionsPath(completionsPath); |
| | | } |
| | | return DeepSeekChatModel.builder() |
| | | .deepSeekApi(apiBuilder.build()) |
| | | .options(DeepSeekChatOptions.builder() |
| | | .model(model) |
| | | .temperature(temperature) |
| | | .maxTokens(maxTokens) |
| | | .topP(topP) |
| | | .build()) |
| | | .build(); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true") |
| | | public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) { |
| | | YudaoAiProperties.Midjourney config = yudaoAiProperties.getMidjourney(); |
| | | return new MidjourneyApi(config.getBaseUrl(), config.getApiKey(), config.getNotifyUrl()); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.suno.enable", havingValue = "true") |
| | | public SunoApi sunoApi(YudaoAiProperties yudaoAiProperties) { |
| | | return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl()); |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.grok.enable", havingValue = "true") |
| | | public GrokChatModel grokChatClient(YudaoAiProperties yudaoAiProperties) { |
| | | YudaoAiProperties.Grok properties = yudaoAiProperties.getGrok(); |
| | | return buildGrokChatClient(properties); |
| | | } |
| | | |
| | | public GrokChatModel buildGrokChatClient(YudaoAiProperties.Grok properties) { |
| | | if (StrUtil.isEmpty(properties.getModel())) { |
| | | properties.setModel(GrokChatModel.MODEL_DEFAULT); |
| | | } |
| | | OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() |
| | | .options(OpenAiChatOptions.builder() |
| | | .baseUrl(StrUtil.blankToDefault(properties.getBaseUrl(), GrokChatModel.BASE_URL)) |
| | | .apiKey(properties.getApiKey()) |
| | | .model(properties.getModel()) |
| | | .temperature(properties.getTemperature()) |
| | | .maxTokens(properties.getMaxTokens()) |
| | | .topP(properties.getTopP()) |
| | | .build()) |
| | | .build(); |
| | | return new GrokChatModel(openAiChatModel); |
| | | } |
| | | |
| | | // ========== RAG ç¸å
³ ========== |
| | | |
| | | @Bean |
| | | public TokenCountEstimator tokenCountEstimator() { |
| | | return new JTokkitTokenCountEstimator(); |
| | | } |
| | | |
| | | @Bean |
| | | public BatchingStrategy batchingStrategy() { |
| | | return new TokenCountBatchingStrategy(); |
| | | } |
| | | |
| | | private static ToolCallingManager getToolCallingManager() { |
| | | return SpringUtil.getBean(ToolCallingManager.class); |
| | | } |
| | | |
| | | private static ObservationRegistry getObservationRegistry() { |
| | | return SpringUtil.getBean(ObservationRegistry.class); |
| | | } |
| | | |
| | | // ========== Web Search ç¸å
³ ========== |
| | | |
| | | @Bean |
| | | @ConditionalOnProperty(value = "yudao.ai.web-search.enable", havingValue = "true") |
| | | public AiWebSearchClient webSearchClient(YudaoAiProperties yudaoAiProperties) { |
| | | return new AiBoChaWebSearchClient(yudaoAiProperties.getWebSearch().getApiKey()); |
| | | @ConditionalOnMissingBean |
| | | public TokenCountEstimator tokenCountEstimator() { |
| | | return new JTokkitTokenCountEstimator(); |
| | | } |
| | | |
| | | // ========== MCP ç¸å
³ ========== |
| | | // ========== éä¹åé® Chatï¼éè¿ OpenAI å
¼å®¹æ¥å£ï¼========== |
| | | |
| | | /** |
| | | * åèèª <a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html">MCP Server Boot Starter</> |
| | | */ |
| | | @Bean |
| | | public List<ToolCallback> toolCallbacks(PersonService personService) { |
| | | return List.of(ToolCallbacks.from(personService)); |
| | | public static OpenAiChatModel buildTongYiChatModel(String apiKey, String model) { |
| | | return OpenAiChatModel.builder() |
| | | .options(OpenAiChatOptions.builder() |
| | | .baseUrl(DASHSCOPE_BASE_URL) |
| | | .apiKey(apiKey) |
| | | .model(StrUtil.blankToDefault(model, "qwen-plus")) |
| | | .temperature(0.7) |
| | | .build()) |
| | | .toolCallingManager(ToolCallingManager.builder().build()) |
| | | .build(); |
| | | } |
| | | |
| | | // ========== éä¹åé® Embeddingï¼éè¿ OpenAI å
¼å®¹æ¥å£ï¼========== |
| | | |
| | | public static OpenAiEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) { |
| | | return OpenAiEmbeddingModel.builder() |
| | | .options(OpenAiEmbeddingOptions.builder() |
| | | .baseUrl(DASHSCOPE_BASE_URL) |
| | | .apiKey(apiKey) |
| | | .model(StrUtil.blankToDefault(model, "text-embedding-v3")) |
| | | .build()) |
| | | .metadataMode(MetadataMode.EMBED) |
| | | .build(); |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | /** |
| | | * èé AI é
置类 |
| | | * |
| | | * @author fansili |
| | | * @since 1.0 |
| | | */ |
| | | @ConfigurationProperties(prefix = "yudao.ai") |
| | | @Data |
| | | public class YudaoAiProperties { |
| | | |
| | | /** |
| | | * åèè±å
|
| | | */ |
| | | private DouBao doubao; |
| | | |
| | | /** |
| | | * è
¾è®¯æ··å
|
| | | */ |
| | | private HunYuan hunyuan; |
| | | |
| | | /** |
| | | * ç¡
åºæµå¨ |
| | | */ |
| | | private SiliconFlow siliconflow; |
| | | |
| | | /** |
| | | * è®¯é£æç« |
| | | */ |
| | | private XingHuo xinghuo; |
| | | |
| | | /** |
| | | * ç¾å· |
| | | */ |
| | | private BaiChuan baichuan; |
| | | |
| | | /** |
| | | * æå¿ä¸è¨ |
| | | */ |
| | | private YiYan yiyan; |
| | | |
| | | /** |
| | | * æºè°± |
| | | */ |
| | | private ZhiPu zhipu; |
| | | |
| | | /** |
| | | * MiniMax |
| | | */ |
| | | private MiniMax minimax; |
| | | |
| | | /** |
| | | * æä¹æé¢ |
| | | */ |
| | | private Moonshot moonshot; |
| | | |
| | | /** |
| | | * é¶è·æè¾° |
| | | */ |
| | | private StepFun stepfun; |
| | | |
| | | /** |
| | | * Grok |
| | | */ |
| | | private Grok grok; |
| | | |
| | | /** |
| | | * Midjourney ç»å¾ |
| | | */ |
| | | private Midjourney midjourney; |
| | | |
| | | /** |
| | | * Suno é³ä¹ |
| | | */ |
| | | @SuppressWarnings("SpellCheckingInspection") |
| | | private Suno suno; |
| | | |
| | | /** |
| | | * ç½ç»æç´¢ |
| | | */ |
| | | private WebSearch webSearch; |
| | | |
| | | @Data |
| | | public static class DouBao { |
| | | |
| | | private String enable; |
| | | private String apiKey; |
| | | |
| | | private String model; |
| | | private Double temperature; |
| | | private Integer maxTokens; |
| | | private Double topP; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | public static class HunYuan { |
| | | |
| | | private String enable; |
| | | private String baseUrl; |
| | | private String apiKey; |
| | | |
| | | private String model; |
| | | private Double temperature; |
| | | private Integer maxTokens; |
| | | private Double topP; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | public static class SiliconFlow { |
| | | |
| | | private String enable; |
| | | private String apiKey; |
| | | |
| | | private String model; |
| | | private Double temperature; |
| | | private Integer maxTokens; |
| | | private Double topP; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | public static class XingHuo { |
| | | |
| | | private String enable; |
| | | private String apiKey; |
| | | |
| | | private String model; |
| | | private Double temperature; |
| | | private Integer maxTokens; |
| | | private Double topP; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | public static class BaiChuan { |
| | | |
| | | private String enable; |
| | | private String apiKey; |
| | | |
| | | private String model; |
| | | private Double temperature; |
| | | private Integer maxTokens; |
| | | private Double topP; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | public static class YiYan { |
| | | |
| | | private String enable; |
| | | private String baseUrl; |
| | | private String apiKey; |
| | | |
| | | private String model; |
| | | private Double temperature; |
| | | private Integer maxTokens; |
| | | private Double topP; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | public static class ZhiPu { |
| | | |
| | | private String enable; |
| | | private String baseUrl; |
| | | private String apiKey; |
| | | |
| | | private String model; |
| | | private Double temperature; |
| | | private Integer maxTokens; |
| | | private Double topP; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | public static class MiniMax { |
| | | |
| | | private String enable; |
| | | private String baseUrl; |
| | | private String apiKey; |
| | | |
| | | private String model; |
| | | private Double temperature; |
| | | private Integer maxTokens; |
| | | private Double topP; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | public static class Moonshot { |
| | | |
| | | private String enable; |
| | | private String baseUrl; |
| | | private String apiKey; |
| | | |
| | | private String model; |
| | | private Double temperature; |
| | | private Integer maxTokens; |
| | | private Double topP; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | public static class Midjourney { |
| | | |
| | | private String enable; |
| | | private String baseUrl; |
| | | |
| | | private String apiKey; |
| | | private String notifyUrl; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | public static class Suno { |
| | | |
| | | private boolean enable; |
| | | |
| | | private String baseUrl; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | public static class Grok { |
| | | |
| | | private String enable; |
| | | private String apiKey; |
| | | private String baseUrl; |
| | | |
| | | private String model; |
| | | private Double temperature; |
| | | private Integer maxTokens; |
| | | private Double topP; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | public static class StepFun { |
| | | |
| | | private String enable; |
| | | private String apiKey; |
| | | private String baseUrl; |
| | | |
| | | private String model; |
| | | private Double temperature; |
| | | private Integer maxTokens; |
| | | private Double topP; |
| | | |
| | | } |
| | | |
| | | @Data |
| | | public static class WebSearch { |
| | | |
| | | private boolean enable; |
| | | |
| | | private String apiKey; |
| | | |
| | | } |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.framework.ai.core.model; |
| | | |
| | | import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; |
| | | import org.springframework.ai.chat.model.ChatModel; |
| | | import org.springframework.ai.embedding.EmbeddingModel; |
| | | import org.springframework.ai.image.ImageModel; |
| | | import org.springframework.ai.vectorstore.VectorStore; |
| | | |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * AI Model 模åå·¥åçæ¥å£ç±» |
| | | * |
| | | * @author fansili |
| | | */ |
| | | public interface AiModelFactory { |
| | | |
| | | /** |
| | | * åºäºæå®é
ç½®ï¼è·å¾ ChatModel 对象 |
| | | * |
| | | * 妿ä¸åå¨ï¼åè¿è¡å建 |
| | | * |
| | | * @param platform å¹³å° |
| | | * @param apiKey API KEY |
| | | * @param url API URL |
| | | * @return ChatModel 对象 |
| | | */ |
| | | ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url); |
| | | ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url, String model); |
| | | |
| | | /** |
| | | * åºäºé»è®¤é
ç½®ï¼è·å¾ ChatModel 对象 |
| | | * |
| | | * é»è®¤é
ç½®ï¼æçæ¯å¨ application.yaml é
ç½®æä»¶ä¸ç spring.ai ç¸å
³çé
ç½® |
| | | * |
| | | * @param platform å¹³å° |
| | | * @return ChatModel 对象 |
| | | */ |
| | | ChatModel getDefaultChatModel(AiPlatformEnum platform); |
| | | |
| | | /** |
| | | * åºäºé»è®¤é
ç½®ï¼è·å¾ ImageModel 对象 |
| | | * |
| | | * é»è®¤é
ç½®ï¼æçæ¯å¨ application.yaml é
ç½®æä»¶ä¸ç spring.ai ç¸å
³çé
ç½® |
| | | * |
| | | * @param platform å¹³å° |
| | | * @return ImageModel 对象 |
| | | */ |
| | | ImageModel getDefaultImageModel(AiPlatformEnum platform); |
| | | |
| | | /** |
| | | * åºäºæå®é
ç½®ï¼è·å¾ ImageModel 对象 |
| | | * |
| | | * 妿ä¸åå¨ï¼åè¿è¡å建 |
| | | * |
| | | * @param platform å¹³å° |
| | | * @param apiKey API KEY |
| | | * @param url API URL |
| | | * @return ImageModel 对象 |
| | | */ |
| | | ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url); |
| | | |
| | | /** |
| | | * åºäºæå®é
ç½®ï¼è·å¾ MidjourneyApi 对象 |
| | | * |
| | | * 妿ä¸åå¨ï¼åè¿è¡å建 |
| | | * |
| | | * @param apiKey API KEY |
| | | * @param url API URL |
| | | * @return MidjourneyApi 对象 |
| | | */ |
| | | MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url); |
| | | |
| | | /** |
| | | * åºäºæå®é
ç½®ï¼è·å¾ SunoApi 对象 |
| | | * |
| | | * 妿ä¸åå¨ï¼åè¿è¡å建 |
| | | * |
| | | * @param apiKey API KEY |
| | | * @param url API URL |
| | | * @return SunoApi 对象 |
| | | */ |
| | | SunoApi getOrCreateSunoApi(String apiKey, String url); |
| | | |
| | | /** |
| | | * åºäºæå®é
ç½®ï¼è·å¾ EmbeddingModel 对象 |
| | | * |
| | | * 妿ä¸åå¨ï¼åè¿è¡å建 |
| | | * |
| | | * @param platform å¹³å° |
| | | * @param apiKey API KEY |
| | | * @param url API URL |
| | | * @param model 模å |
| | | * @return ChatModel 对象 |
| | | */ |
| | | EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model); |
| | | |
| | | /** |
| | | * åºäºæå®é
ç½®ï¼è·å¾ VectorStore 对象 |
| | | * |
| | | * 妿ä¸åå¨ï¼åè¿è¡å建 |
| | | * |
| | | * @param type åéåå¨ç±»å |
| | | * @param embeddingModel å鿍¡å |
| | | * @param metadataFields å
æ°æ®å段 |
| | | * @return VectorStore 对象 |
| | | */ |
| | | VectorStore getOrCreateVectorStore(Class<? extends VectorStore> type, |
| | | EmbeddingModel embeddingModel, |
| | | Map<String, Class<?>> metadataFields); |
| | |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | | /** |
| | | * ai 模åç web ç»ä»¶ç Configuration |
| | | * |
| | | * @author èéæºç |
| | | * AI 模åç web ç»ä»¶ Configuration |
| | | */ |
| | | @Configuration(proxyBeanMethods = false) |
| | | public class AiWebConfiguration { |
| | | |
| | | /** |
| | | * ai 模åç API åç» |
| | | */ |
| | | @Bean |
| | | public GroupedOpenApi aiGroupedOpenApi() { |
| | | return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("ai"); |
| | |
| | | package cn.iocoder.yudao.module.ai.service.knowledge; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentCreateListReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentPageReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.AiKnowledgeDocumentUpdateStatusReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeDocumentCreateReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.document.*; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; |
| | | import jakarta.validation.Valid; |
| | | |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; |
| | | |
| | | /** |
| | | * AI ç¥è¯åºææ¡£ Service æ¥å£ |
| | | * |
| | | * @author xiaoxin |
| | | */ |
| | | public interface AiKnowledgeDocumentService { |
| | | |
| | | /** |
| | | * åå»ºææ¡£ï¼åä¸ªï¼ |
| | | * |
| | | * @param createReqVO ææ¡£å建 Request VO |
| | | * @return ææ¡£ç¼å· |
| | | */ |
| | | Long createKnowledgeDocument(AiKnowledgeDocumentCreateReqVO createReqVO); |
| | | List<Long> createDocuments(@Valid AiKnowledgeDocumentCreateReqVO createReqVO); |
| | | |
| | | void updateDocument(@Valid AiKnowledgeDocumentUpdateReqVO updateReqVO); |
| | | |
| | | void deleteDocument(Long id); |
| | | |
| | | AiKnowledgeDocumentDO getDocument(Long id); |
| | | |
| | | AiKnowledgeDocumentDO validateDocumentExists(Long id); |
| | | |
| | | PageResult<AiKnowledgeDocumentDO> getDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO); |
| | | |
| | | void updateDocumentStatus(@Valid AiKnowledgeDocumentUpdateStatusReqVO updateStatusReqVO); |
| | | |
| | | List<AiKnowledgeSegmentProcessRespVO> getDocumentProcessingProgress(List<Long> documentIds); |
| | | |
| | | void processDocumentSegments(Long documentId); |
| | | |
| | | /** |
| | | * åå»ºææ¡£ï¼å¤ä¸ªï¼ |
| | | * é¢è§ææ¡£ååç»æï¼ä¸ä¿åï¼ |
| | | * |
| | | * @param createListReqVO æ¹éå建 Request VO |
| | | * @return ææ¡£ç¼å·å表 |
| | | * @param url ææ¡£ URL |
| | | * @param name ææ¡£åç§° |
| | | * @param segmentMaxTokens åæ®µæå¤§ Token æ° |
| | | * @return åååçåæ®µææ¬å表 |
| | | */ |
| | | List<Long> createKnowledgeDocumentList(AiKnowledgeDocumentCreateListReqVO createListReqVO); |
| | | |
| | | /** |
| | | * è·åææ¡£å页 |
| | | * |
| | | * @param pageReqVO å页忰 |
| | | * @return ææ¡£å页 |
| | | */ |
| | | PageResult<AiKnowledgeDocumentDO> getKnowledgeDocumentPage(AiKnowledgeDocumentPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * è·åææ¡£è¯¦æ
|
| | | * |
| | | * @param id ææ¡£ç¼å· |
| | | * @return ææ¡£è¯¦æ
|
| | | */ |
| | | AiKnowledgeDocumentDO getKnowledgeDocument(Long id); |
| | | |
| | | /** |
| | | * æ´æ°ææ¡£ |
| | | * |
| | | * @param reqVO æ´æ°ä¿¡æ¯ |
| | | */ |
| | | void updateKnowledgeDocument(AiKnowledgeDocumentUpdateReqVO reqVO); |
| | | |
| | | /** |
| | | * æ´æ°ææ¡£ç¶æ |
| | | * |
| | | * @param reqVO æ´æ°ç¶æä¿¡æ¯ |
| | | */ |
| | | void updateKnowledgeDocumentStatus(AiKnowledgeDocumentUpdateStatusReqVO reqVO); |
| | | |
| | | /** |
| | | * å é¤ææ¡£ |
| | | * |
| | | * @param id ææ¡£ç¼å· |
| | | */ |
| | | void deleteKnowledgeDocument(Long id); |
| | | |
| | | /** |
| | | * æ ¹æ®ç¥è¯åºç¼å·ï¼æ¹éå é¤ææ¡£ |
| | | * |
| | | * @param knowledgeId ç¥è¯åºç¼å· |
| | | */ |
| | | void deleteKnowledgeDocumentByKnowledgeId(Long knowledgeId); |
| | | |
| | | /** |
| | | * æ ¡éªææ¡£æ¯å¦åå¨ |
| | | * |
| | | * @param id ææ¡£ç¼å· |
| | | * @return ææ¡£ä¿¡æ¯ |
| | | */ |
| | | AiKnowledgeDocumentDO validateKnowledgeDocumentExists(Long id); |
| | | |
| | | /** |
| | | * 读å URL å
容 |
| | | * |
| | | * @param url URL |
| | | * @return å
容 |
| | | */ |
| | | String readUrl(String url); |
| | | |
| | | /** |
| | | * è·åææ¡£å表 |
| | | * |
| | | * @param ids ææ¡£ç¼å·å表 |
| | | * @return ææ¡£å表 |
| | | */ |
| | | List<AiKnowledgeDocumentDO> getKnowledgeDocumentList(Collection<Long> ids); |
| | | |
| | | /** |
| | | * æ ¹æ®ç¥è¯åºç¼å·è·åææ¡£å表 |
| | | * |
| | | * @param knowledgeId ç¥è¯åºç¼å· |
| | | * @return ææ¡£å表 |
| | | */ |
| | | List<AiKnowledgeDocumentDO> getKnowledgeDocumentListByKnowledgeId(Long knowledgeId); |
| | | |
| | | /** |
| | | * è·åææ¡£ Map |
| | | * |
| | | * @param ids ææ¡£ç¼å·å表 |
| | | * @return ææ¡£ Map |
| | | */ |
| | | default Map<Long, AiKnowledgeDocumentDO> getKnowledgeDocumentMap(Collection<Long> ids) { |
| | | return convertMap(getKnowledgeDocumentList(ids), AiKnowledgeDocumentDO::getId); |
| | | } |
| | | List<String> previewSplit(String url, String name, Integer segmentMaxTokens); |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.service.knowledge; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSaveReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.*; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; |
| | | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO; |
| | | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO; |
| | | import org.springframework.scheduling.annotation.Async; |
| | | |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; |
| | | |
| | | /** |
| | | * AI ç¥è¯åºæ®µè½ Service æ¥å£ |
| | | * |
| | | * @author xiaoxin |
| | | */ |
| | | public interface AiKnowledgeSegmentService { |
| | | |
| | | /** |
| | | * è·åç¥è¯åºæ®µè½è¯¦æ
|
| | | * |
| | | * @param id 段è½ç¼å· |
| | | * @return 段è½è¯¦æ
|
| | | */ |
| | | AiKnowledgeSegmentDO getKnowledgeSegment(Long id); |
| | | AiKnowledgeSegmentDO getSegment(Long id); |
| | | |
| | | /** |
| | | * è·åç¥è¯åºæ®µè½å表 |
| | | * |
| | | * @param ids 段è½ç¼å·å表 |
| | | * @return 段è½å表 |
| | | */ |
| | | List<AiKnowledgeSegmentDO> getKnowledgeSegmentList(Collection<Long> ids); |
| | | AiKnowledgeSegmentDO validateSegmentExists(Long id); |
| | | |
| | | /** |
| | | * è·åç¥è¯åºæ®µè½ Map |
| | | * |
| | | * @param ids 段è½ç¼å·å表 |
| | | * @return æ®µè½ Map |
| | | */ |
| | | default Map<Long, AiKnowledgeSegmentDO> getKnowledgeSegmentMap(Collection<Long> ids) { |
| | | return convertMap(getKnowledgeSegmentList(ids), AiKnowledgeSegmentDO::getId); |
| | | } |
| | | PageResult<AiKnowledgeSegmentDO> getSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * è·å段è½å页 |
| | | * |
| | | * @param pageReqVO å页æ¥è¯¢ |
| | | * @return ææ¡£å页 |
| | | */ |
| | | PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO); |
| | | Long createSegment(AiKnowledgeSegmentSaveReqVO saveReqVO); |
| | | |
| | | /** |
| | | * åºäº content å
容ï¼åçå建å¤ä¸ªæ®µè½ |
| | | * |
| | | * @param documentId ç¥è¯åºææ¡£ç¼å· |
| | | * @param content ææ¡£å
容 |
| | | */ |
| | | void createKnowledgeSegmentBySplitContent(Long documentId, String content); |
| | | void updateSegment(AiKnowledgeSegmentSaveReqVO saveReqVO); |
| | | |
| | | /** |
| | | * ã弿¥ãåºäº content å
容ï¼åçå建å¤ä¸ªæ®µè½ |
| | | * |
| | | * @param documentId ç¥è¯åºææ¡£ç¼å· |
| | | * @param content ææ¡£å
容 |
| | | */ |
| | | @Async |
| | | default void createKnowledgeSegmentBySplitContentAsync(Long documentId, String content) { |
| | | createKnowledgeSegmentBySplitContent(documentId, content); |
| | | } |
| | | void deleteSegment(Long id); |
| | | |
| | | /** |
| | | * å建ç¥è¯åºæ®µè½ |
| | | * |
| | | * @param createReqVO åå»ºä¿¡æ¯ |
| | | * @return 段è½ç¼å· |
| | | */ |
| | | Long createKnowledgeSegment(AiKnowledgeSegmentSaveReqVO createReqVO); |
| | | void updateSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO updateStatusReqVO); |
| | | |
| | | /** |
| | | * æ´æ°æ®µè½çå
容 |
| | | * |
| | | * @param reqVO æ´æ°å
容 |
| | | */ |
| | | void updateKnowledgeSegment(AiKnowledgeSegmentSaveReqVO reqVO); |
| | | void deleteSegmentsByDocumentId(Long documentId); |
| | | |
| | | /** |
| | | * æ´æ°æ®µè½çç¶æ |
| | | * |
| | | * @param reqVO æ´æ°å
容 |
| | | */ |
| | | void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO); |
| | | void saveSegments(List<AiKnowledgeSegmentDO> segments, Long knowledgeId, Long embeddingModelId); |
| | | |
| | | /** |
| | | * å é¤ç¥è¯åºæ®µè½ |
| | | * |
| | | * @param id 段è½ç¼å· |
| | | */ |
| | | void deleteKnowledgeSegment(Long id); |
| | | |
| | | /** |
| | | * éæ°ç´¢å¼ç¥è¯åºä¸çææææ¡£æ®µè½ |
| | | * |
| | | * @param knowledgeId ç¥è¯åºç¼å· |
| | | */ |
| | | void reindexKnowledgeSegmentByKnowledgeId(Long knowledgeId); |
| | | |
| | | /** |
| | | * ã弿¥ãéæ°ç´¢å¼ç¥è¯åºä¸çææææ¡£æ®µè½ |
| | | * |
| | | * @param knowledgeId ç¥è¯åºç¼å· |
| | | */ |
| | | @Async |
| | | default void reindexByKnowledgeIdAsync(Long knowledgeId) { |
| | | reindexKnowledgeSegmentByKnowledgeId(knowledgeId); |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®ææ¡£ç¼å·å 餿®µè½ |
| | | * |
| | | * @param documentId ææ¡£ç¼å· |
| | | */ |
| | | void deleteKnowledgeSegmentByDocumentId(Long documentId); |
| | | |
| | | /** |
| | | * æç´¢ç¥è¯åºæ®µè½ï¼å¹¶è¿åç»æ |
| | | * |
| | | * @param reqBO æç´¢è¯·æ±ä¿¡æ¯ |
| | | * @return æç´¢ç»ææ®µè½å表 |
| | | */ |
| | | List<AiKnowledgeSegmentSearchRespBO> searchKnowledgeSegment(AiKnowledgeSegmentSearchReqBO reqBO); |
| | | |
| | | /** |
| | | * æ ¹æ® URL å
容ï¼åçå建å¤ä¸ªæ®µè½ |
| | | * |
| | | * @param url URL å°å |
| | | * @param segmentMaxTokens æ®µè½æå¤§ Token æ° |
| | | * @return åçåçæ®µè½å表 |
| | | */ |
| | | List<AiKnowledgeSegmentDO> splitContent(String url, Integer segmentMaxTokens); |
| | | |
| | | /** |
| | | * è·åææ¡£å¤çè¿åº¦ï¼å¤ä¸ªï¼ |
| | | * |
| | | * @param documentIds ææ¡£ç¼å·å表 |
| | | * @return ææ¡£å¤çå表 |
| | | */ |
| | | List<AiKnowledgeSegmentProcessRespVO> getKnowledgeSegmentProcessList(List<Long> documentIds); |
| | | List<AiKnowledgeSegmentSearchRespBO> searchSegments(AiKnowledgeSegmentSearchReqBO searchReqBO); |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgeSaveReqVO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; |
| | | import jakarta.validation.Valid; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * AI ç¥è¯åº-åºç¡ä¿¡æ¯ Service æ¥å£ |
| | | * |
| | | * @author xiaoxin |
| | | */ |
| | | public interface AiKnowledgeService { |
| | | |
| | | /** |
| | | * å建ç¥è¯åº |
| | | * |
| | | * @param createReqVO åå»ºä¿¡æ¯ |
| | | * @return ç¼å· |
| | | */ |
| | | Long createKnowledge(AiKnowledgeSaveReqVO createReqVO); |
| | | Long createKnowledge(@Valid AiKnowledgeSaveReqVO createReqVO); |
| | | |
| | | /** |
| | | * æ´æ°ç¥è¯åº |
| | | * |
| | | * @param updateReqVO æ´æ°ä¿¡æ¯ |
| | | */ |
| | | void updateKnowledge(AiKnowledgeSaveReqVO updateReqVO); |
| | | void updateKnowledge(@Valid AiKnowledgeSaveReqVO updateReqVO); |
| | | |
| | | /** |
| | | * å é¤ç¥è¯åº |
| | | * |
| | | * @param id ç¥è¯åºç¼å· |
| | | */ |
| | | void deleteKnowledge(Long id); |
| | | |
| | | /** |
| | | * è·å¾ç¥è¯åº |
| | | * |
| | | * @param id ç¼å· |
| | | * @return ç¥è¯åº |
| | | */ |
| | | AiKnowledgeDO getKnowledge(Long id); |
| | | |
| | | /** |
| | | * æ ¡éªç¥è¯åºæ¯å¦åå¨ |
| | | * |
| | | * @param id è®°å½ç¼å· |
| | | */ |
| | | AiKnowledgeDO validateKnowledgeExists(Long id); |
| | | |
| | | /** |
| | | * è·å¾ç¥è¯åºå页 |
| | | * |
| | | * @param pageReqVO å页æ¥è¯¢ |
| | | * @return ç¥è¯åºå页 |
| | | */ |
| | | AiKnowledgeDO validateKnowledge(Long id); |
| | | |
| | | PageResult<AiKnowledgeDO> getKnowledgePage(AiKnowledgePageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * è·å¾æå®ç¶æçç¥è¯åºå表 |
| | | * |
| | | * @param status ç¶æ |
| | | * @return ç¥è¯åºå表 |
| | | */ |
| | | List<AiKnowledgeDO> getKnowledgeSimpleListByStatus(Integer status); |
| | | List<AiKnowledgeDO> getEnabledKnowledgeList(); |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.service.knowledge; |
| | | |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.knowledge.AiKnowledgePageReqVO; |
| | |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; |
| | | import cn.iocoder.yudao.module.ai.dal.mysql.knowledge.AiKnowledgeMapper; |
| | | import cn.iocoder.yudao.module.ai.enums.model.AiModelTypeEnum; |
| | | import cn.iocoder.yudao.module.ai.service.model.AiModelService; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.KNOWLEDGE_NOT_EXISTS; |
| | | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; |
| | | |
| | | /** |
| | | * AI ç¥è¯åº-åºç¡ä¿¡æ¯ Service å®ç°ç±» |
| | | * |
| | | * @author xiaoxin |
| | | */ |
| | | @Service |
| | | @Slf4j |
| | | public class AiKnowledgeServiceImpl implements AiKnowledgeService { |
| | | |
| | | @Resource |
| | | private AiKnowledgeMapper knowledgeMapper; |
| | | |
| | | private AiKnowledgeMapper mapper; |
| | | @Resource |
| | | private AiModelService modelService; |
| | | @Resource |
| | | private AiKnowledgeSegmentService knowledgeSegmentService; |
| | | @Resource |
| | | private AiKnowledgeDocumentService knowledgeDocumentService; |
| | | |
| | | @Override |
| | | public Long createKnowledge(AiKnowledgeSaveReqVO createReqVO) { |
| | | // 1. æ ¡éªæ¨¡åé
ç½® |
| | | AiModelDO model = modelService.validateModel(createReqVO.getEmbeddingModelId()); |
| | | |
| | | // 2. æå
¥ç¥è¯åº |
| | | AiKnowledgeDO knowledge = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class) |
| | | .setEmbeddingModel(model.getModel()); |
| | | knowledgeMapper.insert(knowledge); |
| | | AiModelDO embeddingModel = modelService.validateModel(createReqVO.getEmbeddingModelId()); |
| | | if (!AiModelTypeEnum.EMBEDDING.getType().equals(embeddingModel.getType())) { |
| | | throw exception(MODEL_USE_TYPE_ERROR); |
| | | } |
| | | AiKnowledgeDO knowledge = BeanUtils.toBean(createReqVO, AiKnowledgeDO.class); |
| | | knowledge.setEmbeddingModel(embeddingModel.getModel()); |
| | | if (knowledge.getTopK() == null) knowledge.setTopK(3); |
| | | if (knowledge.getSimilarityThreshold() == null) knowledge.setSimilarityThreshold(0.7); |
| | | mapper.insert(knowledge); |
| | | return knowledge.getId(); |
| | | } |
| | | |
| | | @Override |
| | | public void updateKnowledge(AiKnowledgeSaveReqVO updateReqVO) { |
| | | // 1.1 æ ¡éªç¥è¯åºåå¨ |
| | | AiKnowledgeDO oldKnowledge = validateKnowledgeExists(updateReqVO.getId()); |
| | | // 1.2 æ ¡éªæ¨¡åé
ç½® |
| | | AiModelDO model = modelService.validateModel(updateReqVO.getEmbeddingModelId()); |
| | | |
| | | // 2. æ´æ°ç¥è¯åº |
| | | AiKnowledgeDO updateObj = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class) |
| | | .setEmbeddingModel(model.getModel()); |
| | | knowledgeMapper.updateById(updateObj); |
| | | |
| | | // 3. å¦ææ¨¡åååï¼éè¦ reindex ææçææ¡£ |
| | | if (ObjUtil.notEqual(oldKnowledge.getEmbeddingModelId(), updateReqVO.getEmbeddingModelId())) { |
| | | knowledgeSegmentService.reindexByKnowledgeIdAsync(updateReqVO.getId()); |
| | | validateKnowledgeExists(updateReqVO.getId()); |
| | | AiModelDO embeddingModel = modelService.validateModel(updateReqVO.getEmbeddingModelId()); |
| | | if (!AiModelTypeEnum.EMBEDDING.getType().equals(embeddingModel.getType())) { |
| | | throw exception(MODEL_USE_TYPE_ERROR); |
| | | } |
| | | AiKnowledgeDO knowledge = BeanUtils.toBean(updateReqVO, AiKnowledgeDO.class); |
| | | knowledge.setEmbeddingModel(embeddingModel.getModel()); |
| | | mapper.updateById(knowledge); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteKnowledge(Long id) { |
| | | // 1. æ ¡éªåå¨ |
| | | validateKnowledgeExists(id); |
| | | |
| | | // 2. å é¤ç¥è¯åºä¸çææææ¡£åæ®µè½ |
| | | knowledgeDocumentService.deleteKnowledgeDocumentByKnowledgeId(id); |
| | | |
| | | // 3. å é¤ç¥è¯åº |
| | | // ç¹æ®ï¼ç¥è¯åºéè¦æåå é¤ï¼ä¸ç¶ç¸å
³çé
ç½®ä¼æ¾ä¸å° |
| | | knowledgeMapper.deleteById(id); |
| | | mapper.deleteById(id); |
| | | } |
| | | |
| | | @Override |
| | | public AiKnowledgeDO getKnowledge(Long id) { |
| | | return knowledgeMapper.selectById(id); |
| | | return mapper.selectById(id); |
| | | } |
| | | |
| | | @Override |
| | | public AiKnowledgeDO validateKnowledgeExists(Long id) { |
| | | AiKnowledgeDO knowledge = knowledgeMapper.selectById(id); |
| | | if (knowledge == null) { |
| | | throw exception(KNOWLEDGE_NOT_EXISTS); |
| | | } |
| | | AiKnowledgeDO knowledge = mapper.selectById(id); |
| | | if (knowledge == null) throw exception(KNOWLEDGE_NOT_EXISTS); |
| | | return knowledge; |
| | | } |
| | | |
| | | @Override |
| | | public AiKnowledgeDO validateKnowledge(Long id) { |
| | | AiKnowledgeDO knowledge = validateKnowledgeExists(id); |
| | | if (CommonStatusEnum.isDisable(knowledge.getStatus())) throw exception(KNOWLEDGE_NOT_EXISTS); |
| | | return knowledge; |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<AiKnowledgeDO> getKnowledgePage(AiKnowledgePageReqVO pageReqVO) { |
| | | return knowledgeMapper.selectPage(pageReqVO); |
| | | return mapper.selectPage(pageReqVO); |
| | | } |
| | | |
| | | @Override |
| | | public List<AiKnowledgeDO> getKnowledgeSimpleListByStatus(Integer status) { |
| | | return knowledgeMapper.selectListByStatus(status); |
| | | public List<AiKnowledgeDO> getEnabledKnowledgeList() { |
| | | return mapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); |
| | | } |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.service.knowledge.bo; |
| | | |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | import javax.validation.constraints.NotNull; |
| | | |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | |
| | | /** |
| | | * AI ç¥è¯åºæ®µè½æç´¢ Request BO |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | @Data |
| | | public class AiKnowledgeSegmentSearchReqBO { |
| | | |
| | | /** |
| | | * ç¥è¯åºç¼å· |
| | | */ |
| | | @NotNull(message = "ç¥è¯åºç¼å·ä¸è½ä¸ºç©º") |
| | | private Long knowledgeId; |
| | | |
| | | /** |
| | | * å
容 |
| | | */ |
| | | @NotEmpty(message = "å
容ä¸è½ä¸ºç©º") |
| | | private String content; |
| | | |
| | | /** |
| | | * æå¤§è¿åæ°é |
| | | */ |
| | | private Integer topK; |
| | | |
| | | /** |
| | | * ç¸ä¼¼åº¦éå¼ |
| | | */ |
| | | private Double similarityThreshold; |
| | | |
| | | } |
| | | } |
| | |
| | | |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * AI ç¥è¯åºæ®µè½æç´¢ Response BO |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | @Data |
| | | public class AiKnowledgeSegmentSearchRespBO { |
| | | |
| | | /** |
| | | * 段è½ç¼å· |
| | | */ |
| | | private Long id; |
| | | /** |
| | | * ææ¡£ç¼å· |
| | | */ |
| | | |
| | | private Long documentId; |
| | | /** |
| | | * ç¥è¯åºç¼å· |
| | | */ |
| | | |
| | | private String documentName; |
| | | |
| | | private Long knowledgeId; |
| | | |
| | | /** |
| | | * å
容 |
| | | */ |
| | | private String content; |
| | | /** |
| | | * å
容é¿åº¦ |
| | | */ |
| | | |
| | | private Integer contentLength; |
| | | |
| | | /** |
| | | * Token æ°é |
| | | */ |
| | | private Integer tokens; |
| | | |
| | | /** |
| | | * ç¸ä¼¼åº¦åæ° |
| | | */ |
| | | private Double score; |
| | | |
| | | } |
| | | } |
| | |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.ai.transformer.splitter.TextSplitter; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | |
| | | |
| | | /** |
| | | * Markdown QA æ ¼å¼ä¸ç¨åçå¨ |
| | | * |
| | | * <p>åè½ç¹ç¹ï¼ |
| | | * <ul> |
| | | * <li>è¯å«äºçº§æ é¢ï¼## ï¼ä½ä¸ºé®é¢æ è®°</li> |
| | | * <li>ç QA å¯¹ä¿æå®æ´ï¼ä¸è¶
è¿ Token éå¶ï¼</li> |
| | | * <li>é¿çæ¡æºè½ååï¼æ¯ä¸ªç段ä¿ç宿´é®é¢ä½ä¸ºä¸ä¸æ</li> |
| | | * <li>æ¯æèªå®ä¹ Token ä¼°ç®å¨</li> |
| | | * </ul> |
| | | * |
| | | * @author runzhen |
| | | * è¯å«äºçº§æ é¢ï¼## ï¼ä½ä¸ºé®é¢æ è®°ï¼ä¿æé®ç坹宿´æ§ |
| | | */ |
| | | @Slf4j |
| | | @SuppressWarnings("SizeReplaceableByIsEmpty") |
| | | public class MarkdownQaSplitter extends TextSplitter { |
| | | |
| | | /** |
| | | * äºçº§æ 颿£åï¼å¹é
"## " å¼å¤´çè¡ |
| | | */ |
| | | private static final Pattern H2_PATTERN = Pattern.compile("^##\\s+(.+)$", Pattern.MULTILINE); |
| | | |
| | | /** |
| | | * 段è½åé符ï¼åæ¢è¡ |
| | | */ |
| | | private static final String PARAGRAPH_SEPARATOR = "\n\n"; |
| | | |
| | | /** |
| | | * å¥ååé符 |
| | | */ |
| | | private static final Pattern SENTENCE_PATTERN = Pattern.compile("[ãï¼ï¼.!?]\\s*"); |
| | | |
| | | /** |
| | | * åæ®µçæå¤§ Token æ° |
| | | */ |
| | | private final int chunkSize; |
| | | |
| | | /** |
| | | * Token ä¼°ç®å¨ï¼ç®åå®ç°ï¼ä¸ææå符æ°ï¼è±ææåè¯æ°ç 1.3 åï¼ |
| | | */ |
| | | private final TokenEstimator tokenEstimator; |
| | | |
| | | public MarkdownQaSplitter(int chunkSize) { |
| | |
| | | |
| | | @Override |
| | | protected List<String> splitText(String text) { |
| | | if (StrUtil.isEmpty(text)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | // è§£æ QA 对 |
| | | if (StrUtil.isEmpty(text)) return Collections.emptyList(); |
| | | List<QaPair> qaPairs = parseQaPairs(text); |
| | | if (CollUtil.isEmpty(qaPairs)) { |
| | | // å¦ææ²¡æè¯å«å° QA æ ¼å¼ï¼ææ®µè½åå |
| | | return fallbackSplit(text); |
| | | } |
| | | |
| | | // å¤çæ¯ä¸ª QA 对 |
| | | if (CollUtil.isEmpty(qaPairs)) return fallbackSplit(text); |
| | | List<String> result = new ArrayList<>(); |
| | | for (QaPair qaPair : qaPairs) { |
| | | result.addAll(splitQaPair(qaPair)); |
| | | } |
| | | for (QaPair qaPair : qaPairs) result.addAll(splitQaPair(qaPair)); |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * è§£æ Markdown QA 对 |
| | | * |
| | | * @param content ææ¬å
容 |
| | | * @return QA 对å表 |
| | | */ |
| | | private List<QaPair> parseQaPairs(String content) { |
| | | // æ¾å°ææäºçº§æ é¢ä½ç½® |
| | | List<QaPair> qaPairs = new ArrayList<>(); |
| | | List<Integer> headingPositions = new ArrayList<>(); |
| | | List<String> questions = new ArrayList<>(); |
| | | Matcher matcher = H2_PATTERN.matcher(content); |
| | | while (matcher.find()) { |
| | | headingPositions.add(matcher.start()); |
| | | questions.add(matcher.group(1).trim()); |
| | | } |
| | | if (CollUtil.isEmpty(headingPositions)) { |
| | | return qaPairs; |
| | | } |
| | | |
| | | // æåæ¯ä¸ª QA 对 |
| | | while (matcher.find()) { headingPositions.add(matcher.start()); questions.add(matcher.group(1).trim()); } |
| | | if (CollUtil.isEmpty(headingPositions)) return qaPairs; |
| | | for (int i = 0; i < headingPositions.size(); i++) { |
| | | int start = headingPositions.get(i); |
| | | int end = (i + 1 < headingPositions.size()) |
| | | ? headingPositions.get(i + 1) |
| | | : content.length(); |
| | | String qaText = content.substring(start, end).trim(); |
| | | int end = (i + 1 < headingPositions.size()) ? headingPositions.get(i + 1) : content.length(); |
| | | String fullText = content.substring(start, end).trim(); |
| | | String question = questions.get(i); |
| | | // æåçæ¡é¨åï¼å»æé®é¢æ é¢ï¼ |
| | | String answer = qaText.substring(qaText.indexOf('\n') + 1).trim(); |
| | | qaPairs.add(new QaPair(question, answer, qaText)); |
| | | String answer = fullText.substring(fullText.indexOf('\n') + 1).trim(); |
| | | qaPairs.add(new QaPair(question, answer, fullText)); |
| | | } |
| | | return qaPairs; |
| | | } |
| | | |
| | | /** |
| | | * ååå个 QA 对 |
| | | * |
| | | * @param qaPair QA 对 |
| | | * @return åååçææ¬ç段å表 |
| | | */ |
| | | private List<String> splitQaPair(QaPair qaPair) { |
| | | // 妿æ´ä¸ª QA 对ä¸è¶
è¿éå¶ï¼ä¿æå®æ´ |
| | | int qaTokens = tokenEstimator.estimate(qaPair.fullText); |
| | | if (qaTokens <= chunkSize) return Collections.singletonList(qaPair.fullText); |
| | | List<String> chunks = new ArrayList<>(); |
| | | String fullQa = qaPair.fullText; |
| | | int qaTokens = tokenEstimator.estimate(fullQa); |
| | | if (qaTokens <= chunkSize) { |
| | | chunks.add(fullQa); |
| | | return chunks; |
| | | } |
| | | |
| | | // é¿çæ¡éè¦åå |
| | | log.debug("QA 对è¶
è¿ Token éå¶ ({} > {})ï¼å¼å§æºè½åå: {}", qaTokens, chunkSize, qaPair.question); |
| | | List<String> answerChunks = splitLongAnswer(qaPair.answer, qaPair.question); |
| | | for (String answerChunk : answerChunks) { |
| | | // æ¯ä¸ªç段é½å
å«å®æ´é®é¢ |
| | | String chunkText = "## " + qaPair.question + "\n" + answerChunk; |
| | | chunks.add(chunkText); |
| | | chunks.add("## " + qaPair.question + "\n" + answerChunk); |
| | | } |
| | | return chunks; |
| | | } |
| | | |
| | | /** |
| | | * ååé¿çæ¡ |
| | | * |
| | | * @param answer çæ¡ææ¬ |
| | | * @param question é®é¢ææ¬ |
| | | * @return åååççæ¡çæ®µå表 |
| | | */ |
| | | private List<String> splitLongAnswer(String answer, String question) { |
| | | List<String> chunks = new ArrayList<>(); |
| | | // é¢çé®é¢ç Token ç©ºé´ |
| | | String questionHeader = "## " + question + "\n"; |
| | | int questionTokens = tokenEstimator.estimate(questionHeader); |
| | | int availableTokens = chunkSize - questionTokens - 10; // é¢ç 10 个 Token çç¼å² |
| | | |
| | | // å
ææ®µè½åå |
| | | int availableTokens = chunkSize - questionTokens - 10; |
| | | String[] paragraphs = answer.split(PARAGRAPH_SEPARATOR); |
| | | StringBuilder currentChunk = new StringBuilder(); |
| | | int currentTokens = 0; |
| | | for (String paragraph : paragraphs) { |
| | | if (StrUtil.isEmpty(paragraph)) { |
| | | continue; |
| | | } |
| | | if (StrUtil.isEmpty(paragraph)) continue; |
| | | int paragraphTokens = tokenEstimator.estimate(paragraph); |
| | | // 妿å个段è½å°±è¶
è¿éå¶ï¼éè¦æå¥ååå |
| | | if (paragraphTokens > availableTokens) { |
| | | // å
ä¿åå½åå |
| | | if (currentChunk.length() > 0) { |
| | | chunks.add(currentChunk.toString().trim()); |
| | | currentChunk = new StringBuilder(); |
| | | currentTokens = 0; |
| | | } |
| | | // æå¥åååé¿æ®µè½ |
| | | if (currentChunk.length() > 0) { chunks.add(currentChunk.toString().trim()); currentChunk = new StringBuilder(); currentTokens = 0; } |
| | | chunks.addAll(splitLongParagraph(paragraph, availableTokens)); |
| | | continue; |
| | | } |
| | | // 妿å ä¸è¿ä¸ªæ®µè½ä¼è¶
è¿éå¶ |
| | | if (currentTokens + paragraphTokens > availableTokens && currentChunk.length() > 0) { |
| | | chunks.add(currentChunk.toString().trim()); |
| | | currentChunk = new StringBuilder(); |
| | | currentTokens = 0; |
| | | } |
| | | if (currentChunk.length() > 0) { |
| | | currentChunk.append("\n\n"); |
| | | } |
| | | // æ·»å æ®µè½ |
| | | if (currentTokens + paragraphTokens > availableTokens && currentChunk.length() > 0) { chunks.add(currentChunk.toString().trim()); currentChunk = new StringBuilder(); currentTokens = 0; } |
| | | if (currentChunk.length() > 0) currentChunk.append("\n\n"); |
| | | currentChunk.append(paragraph); |
| | | currentTokens += paragraphTokens; |
| | | } |
| | | |
| | | // æ·»å æåä¸å |
| | | if (currentChunk.length() > 0) { |
| | | chunks.add(currentChunk.toString().trim()); |
| | | } |
| | | if (currentChunk.length() > 0) chunks.add(currentChunk.toString().trim()); |
| | | return CollUtil.isEmpty(chunks) ? Collections.singletonList(answer) : chunks; |
| | | } |
| | | |
| | | /** |
| | | * ååé¿æ®µè½ï¼æå¥åï¼ |
| | | * |
| | | * @param paragraph æ®µè½ææ¬ |
| | | * @param availableTokens å¯ç¨ç Token æ° |
| | | * @return åååçææ¬ç段å表 |
| | | */ |
| | | private List<String> splitLongParagraph(String paragraph, int availableTokens) { |
| | | // æå¥ååå |
| | | List<String> chunks = new ArrayList<>(); |
| | | String[] sentences = SENTENCE_PATTERN.split(paragraph); |
| | | |
| | | // æå¥å累积åå |
| | | StringBuilder currentChunk = new StringBuilder(); |
| | | int currentTokens = 0; |
| | | for (String sentence : sentences) { |
| | | if (StrUtil.isEmpty(sentence)) { |
| | | continue; |
| | | } |
| | | if (StrUtil.isEmpty(sentence)) continue; |
| | | int sentenceTokens = tokenEstimator.estimate(sentence); |
| | | // 妿å个å¥åå°±è¶
è¿éå¶ï¼å¼ºå¶åå |
| | | if (sentenceTokens > availableTokens) { |
| | | if (currentChunk.length() > 0) { |
| | | chunks.add(currentChunk.toString().trim()); |
| | | currentChunk = new StringBuilder(); |
| | | currentTokens = 0; |
| | | } |
| | | chunks.add(sentence.trim()); |
| | | continue; |
| | | } |
| | | // 妿å ä¸è¿ä¸ªå¥åä¼è¶
è¿éå¶ |
| | | if (currentTokens + sentenceTokens > availableTokens && currentChunk.length() > 0) { |
| | | chunks.add(currentChunk.toString().trim()); |
| | | currentChunk = new StringBuilder(); |
| | | currentTokens = 0; |
| | | } |
| | | // æ·»å å¥å |
| | | if (sentenceTokens > availableTokens) { if (currentChunk.length() > 0) { chunks.add(currentChunk.toString().trim()); currentChunk = new StringBuilder(); currentTokens = 0; } chunks.add(sentence.trim()); continue; } |
| | | if (currentTokens + sentenceTokens > availableTokens && currentChunk.length() > 0) { chunks.add(currentChunk.toString().trim()); currentChunk = new StringBuilder(); currentTokens = 0; } |
| | | currentChunk.append(sentence); |
| | | currentTokens += sentenceTokens; |
| | | } |
| | | |
| | | // æ·»å æåä¸å |
| | | if (currentChunk.length() > 0) { |
| | | chunks.add(currentChunk.toString().trim()); |
| | | } |
| | | if (currentChunk.length() > 0) chunks.add(currentChunk.toString().trim()); |
| | | return chunks.isEmpty() ? Collections.singletonList(paragraph) : chunks; |
| | | } |
| | | |
| | | /** |
| | | * é级ååçç¥ï¼å½æªè¯å«å° QA æ ¼å¼æ¶ï¼ |
| | | * |
| | | * @param content ææ¬å
容 |
| | | * @return åååçææ¬ç段å表 |
| | | */ |
| | | private List<String> fallbackSplit(String content) { |
| | | // ææ®µè½åå |
| | | List<String> chunks = new ArrayList<>(); |
| | | String[] paragraphs = content.split(PARAGRAPH_SEPARATOR); |
| | | |
| | | // ææ®µè½ç´¯ç§¯åå |
| | | StringBuilder currentChunk = new StringBuilder(); |
| | | int currentTokens = 0; |
| | | for (String paragraph : paragraphs) { |
| | | if (StrUtil.isEmpty(paragraph)) { |
| | | continue; |
| | | } |
| | | if (StrUtil.isEmpty(paragraph)) continue; |
| | | int paragraphTokens = tokenEstimator.estimate(paragraph); |
| | | // 妿å ä¸è¿ä¸ªæ®µè½ä¼è¶
è¿éå¶ |
| | | if (currentTokens + paragraphTokens > chunkSize && currentChunk.length() > 0) { |
| | | chunks.add(currentChunk.toString().trim()); |
| | | currentChunk = new StringBuilder(); |
| | | currentTokens = 0; |
| | | } |
| | | // æ·»å æ®µè½ |
| | | if (currentChunk.length() > 0) { |
| | | currentChunk.append("\n\n"); |
| | | } |
| | | if (currentTokens + paragraphTokens > chunkSize && currentChunk.length() > 0) { chunks.add(currentChunk.toString().trim()); currentChunk = new StringBuilder(); currentTokens = 0; } |
| | | if (currentChunk.length() > 0) currentChunk.append("\n\n"); |
| | | currentChunk.append(paragraph); |
| | | currentTokens += paragraphTokens; |
| | | } |
| | | |
| | | // æ·»å æåä¸å |
| | | if (currentChunk.length() > 0) { |
| | | chunks.add(currentChunk.toString().trim()); |
| | | } |
| | | if (currentChunk.length() > 0) chunks.add(currentChunk.toString().trim()); |
| | | return chunks.isEmpty() ? Collections.singletonList(content) : chunks; |
| | | } |
| | | |
| | | /** |
| | | * QA å¯¹æ°æ®ç»æ |
| | | */ |
| | | @AllArgsConstructor |
| | | private static class QaPair { |
| | | private static class QaPair { String question; String answer; String fullText; } |
| | | |
| | | String question; |
| | | String answer; |
| | | String fullText; |
| | | public interface TokenEstimator { int estimate(String text); } |
| | | |
| | | } |
| | | |
| | | /** |
| | | * Token ä¼°ç®å¨æ¥å£ |
| | | */ |
| | | public interface TokenEstimator { |
| | | |
| | | int estimate(String text); |
| | | |
| | | } |
| | | |
| | | /** |
| | | * ç®åç Token ä¼°ç®å¨å®ç° |
| | | * 䏿ï¼1 å符 â 1 Token |
| | | * è±æï¼1 åè¯ â 1.3 Token |
| | | */ |
| | | private static class SimpleTokenEstimator implements TokenEstimator { |
| | | |
| | | @Override |
| | | public int estimate(String text) { |
| | | if (StrUtil.isEmpty(text)) { |
| | | return 0; |
| | | } |
| | | |
| | | int chineseChars = 0; |
| | | int englishWords = 0; |
| | | // ç®åç»è®¡ä¸è±æ |
| | | for (char c : text.toCharArray()) { |
| | | if (c >= 0x4E00 && c <= 0x9FA5) { |
| | | chineseChars++; |
| | | } |
| | | } |
| | | // è±æåè¯ä¼°ç® |
| | | String[] words = text.split("\\s+"); |
| | | for (String word : words) { |
| | | if (word.matches(".*[a-zA-Z].*")) { |
| | | englishWords++; |
| | | } |
| | | } |
| | | return chineseChars + (int) (englishWords * 1.3); |
| | | if (StrUtil.isEmpty(text)) return 0; |
| | | int chineseChars = 0, englishWords = 0; |
| | | for (char c : text.toCharArray()) { if (c >= 0x4E00 && c <= 0x9FA5) chineseChars++; } |
| | | for (String word : text.split("\\s+")) { if (word.matches(".*[a-zA-Z].*")) englishWords++; } |
| | | return chineseChars + (int)(englishWords * 1.3); |
| | | } |
| | | } |
| | | |
| | | } |
| | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.ai.transformer.splitter.TextSplitter; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | |
| | | |
| | | /** |
| | | * è¯ä¹åææ¬åçå¨ |
| | | * |
| | | * <p>åè½ç¹ç¹ï¼ |
| | | * <ul> |
| | | * <li>ä¼å
卿®µè½è¾¹çï¼åæ¢è¡ï¼å¤åå</li> |
| | | * <li>å
¶æ¬¡å¨å¥åè¾¹çï¼å¥å·ãé®å·ãæå¹å·ï¼å¤åå</li> |
| | | * <li>é¿å
å¨å¥åä¸é´æªæï¼ä¿æè¯ä¹å®æ´æ§</li> |
| | | * <li>æ¯æä¸è±ææ ç¹ç¬¦å·è¯å«</li> |
| | | * </ul> |
| | | * |
| | | * @author runzhen |
| | | * ä¼å
卿®µè½è¾¹çå¤ååï¼å
¶æ¬¡å¨å¥åè¾¹çå¤ååï¼ä¿æè¯ä¹å®æ´æ§ |
| | | */ |
| | | @Slf4j |
| | | public class SemanticTextSplitter extends TextSplitter { |
| | | |
| | | /** |
| | | * åæ®µçæå¤§ Token æ° |
| | | */ |
| | | private final int chunkSize; |
| | | |
| | | /** |
| | | * 段è½éå 大å°ï¼ç¨äºä¿æä¸ä¸æè¿è´¯æ§ï¼ |
| | | */ |
| | | private final int chunkOverlap; |
| | | |
| | | /** |
| | | * 段è½åéç¬¦ï¼æä¼å
级æåºï¼ |
| | | */ |
| | | private static final List<String> PARAGRAPH_SEPARATORS = Arrays.asList( |
| | | "\n\n\n", // ä¸ä¸ªæ¢è¡ |
| | | "\n\n", // åæ¢è¡ |
| | | "\n" // åæ¢è¡ |
| | | ); |
| | | |
| | | /** |
| | | * å¥åç»ææ è®°ï¼ä¸è±ææ ç¹ï¼ |
| | | */ |
| | | private static final Pattern SENTENCE_END_PATTERN = Pattern.compile( |
| | | "[ãï¼ï¼.!?]+[\\s\"'ï¼)ã\\]]*" |
| | | ); |
| | | |
| | | /** |
| | | * Token ä¼°ç®å¨ |
| | | */ |
| | | private static final List<String> PARAGRAPH_SEPARATORS = Arrays.asList("\n\n\n", "\n\n", "\n"); |
| | | private static final Pattern SENTENCE_END_PATTERN = Pattern.compile("[ãï¼ï¼.!?]+[\\s\"'ï¼)ã\\]]*"); |
| | | private final MarkdownQaSplitter.TokenEstimator tokenEstimator; |
| | | |
| | | public SemanticTextSplitter(int chunkSize, int chunkOverlap) { |
| | | this.chunkSize = chunkSize; |
| | | this.chunkOverlap = Math.min(chunkOverlap, chunkSize / 2); // éå ä¸è¶
è¿ä¸å |
| | | this.chunkOverlap = Math.min(chunkOverlap, chunkSize / 2); |
| | | this.tokenEstimator = new SimpleTokenEstimator(); |
| | | } |
| | | |
| | | public SemanticTextSplitter(int chunkSize) { |
| | | this(chunkSize, 50); // é»è®¤éå 50 个 Token |
| | | } |
| | | public SemanticTextSplitter(int chunkSize) { this(chunkSize, 50); } |
| | | |
| | | @Override |
| | | protected List<String> splitText(String text) { |
| | | if (StrUtil.isEmpty(text)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | if (StrUtil.isEmpty(text)) return Collections.emptyList(); |
| | | return splitTextRecursive(text); |
| | | } |
| | | |
| | | /** |
| | | * ååææ¬ï¼éå½çç¥ï¼ |
| | | * |
| | | * @param text å¾
ååææ¬ |
| | | * @return åååçææ¬åå表 |
| | | */ |
| | | private List<String> splitTextRecursive(String text) { |
| | | List<String> chunks = new ArrayList<>(); |
| | | |
| | | // å¦æææ¬ä¸è¶
è¿éå¶ï¼ç´æ¥è¿å |
| | | int textTokens = tokenEstimator.estimate(text); |
| | | if (textTokens <= chunkSize) { |
| | | chunks.add(text.trim()); |
| | | return chunks; |
| | | } |
| | | if (textTokens <= chunkSize) return Collections.singletonList(text.trim()); |
| | | |
| | | // å°è¯æä¸ååé符åå |
| | | List<String> splits = null; |
| | | String usedSeparator = null; |
| | | for (String separator : PARAGRAPH_SEPARATORS) { |
| | | if (text.contains(separator)) { |
| | | splits = Arrays.asList(text.split(Pattern.quote(separator))); |
| | | usedSeparator = separator; |
| | | break; |
| | | } |
| | | if (text.contains(separator)) { splits = Arrays.asList(text.split(Pattern.quote(separator))); usedSeparator = separator; break; } |
| | | } |
| | | |
| | | // å¦ææ²¡ææ¾å°æ®µè½åéç¬¦ï¼æå¥ååå |
| | | if (splits == null || splits.size() == 1) { |
| | | splits = splitBySentences(text); |
| | | usedSeparator = ""; // å¥åååä¸éè¦åé符 |
| | | } |
| | | |
| | | // åå¹¶å°ç段 |
| | | chunks = mergeSplits(splits, usedSeparator); |
| | | return chunks; |
| | | if (splits == null || splits.size() == 1) { splits = splitBySentences(text); usedSeparator = ""; } |
| | | return mergeSplits(splits, usedSeparator); |
| | | } |
| | | |
| | | /** |
| | | * æå¥ååå |
| | | * |
| | | * @param text å¾
ååææ¬ |
| | | * @return å¥åå表 |
| | | */ |
| | | private List<String> splitBySentences(String text) { |
| | | // ä½¿ç¨æ£å表达å¼å¹é
å¥åç»æä½ç½® |
| | | List<String> sentences = new ArrayList<>(); |
| | | int lastEnd = 0; |
| | | Matcher matcher = SENTENCE_END_PATTERN.matcher(text); |
| | | while (matcher.find()) { |
| | | String sentence = text.substring(lastEnd, matcher.end()).trim(); |
| | | if (StrUtil.isNotEmpty(sentence)) { |
| | | sentences.add(sentence); |
| | | } |
| | | if (StrUtil.isNotEmpty(sentence)) sentences.add(sentence); |
| | | lastEnd = matcher.end(); |
| | | } |
| | | |
| | | // æ·»å å©ä½é¨å |
| | | if (lastEnd < text.length()) { |
| | | String remaining = text.substring(lastEnd).trim(); |
| | | if (StrUtil.isNotEmpty(remaining)) { |
| | | sentences.add(remaining); |
| | | } |
| | | } |
| | | if (lastEnd < text.length()) { String remaining = text.substring(lastEnd).trim(); if (StrUtil.isNotEmpty(remaining)) sentences.add(remaining); } |
| | | return sentences.isEmpty() ? Collections.singletonList(text) : sentences; |
| | | } |
| | | |
| | | /** |
| | | * åå¹¶åååçå°ç段 |
| | | * |
| | | * @param splits åååççæ®µå表 |
| | | * @param separator çæ®µé´çåé符 |
| | | * @return åå¹¶åçææ¬åå表 |
| | | */ |
| | | private List<String> mergeSplits(List<String> splits, String separator) { |
| | | List<String> chunks = new ArrayList<>(); |
| | | List<String> currentChunks = new ArrayList<>(); |
| | | int currentLength = 0; |
| | | |
| | | for (String split : splits) { |
| | | if (StrUtil.isEmpty(split)) { |
| | | continue; |
| | | } |
| | | if (StrUtil.isEmpty(split)) continue; |
| | | int splitTokens = tokenEstimator.estimate(split); |
| | | // 妿åä¸ªçæ®µå°±è¶
è¿éå¶ï¼è¿ä¸æ¥éå½åå |
| | | if (splitTokens > chunkSize) { |
| | | // å
ä¿åå½å累积çå |
| | | if (!currentChunks.isEmpty()) { |
| | | String chunkText = String.join(separator, currentChunks); |
| | | chunks.add(chunkText.trim()); |
| | | currentChunks.clear(); |
| | | currentLength = 0; |
| | | } |
| | | // éå½ååå¤§çæ®µ |
| | | if (!separator.isEmpty()) { |
| | | // å¦ææ¯æ®µè½åé符ï¼å°è¯æå¥ååå |
| | | chunks.addAll(splitTextRecursive(split)); |
| | | } else { |
| | | // å¦æå·²ç»æ¯å¥å级å«ï¼å¼ºå¶æå符åå |
| | | chunks.addAll(forceSplitLongText(split)); |
| | | } |
| | | if (!currentChunks.isEmpty()) { chunks.add(String.join(separator, currentChunks).trim()); currentChunks.clear(); currentLength = 0; } |
| | | if (!separator.isEmpty()) { chunks.addAll(splitTextRecursive(split)); } else { chunks.addAll(forceSplitLongText(split)); } |
| | | continue; |
| | | } |
| | | // 计ç®å ä¸åé符ç Token æ° |
| | | int separatorTokens = StrUtil.isEmpty(separator) ? 0 : tokenEstimator.estimate(separator); |
| | | // 妿å ä¸è¿ä¸ªç段ä¼è¶
è¿éå¶ |
| | | if (!currentChunks.isEmpty() && currentLength + splitTokens + separatorTokens > chunkSize) { |
| | | // ä¿åå½åå |
| | | String chunkText = String.join(separator, currentChunks); |
| | | chunks.add(chunkText.trim()); |
| | | |
| | | // å¤çéå ï¼ä¿çæåå ä¸ªçæ®µ |
| | | chunks.add(String.join(separator, currentChunks).trim()); |
| | | currentChunks = getOverlappingChunks(currentChunks, separator); |
| | | currentLength = estimateTokens(currentChunks, separator); |
| | | } |
| | | // æ·»å å½åçæ®µ |
| | | currentChunks.add(split); |
| | | currentLength += splitTokens + separatorTokens; |
| | | } |
| | | |
| | | // æ·»å æåä¸å |
| | | if (!currentChunks.isEmpty()) { |
| | | String chunkText = String.join(separator, currentChunks); |
| | | chunks.add(chunkText.trim()); |
| | | } |
| | | if (!currentChunks.isEmpty()) chunks.add(String.join(separator, currentChunks).trim()); |
| | | return chunks; |
| | | } |
| | | |
| | | /** |
| | | * è·åéå ççæ®µï¼ç¨äºä¿æä¸ä¸æï¼ |
| | | * |
| | | * @param chunks å½åçæ®µå表 |
| | | * @param separator çæ®µé´çåé符 |
| | | * @return éå ççæ®µå表 |
| | | */ |
| | | private List<String> getOverlappingChunks(List<String> chunks, String separator) { |
| | | if (chunkOverlap == 0 || chunks.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | // ä»åå¾ååçæ®µï¼ç´å°è¾¾å°éå å¤§å° |
| | | if (chunkOverlap == 0 || chunks.isEmpty()) return new ArrayList<>(); |
| | | List<String> overlapping = new ArrayList<>(); |
| | | int tokens = 0; |
| | | for (int i = chunks.size() - 1; i >= 0; i--) { |
| | | String chunk = chunks.get(i); |
| | | int chunkTokens = tokenEstimator.estimate(chunk); |
| | | if (tokens + chunkTokens > chunkOverlap) { |
| | | break; |
| | | } |
| | | // æ·»å å°éå å表å端 |
| | | if (tokens + chunkTokens > chunkOverlap) break; |
| | | overlapping.add(0, chunk); |
| | | tokens += chunkTokens + (StrUtil.isEmpty(separator) ? 0 : tokenEstimator.estimate(separator)); |
| | | } |
| | | return overlapping; |
| | | } |
| | | |
| | | /** |
| | | * ä¼°ç®ç段åè¡¨çæ» Token æ° |
| | | * |
| | | * @param chunks çæ®µå表 |
| | | * @param separator çæ®µé´çåé符 |
| | | * @return æ» Token æ° |
| | | */ |
| | | private int estimateTokens(List<String> chunks, String separator) { |
| | | int total = 0; |
| | | for (int i = 0; i < chunks.size(); i++) { |
| | | total += tokenEstimator.estimate(chunks.get(i)); |
| | | if (i < chunks.size() - 1 && StrUtil.isNotEmpty(separator)) { |
| | | total += tokenEstimator.estimate(separator); |
| | | } |
| | | } |
| | | for (int i = 0; i < chunks.size(); i++) { total += tokenEstimator.estimate(chunks.get(i)); if (i < chunks.size() - 1 && StrUtil.isNotEmpty(separator)) total += tokenEstimator.estimate(separator); } |
| | | return total; |
| | | } |
| | | |
| | | /** |
| | | * 强å¶ååé¿ææ¬ï¼å½è¯ä¹åå失败æ¶ï¼ |
| | | * |
| | | * @param text å¾
ååææ¬ |
| | | * @return åååçææ¬åå表 |
| | | */ |
| | | private List<String> forceSplitLongText(String text) { |
| | | List<String> chunks = new ArrayList<>(); |
| | | int charsPerChunk = (int) (chunkSize * 0.8); // ä¿å®ä¼°è®¡ |
| | | for (int i = 0; i < text.length(); i += charsPerChunk) { |
| | | int end = Math.min(i + charsPerChunk, text.length()); |
| | | String chunk = text.substring(i, end); |
| | | chunks.add(chunk.trim()); |
| | | } |
| | | log.warn("ææ¬è¿é¿ï¼å·²å¼ºå¶æå符ååï¼å¯è½å½±åè¯ä¹å®æ´æ§"); |
| | | int charsPerChunk = (int)(chunkSize * 0.8); |
| | | for (int i = 0; i < text.length(); i += charsPerChunk) { int end = Math.min(i + charsPerChunk, text.length()); chunks.add(text.substring(i, end).trim()); } |
| | | return chunks; |
| | | } |
| | | |
| | | /** |
| | | * ç®åç Token ä¼°ç®å¨å®ç° |
| | | */ |
| | | private static class SimpleTokenEstimator implements MarkdownQaSplitter.TokenEstimator { |
| | | |
| | | @Override |
| | | public int estimate(String text) { |
| | | if (StrUtil.isEmpty(text)) { |
| | | return 0; |
| | | } |
| | | |
| | | int chineseChars = 0; |
| | | int englishWords = 0; |
| | | // ç®åç»è®¡ä¸è±æ |
| | | for (char c : text.toCharArray()) { |
| | | if (c >= 0x4E00 && c <= 0x9FA5) { |
| | | chineseChars++; |
| | | } |
| | | } |
| | | // è±æåè¯ä¼°ç® |
| | | String[] words = text.split("\\s+"); |
| | | for (String word : words) { |
| | | if (word.matches(".*[a-zA-Z].*")) { |
| | | englishWords++; |
| | | } |
| | | } |
| | | return chineseChars + (int) (englishWords * 1.3); |
| | | if (StrUtil.isEmpty(text)) return 0; |
| | | int chineseChars = 0, englishWords = 0; |
| | | for (char c : text.toCharArray()) { if (c >= 0x4E00 && c <= 0x9FA5) chineseChars++; } |
| | | for (String word : text.split("\\s+")) { if (word.matches(".*[a-zA-Z].*")) englishWords++; } |
| | | return chineseChars + (int)(englishWords * 1.3); |
| | | } |
| | | } |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeyPageReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.apikey.AiApiKeySaveReqVO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; |
| | | import jakarta.validation.Valid; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * AI API å¯é¥ Service æ¥å£ |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | public interface AiApiKeyService { |
| | | |
| | | /** |
| | | * å建 API å¯é¥ |
| | | * |
| | | * @param createReqVO åå»ºä¿¡æ¯ |
| | | * @return ç¼å· |
| | | */ |
| | | Long createApiKey(@Valid AiApiKeySaveReqVO createReqVO); |
| | | Long createApiKey(AiApiKeySaveReqVO createReqVO); |
| | | |
| | | /** |
| | | * æ´æ° API å¯é¥ |
| | | * |
| | | * @param updateReqVO æ´æ°ä¿¡æ¯ |
| | | */ |
| | | void updateApiKey(@Valid AiApiKeySaveReqVO updateReqVO); |
| | | void updateApiKey(AiApiKeySaveReqVO updateReqVO); |
| | | |
| | | /** |
| | | * å é¤ API å¯é¥ |
| | | * |
| | | * @param id ç¼å· |
| | | */ |
| | | void deleteApiKey(Long id); |
| | | |
| | | /** |
| | | * è·å¾ API å¯é¥ |
| | | * |
| | | * @param id ç¼å· |
| | | * @return API å¯é¥ |
| | | */ |
| | | AiApiKeyDO getApiKey(Long id); |
| | | |
| | | /** |
| | | * æ ¡éª API å¯é¥ |
| | | * |
| | | * @param id æ¯é£å¥½ |
| | | * @return API å¯é¥ |
| | | */ |
| | | AiApiKeyDO validateApiKey(Long id); |
| | | |
| | | /** |
| | | * è·å¾ API å¯é¥å页 |
| | | * |
| | | * @param pageReqVO å页æ¥è¯¢ |
| | | * @return API å¯é¥å页 |
| | | */ |
| | | PageResult<AiApiKeyDO> getApiKeyPage(AiApiKeyPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * è·å¾ API å¯é¥å表 |
| | | * |
| | | * @return API å¯é¥å表 |
| | | */ |
| | | List<AiApiKeyDO> getApiKeyList(); |
| | | |
| | | /** |
| | | * è·å¾é»è®¤ç API å¯é¥ |
| | | * |
| | | * @param platform å¹³å° |
| | | * @param status ç¶æ |
| | | * @return API å¯é¥ |
| | | */ |
| | | AiApiKeyDO getRequiredDefaultApiKey(String platform, Integer status); |
| | | |
| | | } |
| | | PageResult<AiApiKeyDO> getApiKeyPage(AiApiKeyPageReqVO pageReqVO); |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.ai.dal.mysql.model.AiApiKeyMapper; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.util.List; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.API_KEY_DISABLE; |
| | | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.API_KEY_NOT_EXISTS; |
| | | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; |
| | | |
| | | /** |
| | | * AI API å¯é¥ Service å®ç°ç±» |
| | | * |
| | | * @author èéæºç |
| | | */ |
| | | @Service |
| | | @Validated |
| | | public class AiApiKeyServiceImpl implements AiApiKeyService { |
| | | |
| | | @Resource |
| | | private AiApiKeyMapper apiKeyMapper; |
| | | private AiApiKeyMapper mapper; |
| | | |
| | | @Override |
| | | public Long createApiKey(AiApiKeySaveReqVO createReqVO) { |
| | | // æå
¥ |
| | | AiApiKeyDO apiKey = BeanUtils.toBean(createReqVO, AiApiKeyDO.class); |
| | | apiKeyMapper.insert(apiKey); |
| | | // è¿å |
| | | return apiKey.getId(); |
| | | AiApiKeyDO key = BeanUtils.toBean(createReqVO, AiApiKeyDO.class); |
| | | mapper.insert(key); |
| | | return key.getId(); |
| | | } |
| | | |
| | | @Override |
| | | public void updateApiKey(AiApiKeySaveReqVO updateReqVO) { |
| | | // æ ¡éªåå¨ |
| | | validateApiKeyExists(updateReqVO.getId()); |
| | | // æ´æ° |
| | | AiApiKeyDO updateObj = BeanUtils.toBean(updateReqVO, AiApiKeyDO.class); |
| | | apiKeyMapper.updateById(updateObj); |
| | | mapper.updateById(BeanUtils.toBean(updateReqVO, AiApiKeyDO.class)); |
| | | } |
| | | |
| | | @Override |
| | | public void deleteApiKey(Long id) { |
| | | // æ ¡éªåå¨ |
| | | validateApiKeyExists(id); |
| | | // å é¤ |
| | | apiKeyMapper.deleteById(id); |
| | | } |
| | | |
| | | private AiApiKeyDO validateApiKeyExists(Long id) { |
| | | AiApiKeyDO apiKey = apiKeyMapper.selectById(id); |
| | | if (apiKey == null) { |
| | | throw exception(API_KEY_NOT_EXISTS); |
| | | } |
| | | return apiKey; |
| | | mapper.deleteById(id); |
| | | } |
| | | |
| | | @Override |
| | | public AiApiKeyDO getApiKey(Long id) { |
| | | return apiKeyMapper.selectById(id); |
| | | return mapper.selectById(id); |
| | | } |
| | | |
| | | @Override |
| | | public AiApiKeyDO validateApiKey(Long id) { |
| | | AiApiKeyDO apiKey = validateApiKeyExists(id); |
| | | if (CommonStatusEnum.isDisable(apiKey.getStatus())) { |
| | | throw exception(API_KEY_DISABLE); |
| | | } |
| | | return apiKey; |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<AiApiKeyDO> getApiKeyPage(AiApiKeyPageReqVO pageReqVO) { |
| | | return apiKeyMapper.selectPage(pageReqVO); |
| | | } |
| | | |
| | | @Override |
| | | public List<AiApiKeyDO> getApiKeyList() { |
| | | return apiKeyMapper.selectList(); |
| | | AiApiKeyDO key = mapper.selectById(id); |
| | | if (key == null) throw exception(API_KEY_NOT_EXISTS); |
| | | if (CommonStatusEnum.isDisable(key.getStatus())) throw exception(API_KEY_DISABLE); |
| | | return key; |
| | | } |
| | | |
| | | @Override |
| | | public AiApiKeyDO getRequiredDefaultApiKey(String platform, Integer status) { |
| | | AiApiKeyDO apiKey = apiKeyMapper.selectFirstByPlatformAndStatus(platform, status); |
| | | if (apiKey == null) { |
| | | throw exception(API_KEY_NOT_EXISTS); |
| | | } |
| | | return apiKey; |
| | | return mapper.selectFirstByPlatformAndStatus(platform, status); |
| | | } |
| | | |
| | | } |
| | | @Override |
| | | public PageResult<AiApiKeyDO> getApiKeyPage(AiApiKeyPageReqVO pageReqVO) { |
| | | return mapper.selectPage(pageReqVO); |
| | | } |
| | | |
| | | private void validateApiKeyExists(Long id) { |
| | | if (mapper.selectById(id) == null) throw exception(API_KEY_NOT_EXISTS); |
| | | } |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.service.model; |
| | | |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelPageReqVO; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; |
| | | import dev.tinyflow.core.Tinyflow; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.ai.chat.model.ChatModel; |
| | | import org.springframework.ai.image.ImageModel; |
| | | import org.springframework.ai.vectorstore.VectorStore; |
| | | |
| | | import javax.annotation.Nullable; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * AI 模å Service æ¥å£ |
| | | * |
| | | * @author fansili |
| | | * @since 2024/4/24 19:42 |
| | | */ |
| | | public interface AiModelService { |
| | | |
| | | /** |
| | | * å建模å |
| | | * |
| | | * @param createReqVO åå»ºä¿¡æ¯ |
| | | * @return ç¼å· |
| | | */ |
| | | Long createModel(@Valid AiModelSaveReqVO createReqVO); |
| | | |
| | | /** |
| | | * æ´æ°æ¨¡å |
| | | * |
| | | * @param updateReqVO æ´æ°ä¿¡æ¯ |
| | | */ |
| | | void updateModel(@Valid AiModelSaveReqVO updateReqVO); |
| | | |
| | | /** |
| | | * å 餿¨¡å |
| | | * |
| | | * @param id ç¼å· |
| | | */ |
| | | void deleteModel(Long id); |
| | | |
| | | /** |
| | | * è·å¾æ¨¡å |
| | | * |
| | | * @param id ç¼å· |
| | | * @return 模å |
| | | */ |
| | | AiModelDO getModel(Long id); |
| | | |
| | | /** |
| | | * è·å¾é»è®¤ç模å |
| | | * |
| | | * 妿è·åä¸å°ï¼åæåº {@link cn.iocoder.yudao.framework.common.exception.ServiceException} ä¸å¡å¼å¸¸ |
| | | * |
| | | * @return 模å |
| | | */ |
| | | AiModelDO getRequiredDefaultModel(Integer type); |
| | | |
| | | /** |
| | | * è·å¾æ¨¡åå页 |
| | | * |
| | | * @param pageReqVO å页æ¥è¯¢ |
| | | * @return 模åå页 |
| | | */ |
| | | PageResult<AiModelDO> getModelPage(AiModelPageReqVO pageReqVO); |
| | | |
| | | /** |
| | | * æ ¡éªæ¨¡åæ¯å¦å¯ä½¿ç¨ |
| | | * |
| | | * @param id ç¼å· |
| | | * @return 模å |
| | | */ |
| | | AiModelDO validateModel(Long id); |
| | | |
| | | /** |
| | | * è·å¾æ¨¡åå表 |
| | | * |
| | | * @param status ç¶æ |
| | | * @param type ç±»å |
| | | * @param platform å¹³å°ï¼å
许空 |
| | | * @return 模åå表 |
| | | */ |
| | | List<AiModelDO> getModelListByStatusAndType(Integer status, Integer type, |
| | | @Nullable String platform); |
| | | List<AiModelDO> getModelListByStatusAndType(Integer status, Integer type, @Nullable String platform); |
| | | |
| | | // ========== ä¸ Spring AI éæ ========== |
| | | |
| | | /** |
| | | * è·å¾ ChatModel 对象 |
| | | * |
| | | * @param id ç¼å· |
| | | * @return ChatModel 对象 |
| | | */ |
| | | ChatModel getChatModel(Long id); |
| | | |
| | | /** |
| | | * è·å¾ ImageModel 对象 |
| | | * |
| | | * @param id ç¼å· |
| | | * @return ImageModel 对象 |
| | | */ |
| | | ImageModel getImageModel(Long id); |
| | | |
| | | /** |
| | | * è·å¾ MidjourneyApi 对象 |
| | | * |
| | | * @param id ç¼å· |
| | | * @return MidjourneyApi 对象 |
| | | */ |
| | | MidjourneyApi getMidjourneyApi(Long id); |
| | | |
| | | /** |
| | | * è·å¾ SunoApi 对象 |
| | | * |
| | | * @return SunoApi 对象 |
| | | */ |
| | | SunoApi getSunoApi(); |
| | | |
| | | /** |
| | | * è·å¾ VectorStore 对象 |
| | | * |
| | | * @param id ç¼å· |
| | | * @param metadataFields å
æ°æ®çå®ä¹ |
| | | * @return VectorStore 对象 |
| | | */ |
| | | VectorStore getOrCreateVectorStore(Long id, Map<String, Class<?>> metadataFields); |
| | | |
| | | /** |
| | | * è·å TinyFlow æé LLm Provider |
| | | * |
| | | * @param tinyflow tinyflow |
| | | * @param modelId AI 模å ID |
| | | */ |
| | | void getLLmProvider4Tinyflow(Tinyflow tinyflow, Long modelId); |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.ai.service.model; |
| | | |
| | | import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactory; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | |
| | | import cn.iocoder.yudao.module.ai.controller.admin.model.vo.model.AiModelSaveReqVO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiApiKeyDO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; |
| | | import cn.iocoder.yudao.module.ai.dal.mysql.model.AiChatMapper; |
| | | import com.agentsflex.llm.ollama.OllamaLlm; |
| | | import com.agentsflex.llm.ollama.OllamaLlmConfig; |
| | | import com.agentsflex.llm.qwen.QwenLlm; |
| | | import com.agentsflex.llm.qwen.QwenLlmConfig; |
| | | import dev.tinyflow.core.Tinyflow; |
| | | import cn.iocoder.yudao.module.ai.dal.mysql.model.AiModelMapper; |
| | | import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; |
| | | import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactory; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.ai.chat.model.ChatModel; |
| | | import org.springframework.ai.embedding.EmbeddingModel; |
| | | import org.springframework.ai.image.ImageModel; |
| | | import org.springframework.ai.vectorstore.SimpleVectorStore; |
| | | import org.springframework.ai.vectorstore.VectorStore; |
| | | import org.springframework.ai.vectorstore.milvus.MilvusVectorStore; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.*; |
| | | |
| | | /** |
| | | * AI 模å Service å®ç°ç±» |
| | | * |
| | | * @author fansili |
| | | */ |
| | | @Service |
| | | @Validated |
| | | public class AiModelServiceImpl implements AiModelService { |
| | | |
| | | @Resource |
| | | private AiApiKeyService apiKeyService; |
| | | |
| | | @Resource |
| | | private AiChatMapper modelMapper; |
| | | |
| | | private AiModelMapper modelMapper; |
| | | @Resource |
| | | private AiModelFactory modelFactory; |
| | | |
| | | @Override |
| | | public Long createModel(AiModelSaveReqVO createReqVO) { |
| | | // 1. æ ¡éª |
| | | AiPlatformEnum.validatePlatform(createReqVO.getPlatform()); |
| | | apiKeyService.validateApiKey(createReqVO.getKeyId()); |
| | | |
| | | // 2. æå
¥ |
| | | AiModelDO model = BeanUtils.toBean(createReqVO, AiModelDO.class); |
| | | modelMapper.insert(model); |
| | | return model.getId(); |
| | |
| | | |
| | | @Override |
| | | public void updateModel(AiModelSaveReqVO updateReqVO) { |
| | | // 1. æ ¡éª |
| | | validateModelExists(updateReqVO.getId()); |
| | | AiPlatformEnum.validatePlatform(updateReqVO.getPlatform()); |
| | | apiKeyService.validateApiKey(updateReqVO.getKeyId()); |
| | | |
| | | // 2. æ´æ° |
| | | AiModelDO updateObj = BeanUtils.toBean(updateReqVO, AiModelDO.class); |
| | | modelMapper.updateById(updateObj); |
| | | modelMapper.updateById(BeanUtils.toBean(updateReqVO, AiModelDO.class)); |
| | | } |
| | | |
| | | @Override |
| | | public void deleteModel(Long id) { |
| | | // æ ¡éªåå¨ |
| | | validateModelExists(id); |
| | | // å é¤ |
| | | modelMapper.deleteById(id); |
| | | } |
| | | |
| | | private AiModelDO validateModelExists(Long id) { |
| | | AiModelDO model = modelMapper.selectById(id); |
| | | if (modelMapper.selectById(id) == null) { |
| | | throw exception(MODEL_NOT_EXISTS); |
| | | } |
| | | return model; |
| | | } |
| | | |
| | | @Override |
| | |
| | | @Override |
| | | public AiModelDO getRequiredDefaultModel(Integer type) { |
| | | AiModelDO model = modelMapper.selectFirstByStatus(type, CommonStatusEnum.ENABLE.getStatus()); |
| | | if (model == null) { |
| | | throw exception(MODEL_DEFAULT_NOT_EXISTS); |
| | | } |
| | | if (model == null) throw exception(MODEL_DISABLE); |
| | | return model; |
| | | } |
| | | |
| | |
| | | @Override |
| | | public AiModelDO validateModel(Long id) { |
| | | AiModelDO model = validateModelExists(id); |
| | | if (CommonStatusEnum.isDisable(model.getStatus())) { |
| | | throw exception(MODEL_DISABLE); |
| | | } |
| | | if (CommonStatusEnum.isDisable(model.getStatus())) throw exception(MODEL_DISABLE); |
| | | return model; |
| | | } |
| | | |
| | | @Override |
| | | public List<AiModelDO> getModelListByStatusAndType(Integer status, Integer type, String platform) { |
| | | return modelMapper.selectListByStatusAndType(status, type, platform); |
| | | } |
| | | |
| | | private AiModelDO validateModelExists(Long id) { |
| | | AiModelDO model = modelMapper.selectById(id); |
| | | if (model == null) throw exception(MODEL_NOT_EXISTS); |
| | | return model; |
| | | } |
| | | |
| | | // ========== ä¸ Spring AI éæ ========== |
| | |
| | | AiModelDO model = validateModel(id); |
| | | AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); |
| | | AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); |
| | | return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl()); |
| | | } |
| | | |
| | | @Override |
| | | public ImageModel getImageModel(Long id) { |
| | | AiModelDO model = validateModel(id); |
| | | AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); |
| | | AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); |
| | | return modelFactory.getOrCreateImageModel(platform, apiKey.getApiKey(), apiKey.getUrl()); |
| | | } |
| | | |
| | | @Override |
| | | public MidjourneyApi getMidjourneyApi(Long id) { |
| | | AiModelDO model = validateModel(id); |
| | | AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); |
| | | return modelFactory.getOrCreateMidjourneyApi(apiKey.getApiKey(), apiKey.getUrl()); |
| | | } |
| | | |
| | | @Override |
| | | public SunoApi getSunoApi() { |
| | | AiApiKeyDO apiKey = apiKeyService.getRequiredDefaultApiKey( |
| | | AiPlatformEnum.SUNO.getPlatform(), CommonStatusEnum.ENABLE.getStatus()); |
| | | return modelFactory.getOrCreateSunoApi(apiKey.getApiKey(), apiKey.getUrl()); |
| | | return modelFactory.getOrCreateChatModel(platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel()); |
| | | } |
| | | |
| | | @Override |
| | | public VectorStore getOrCreateVectorStore(Long id, Map<String, Class<?>> metadataFields) { |
| | | // è·å模å + å¯é¥ |
| | | AiModelDO model = validateModel(id); |
| | | AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); |
| | | AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); |
| | | |
| | | // å建æè·å EmbeddingModel 对象 |
| | | EmbeddingModel embeddingModel = modelFactory.getOrCreateEmbeddingModel( |
| | | platform, apiKey.getApiKey(), apiKey.getUrl(), model.getModel()); |
| | | |
| | | // å建æè·å VectorStore 对象 |
| | | return modelFactory.getOrCreateVectorStore(SimpleVectorStore.class, embeddingModel, metadataFields); |
| | | // return modelFactory.getOrCreateVectorStore(QdrantVectorStore.class, embeddingModel, metadataFields); |
| | | // return modelFactory.getOrCreateVectorStore(RedisVectorStore.class, embeddingModel, metadataFields); |
| | | // return modelFactory.getOrCreateVectorStore(MilvusVectorStore.class, embeddingModel, metadataFields); |
| | | } |
| | | |
| | | // TODO @lesanï¼æ¯ä¸æ¯è¿å Llm 对象ä¼å¥½ç¹åï¼ |
| | | @Override |
| | | public void getLLmProvider4Tinyflow(Tinyflow tinyflow, Long modelId) { |
| | | AiModelDO model = validateModel(modelId); |
| | | AiApiKeyDO apiKey = apiKeyService.validateApiKey(model.getKeyId()); |
| | | AiPlatformEnum platform = AiPlatformEnum.validatePlatform(apiKey.getPlatform()); |
| | | switch (platform) { |
| | | // TODO @lesan èèå°æªæ¥ä¸éè¦ä½¿ç¨agents-flex ç°å¨ä»
æµè¯éä¹åé® |
| | | // TODO @lesanï¼ãéè¦ãæ¯ä¸æ¯å¯ä»¥å®ç°ä¸ä¸ª SpringAiLlmï¼è¿æ ·çè¯ï¼å
é¨å
¨é¨ç¨å®å°±å¥½äºãåªå®ç° chat é¨åï¼è¿æ ·ï¼å°±æ flex ä½ä¸ºä¸ä¸ª agent æ¡æ¶ï¼å
é¨è°ç¨ï¼è¿æ¯ spring ai ç¸å
³çãææ¬å¯è½ä½ä¸ç¹ï¼ï¼ |
| | | case TONG_YI: |
| | | QwenLlmConfig qwenLlmConfig = new QwenLlmConfig(); |
| | | qwenLlmConfig.setApiKey(apiKey.getApiKey()); |
| | | qwenLlmConfig.setModel(model.getModel()); |
| | | // TODO @lesanï¼è¿ä¸ªæç¹å¥æªããã妿ä¸ä¸ªé¾å¼éï¼æå¤ä¸ªæ¨¡åï¼åæ´åããã |
| | | tinyflow.setLlmProvider(id -> new QwenLlm(qwenLlmConfig)); |
| | | break; |
| | | case OLLAMA: |
| | | OllamaLlmConfig ollamaLlmConfig = new OllamaLlmConfig(); |
| | | ollamaLlmConfig.setEndpoint(apiKey.getUrl()); |
| | | ollamaLlmConfig.setModel(model.getModel()); |
| | | tinyflow.setLlmProvider(id -> new OllamaLlm(ollamaLlmConfig)); |
| | | break; |
| | | } |
| | | return modelFactory.getOrCreateVectorStore(MilvusVectorStore.class, embeddingModel, metadataFields); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.controller.admin.oa; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.OaNoticePageReqVO; |
| | | import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.OaNoticeRespVO; |
| | | import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.OaNoticeSaveReqVO; |
| | | import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.OaNoticeDO; |
| | | import cn.iocoder.yudao.module.bpm.service.oa.OaNoticeService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | |
| | | @Tag(name = "管çåå° - OA éç¥å
Œ") |
| | | @RestController |
| | | @RequestMapping("/bpm/oa/notice") |
| | | @Validated |
| | | public class BpmOANoticeController { |
| | | |
| | | @Resource |
| | | private OaNoticeService oaNoticeService; |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "å建éç¥å
Œ") |
| | | @PreAuthorize("@ss.hasPermission('bpm:oa-notice:create')") |
| | | public CommonResult<Long> createNotice(@Valid @RequestBody OaNoticeSaveReqVO createReqVO) { |
| | | return success(oaNoticeService.createNotice(getLoginUserId(), createReqVO)); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Operation(summary = "ä¿®æ¹éç¥å
Œ") |
| | | @PreAuthorize("@ss.hasPermission('bpm:oa-notice:update')") |
| | | public CommonResult<Boolean> updateNotice(@Valid @RequestBody OaNoticeSaveReqVO updateReqVO) { |
| | | oaNoticeService.updateNotice(updateReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "å é¤éç¥å
Œ") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true, example = "1024") |
| | | @PreAuthorize("@ss.hasPermission('bpm:oa-notice:delete')") |
| | | public CommonResult<Boolean> deleteNotice(@RequestParam("id") Long id) { |
| | | oaNoticeService.deleteNotice(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "è·åéç¥å
¬åå页å表") |
| | | @PreAuthorize("@ss.hasPermission('bpm:oa-notice:query')") |
| | | public CommonResult<PageResult<OaNoticeRespVO>> getNoticePage(@Valid OaNoticePageReqVO pageReqVO) { |
| | | PageResult<OaNoticeDO> pageResult = oaNoticeService.getNoticePage(getLoginUserId(), pageReqVO); |
| | | return success(BeanUtils.toBean(pageResult, OaNoticeRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·åéç¥å
¬å详æ
") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true, example = "1024") |
| | | @PreAuthorize("@ss.hasPermission('bpm:oa-notice:query')") |
| | | public CommonResult<OaNoticeRespVO> getNotice(@RequestParam("id") Long id) { |
| | | OaNoticeDO notice = oaNoticeService.getNotice(id, getLoginUserId()); |
| | | return success(BeanUtils.toBean(notice, OaNoticeRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/my-unread-count") |
| | | @Operation(summary = "è·åæçæªè¯»éç¥å
¬åæ°é") |
| | | public CommonResult<Long> getMyUnreadCount() { |
| | | return success(oaNoticeService.getUnreadCount(getLoginUserId())); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | @Schema(description = "管çåå° - éç¥å
¬åå页 Request VO") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class OaNoticePageReqVO extends PageParam { |
| | | |
| | | @Schema(description = "å
¬åæ é¢ï¼æ¨¡ç³å¹é
", example = "æ¾å") |
| | | private String title; |
| | | |
| | | @Schema(description = "å
¬åç±»å", example = "1") |
| | | private Integer type; |
| | | |
| | | @Schema(description = "ç¶æ", example = "1") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "åéèå´", example = "1") |
| | | private Integer sendScope; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管çåå° - éç¥å
Œ Response VO") |
| | | @Data |
| | | public class OaNoticeRespVO { |
| | | |
| | | @Schema(description = "éç¥å
¬åç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | private Long id; |
| | | |
| | | @Schema(description = "å
¬åæ é¢", requiredMode = Schema.RequiredMode.REQUIRED, example = "å
³äºæ¾åçéç¥") |
| | | private String title; |
| | | |
| | | @Schema(description = "å
¬åå
容", requiredMode = Schema.RequiredMode.REQUIRED, example = "æ ¹æ®å½å®¶è§å®...") |
| | | private String content; |
| | | |
| | | @Schema(description = "å
¬åç±»å", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Integer type; |
| | | |
| | | @Schema(description = "ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "åéèå´", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | private Integer sendScope; |
| | | |
| | | @Schema(description = "æ¯å¦ç½®é¡¶", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| | | private Integer isTop; |
| | | |
| | | @Schema(description = "åå¸äººID", example = "1") |
| | | private Long publishUserId; |
| | | |
| | | @Schema(description = "å叿¶é´", example = "2025-01-01 10:00:00") |
| | | private LocalDateTime publishTime; |
| | | |
| | | @Schema(description = "å建æ¶é´", requiredMode = Schema.RequiredMode.REQUIRED) |
| | | private LocalDateTime createTime; |
| | | |
| | | @Schema(description = "æå®ç¨æ·IDå表ï¼ä»
sendScope=2æ¶æå¼ï¼") |
| | | private List<Long> userIds; |
| | | |
| | | @Schema(description = "已读人æ°", example = "10") |
| | | private Long readCount; |
| | | |
| | | @Schema(description = "å½åç¨æ·æ¯å¦å·²è¯»", example = "true") |
| | | private Boolean isRead; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.Size; |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管çåå° - éç¥å
¬åå建/ä¿®æ¹ Request VO") |
| | | @Data |
| | | public class OaNoticeSaveReqVO { |
| | | |
| | | @Schema(description = "éç¥å
¬åç¼å·", example = "1024") |
| | | private Long id; |
| | | |
| | | @Schema(description = "å
¬åæ é¢", requiredMode = Schema.RequiredMode.REQUIRED, example = "å
³äºæ¾åçéç¥") |
| | | @NotBlank(message = "å
¬åæ é¢ä¸è½ä¸ºç©º") |
| | | @Size(max = 50, message = "å
¬åæ é¢ä¸è½è¶
è¿50个å符") |
| | | private String title; |
| | | |
| | | @Schema(description = "å
¬åå
容", requiredMode = Schema.RequiredMode.REQUIRED, example = "æ ¹æ®å½å®¶è§å®...") |
| | | @NotBlank(message = "å
¬åå
容ä¸è½ä¸ºç©º") |
| | | private String content; |
| | | |
| | | @Schema(description = "å
¬åç±»å", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "å
¬åç±»åä¸è½ä¸ºç©º") |
| | | private Integer type; |
| | | |
| | | @Schema(description = "ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "ç¶æä¸è½ä¸ºç©º") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "åéèå´", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "åéèå´ä¸è½ä¸ºç©º") |
| | | private Integer sendScope; |
| | | |
| | | @Schema(description = "æ¯å¦ç½®é¡¶", example = "0") |
| | | private Integer isTop; |
| | | |
| | | @Schema(description = "æå®ç¨æ·IDå表ï¼sendScope=2æ¶å¿
å¡«ï¼", example = "[1, 2, 3]") |
| | | private List<Long> userIds; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.dal.dataobject.oa; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import cn.iocoder.yudao.module.bpm.enums.oa.OaNoticeSendScopeEnum; |
| | | import cn.iocoder.yudao.module.bpm.enums.oa.OaNoticeStatusEnum; |
| | | import cn.iocoder.yudao.module.bpm.enums.oa.OaNoticeTypeEnum; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * OA éç¥å
Œ DO |
| | | */ |
| | | @TableName("oa_notice") |
| | | @KeySequence("oa_notice_seq") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class OaNoticeDO extends BaseDO { |
| | | |
| | | @TableId |
| | | private Long id; |
| | | |
| | | /** |
| | | * å
¬åæ é¢ |
| | | */ |
| | | private String title; |
| | | |
| | | /** |
| | | * å
¬åå
容 |
| | | */ |
| | | private String content; |
| | | |
| | | /** |
| | | * å
¬åç±»å |
| | | * |
| | | * æä¸¾ {@link OaNoticeTypeEnum} |
| | | */ |
| | | private Integer type; |
| | | |
| | | /** |
| | | * ç¶æ |
| | | * |
| | | * æä¸¾ {@link OaNoticeStatusEnum} |
| | | */ |
| | | private Integer status; |
| | | |
| | | /** |
| | | * åéèå´ |
| | | * |
| | | * æä¸¾ {@link OaNoticeSendScopeEnum} |
| | | */ |
| | | private Integer sendScope; |
| | | |
| | | /** |
| | | * æ¯å¦ç½®é¡¶ |
| | | */ |
| | | private Integer isTop; |
| | | |
| | | /** |
| | | * åå¸äººID |
| | | */ |
| | | private Long publishUserId; |
| | | |
| | | /** |
| | | * å叿¶é´ |
| | | */ |
| | | private LocalDateTime publishTime; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.dal.dataobject.oa; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * OA éç¥å
¬åå·²è¯»è®°å½ DO |
| | | */ |
| | | @TableName("oa_notice_read") |
| | | @KeySequence("oa_notice_read_seq") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class OaNoticeReadDO extends BaseDO { |
| | | |
| | | @TableId |
| | | private Long id; |
| | | |
| | | /** |
| | | * éç¥å
ŒID |
| | | */ |
| | | private Long noticeId; |
| | | |
| | | /** |
| | | * ç¨æ·ID |
| | | */ |
| | | private Long userId; |
| | | |
| | | /** |
| | | * é
读æ¶é´ |
| | | */ |
| | | private LocalDateTime readTime; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.dal.dataobject.oa; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | /** |
| | | * OA éç¥å
¬åæå®äººå
³è DO |
| | | */ |
| | | @TableName("oa_notice_user") |
| | | @KeySequence("oa_notice_user_seq") |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | public class OaNoticeUserDO extends BaseDO { |
| | | |
| | | @TableId |
| | | private Long id; |
| | | |
| | | /** |
| | | * éç¥å
ŒID |
| | | */ |
| | | private Long noticeId; |
| | | |
| | | /** |
| | | * æå®ç¨æ·ID |
| | | */ |
| | | private Long userId; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.dal.mysql.oa; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.OaNoticePageReqVO; |
| | | import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.OaNoticeDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.Set; |
| | | |
| | | @Mapper |
| | | public interface OaNoticeMapper extends BaseMapperX<OaNoticeDO> { |
| | | |
| | | default PageResult<OaNoticeDO> selectPage(OaNoticePageReqVO reqVO, Set<Long> noticeIds) { |
| | | LambdaQueryWrapperX<OaNoticeDO> wrapper = new LambdaQueryWrapperX<OaNoticeDO>() |
| | | .likeIfPresent(OaNoticeDO::getTitle, reqVO.getTitle()) |
| | | .eqIfPresent(OaNoticeDO::getType, reqVO.getType()) |
| | | .eqIfPresent(OaNoticeDO::getStatus, reqVO.getStatus()) |
| | | .eqIfPresent(OaNoticeDO::getSendScope, reqVO.getSendScope()) |
| | | .orderByDesc(OaNoticeDO::getIsTop) |
| | | .orderByDesc(OaNoticeDO::getId); |
| | | if (noticeIds != null) { |
| | | wrapper.in(OaNoticeDO::getId, noticeIds); |
| | | } |
| | | return selectPage(reqVO, wrapper); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.dal.mysql.oa; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.OaNoticeReadDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Mapper |
| | | public interface OaNoticeReadMapper extends BaseMapperX<OaNoticeReadDO> { |
| | | |
| | | default OaNoticeReadDO selectByNoticeIdAndUserId(Long noticeId, Long userId) { |
| | | return selectOne(new LambdaQueryWrapperX<OaNoticeReadDO>() |
| | | .eq(OaNoticeReadDO::getNoticeId, noticeId) |
| | | .eq(OaNoticeReadDO::getUserId, userId)); |
| | | } |
| | | |
| | | default List<OaNoticeReadDO> selectListByNoticeId(Long noticeId) { |
| | | return selectList(new LambdaQueryWrapperX<OaNoticeReadDO>() |
| | | .eq(OaNoticeReadDO::getNoticeId, noticeId)); |
| | | } |
| | | |
| | | default Long selectCountByNoticeId(Long noticeId) { |
| | | return selectCount(new LambdaQueryWrapperX<OaNoticeReadDO>() |
| | | .eq(OaNoticeReadDO::getNoticeId, noticeId)); |
| | | } |
| | | |
| | | default Long selectUnreadCountByUserId(Long userId, List<Long> noticeIds) { |
| | | if (noticeIds == null || noticeIds.isEmpty()) { |
| | | return 0L; |
| | | } |
| | | // æ»å
¬åæ° - å·²è¯»æ° = æªè¯»æ° |
| | | return (long) noticeIds.size() - selectCount(new LambdaQueryWrapperX<OaNoticeReadDO>() |
| | | .in(OaNoticeReadDO::getNoticeId, noticeIds) |
| | | .eq(OaNoticeReadDO::getUserId, userId)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.dal.mysql.oa; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.OaNoticeUserDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Mapper |
| | | public interface OaNoticeUserMapper extends BaseMapperX<OaNoticeUserDO> { |
| | | |
| | | default List<OaNoticeUserDO> selectListByNoticeId(Long noticeId) { |
| | | return selectList(new LambdaQueryWrapperX<OaNoticeUserDO>() |
| | | .eq(OaNoticeUserDO::getNoticeId, noticeId)); |
| | | } |
| | | |
| | | default void deleteByNoticeId(Long noticeId) { |
| | | delete(new LambdaQueryWrapperX<OaNoticeUserDO>() |
| | | .eq(OaNoticeUserDO::getNoticeId, noticeId)); |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | // ========== OA æµç¨æ¨¡å 1-009-001-000 ========== |
| | | ErrorCode OA_LEAVE_NOT_EXISTS = new ErrorCode(1_009_001_001, "请åç³è¯·ä¸åå¨"); |
| | | ErrorCode OA_NOTICE_NOT_EXISTS = new ErrorCode(1_009_001_002, "éç¥å
¬åä¸åå¨"); |
| | | |
| | | // ========== æµç¨æ¨¡å 1-009-002-000 ========== |
| | | ErrorCode MODEL_KEY_EXISTS = new ErrorCode(1_009_002_000, "å·²ç»å卿µç¨æ è¯ä¸ºã{}ãçæµç¨"); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.enums.oa; |
| | | |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | /** |
| | | * OA éç¥å
¬ååéè崿䏾 |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum OaNoticeSendScopeEnum { |
| | | |
| | | ALL(1, "éç¥ææäºº"), |
| | | SPECIFIED(2, "æå®äºº"); |
| | | |
| | | private final Integer scope; |
| | | private final String name; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.enums.oa; |
| | | |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | /** |
| | | * OA éç¥å
¬åç¶ææä¸¾ |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum OaNoticeStatusEnum { |
| | | |
| | | DRAFT(0, "è稿"), |
| | | PUBLISHED(1, "å·²åå¸"), |
| | | REVOKED(2, "å·²æ¤é"); |
| | | |
| | | private final Integer status; |
| | | private final String name; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.enums.oa; |
| | | |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Getter; |
| | | |
| | | /** |
| | | * OA éç¥å
¬åç±»åæä¸¾ |
| | | */ |
| | | @Getter |
| | | @AllArgsConstructor |
| | | public enum OaNoticeTypeEnum { |
| | | |
| | | NOTIFICATION(1, "éç¥"), |
| | | ANNOUNCEMENT(2, "å
Œ"); |
| | | |
| | | private final Integer type; |
| | | private final String name; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.service.oa; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.OaNoticePageReqVO; |
| | | import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.OaNoticeSaveReqVO; |
| | | import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.OaNoticeDO; |
| | | |
| | | /** |
| | | * OA éç¥å
¬å Service æ¥å£ |
| | | */ |
| | | public interface OaNoticeService { |
| | | |
| | | /** |
| | | * å建éç¥å
Œ |
| | | * |
| | | * @param userId å½åç¨æ·ID |
| | | * @param createReqVO åå»ºè¯·æ± |
| | | * @return éç¥å
ŒID |
| | | */ |
| | | Long createNotice(Long userId, OaNoticeSaveReqVO createReqVO); |
| | | |
| | | /** |
| | | * æ´æ°éç¥å
Œ |
| | | * |
| | | * @param updateReqVO æ´æ°è¯·æ± |
| | | */ |
| | | void updateNotice(OaNoticeSaveReqVO updateReqVO); |
| | | |
| | | /** |
| | | * å é¤éç¥å
Œ |
| | | * |
| | | * @param id ç¼å· |
| | | */ |
| | | void deleteNotice(Long id); |
| | | |
| | | /** |
| | | * è·å¾éç¥å
¬åå页åè¡¨ï¼æå¯è§æ§è¿æ»¤ï¼ |
| | | * |
| | | * @param userId å½åç¨æ·ID |
| | | * @param reqVO å页æ¡ä»¶ |
| | | * @return å页å表 |
| | | */ |
| | | PageResult<OaNoticeDO> getNoticePage(Long userId, OaNoticePageReqVO reqVO); |
| | | |
| | | /** |
| | | * è·å¾éç¥å
¬å详æ
ï¼åæ¶æ è®°å·²è¯»ï¼ |
| | | * |
| | | * @param id ç¼å· |
| | | * @param userId å½åç¨æ·ID |
| | | * @return éç¥å
Œ |
| | | */ |
| | | OaNoticeDO getNotice(Long id, Long userId); |
| | | |
| | | /** |
| | | * è·åå½åç¨æ·æªè¯»éç¥å
¬åæ°é |
| | | * |
| | | * @param userId å½åç¨æ·ID |
| | | * @return æªè¯»æ°é |
| | | */ |
| | | Long getUnreadCount(Long userId); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.bpm.service.oa; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.OaNoticePageReqVO; |
| | | import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.OaNoticeSaveReqVO; |
| | | import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.OaNoticeDO; |
| | | import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.OaNoticeReadDO; |
| | | import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.OaNoticeUserDO; |
| | | import cn.iocoder.yudao.module.bpm.dal.mysql.oa.OaNoticeMapper; |
| | | import cn.iocoder.yudao.module.bpm.dal.mysql.oa.OaNoticeReadMapper; |
| | | import cn.iocoder.yudao.module.bpm.dal.mysql.oa.OaNoticeUserMapper; |
| | | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| | | import cn.iocoder.yudao.module.bpm.enums.oa.OaNoticeSendScopeEnum; |
| | | import cn.iocoder.yudao.module.bpm.enums.oa.OaNoticeStatusEnum; |
| | | import cn.iocoder.yudao.module.system.dal.mysql.notify.NotifyMessageMapper; |
| | | import cn.iocoder.yudao.module.system.service.notify.NotifySendService; |
| | | import cn.iocoder.yudao.module.system.service.user.AdminUserService; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.OA_NOTICE_NOT_EXISTS; |
| | | |
| | | /** |
| | | * OA éç¥å
¬å Service å®ç°ç±» |
| | | */ |
| | | @Service |
| | | @Slf4j |
| | | public class OaNoticeServiceImpl implements OaNoticeService { |
| | | |
| | | @Resource |
| | | private OaNoticeMapper oaNoticeMapper; |
| | | |
| | | @Resource |
| | | private OaNoticeUserMapper oaNoticeUserMapper; |
| | | |
| | | @Resource |
| | | private OaNoticeReadMapper oaNoticeReadMapper; |
| | | |
| | | @Resource |
| | | private NotifySendService notifySendService; |
| | | |
| | | @Resource |
| | | private AdminUserService adminUserService; |
| | | |
| | | @Resource |
| | | private NotifyMessageMapper notifyMessageMapper; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createNotice(Long userId, OaNoticeSaveReqVO createReqVO) { |
| | | // å建éç¥å
Œ |
| | | OaNoticeDO notice = BeanUtils.toBean(createReqVO, OaNoticeDO.class); |
| | | notice.setPublishUserId(userId); |
| | | if (OaNoticeStatusEnum.PUBLISHED.getStatus().equals(createReqVO.getStatus())) { |
| | | notice.setPublishTime(LocalDateTime.now()); |
| | | } |
| | | oaNoticeMapper.insert(notice); |
| | | |
| | | // æå®äººåå
¥å
³è表 |
| | | if (OaNoticeSendScopeEnum.SPECIFIED.getScope().equals(createReqVO.getSendScope()) |
| | | && CollUtil.isNotEmpty(createReqVO.getUserIds())) { |
| | | saveNoticeUsers(notice.getId(), createReqVO.getUserIds()); |
| | | } |
| | | |
| | | // å·²åå¸ååéç³»ç»éç¥ |
| | | if (OaNoticeStatusEnum.PUBLISHED.getStatus().equals(notice.getStatus())) { |
| | | sendPublishNotify(notice); |
| | | } |
| | | |
| | | return notice.getId(); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateNotice(OaNoticeSaveReqVO updateReqVO) { |
| | | // æ ¡éªæ¯å¦åå¨ |
| | | OaNoticeDO oldNotice = validateNoticeExists(updateReqVO.getId()); |
| | | |
| | | // æ´æ°éç¥å
Œ |
| | | OaNoticeDO updateObj = BeanUtils.toBean(updateReqVO, OaNoticeDO.class); |
| | | // 仿ªåå¸å为已å叿¶ï¼è®¾ç½®å叿¶é´ |
| | | if (!OaNoticeStatusEnum.PUBLISHED.getStatus().equals(oldNotice.getStatus()) |
| | | && OaNoticeStatusEnum.PUBLISHED.getStatus().equals(updateReqVO.getStatus())) { |
| | | updateObj.setPublishTime(LocalDateTime.now()); |
| | | } |
| | | oaNoticeMapper.updateById(updateObj); |
| | | |
| | | // æ´æ°æå®äººå
³è |
| | | oaNoticeUserMapper.deleteByNoticeId(updateReqVO.getId()); |
| | | if (OaNoticeSendScopeEnum.SPECIFIED.getScope().equals(updateReqVO.getSendScope()) |
| | | && CollUtil.isNotEmpty(updateReqVO.getUserIds())) { |
| | | saveNoticeUsers(updateReqVO.getId(), updateReqVO.getUserIds()); |
| | | } |
| | | |
| | | // æ°å叿¶åéç³»ç»éç¥ |
| | | if (!OaNoticeStatusEnum.PUBLISHED.getStatus().equals(oldNotice.getStatus()) |
| | | && OaNoticeStatusEnum.PUBLISHED.getStatus().equals(updateReqVO.getStatus())) { |
| | | sendPublishNotify(updateObj); |
| | | } |
| | | |
| | | // æ¤éæ¶å é¤å·²åéçç³»ç»éç¥ |
| | | if (OaNoticeStatusEnum.REVOKED.getStatus().equals(updateReqVO.getStatus()) |
| | | && OaNoticeStatusEnum.PUBLISHED.getStatus().equals(oldNotice.getStatus())) { |
| | | notifyMessageMapper.deleteByTemplateCodeAndNoticeId( |
| | | "oa-notice-publish", updateReqVO.getId()); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteNotice(Long id) { |
| | | validateNoticeExists(id); |
| | | oaNoticeMapper.deleteById(id); |
| | | oaNoticeUserMapper.deleteByNoticeId(id); |
| | | } |
| | | |
| | | @Override |
| | | public PageResult<OaNoticeDO> getNoticePage(Long userId, OaNoticePageReqVO reqVO) { |
| | | // æå»ºå¯è§çéç¥IDéå |
| | | Set<Long> visibleIds = buildVisibleNoticeIds(userId); |
| | | if (visibleIds != null && visibleIds.isEmpty()) { |
| | | return PageResult.empty(); |
| | | } |
| | | return oaNoticeMapper.selectPage(reqVO, visibleIds); |
| | | } |
| | | |
| | | @Override |
| | | public OaNoticeDO getNotice(Long id, Long userId) { |
| | | OaNoticeDO notice = validateNoticeExists(id); |
| | | // æ 记已读 |
| | | markAsRead(id, userId); |
| | | return notice; |
| | | } |
| | | |
| | | @Override |
| | | public Long getUnreadCount(Long userId) { |
| | | // è·åææå·²åå¸ä¸å½åç¨æ·å¯è§çå
ŒID |
| | | Set<Long> visibleIds = buildVisibleNoticeIds(userId); |
| | | if (CollUtil.isEmpty(visibleIds)) { |
| | | return 0L; |
| | | } |
| | | List<Long> visibleIdList = new ArrayList<>(visibleIds); |
| | | return oaNoticeReadMapper.selectUnreadCountByUserId(userId, visibleIdList); |
| | | } |
| | | |
| | | /** |
| | | * æå»ºå½åç¨æ·å¯è§çéç¥å
¬åIDéå |
| | | * |
| | | * @param userId å½åç¨æ·ID |
| | | * @return null表示å
¨é¨å¯è§ï¼ç©ºéå表示æ å¯ç |
| | | */ |
| | | private Set<Long> buildVisibleNoticeIds(Long userId) { |
| | | // ææéç¥ææäººå
ŒID |
| | | List<OaNoticeDO> allPublishedNotices = oaNoticeMapper.selectList( |
| | | new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<OaNoticeDO>() |
| | | .eq(OaNoticeDO::getSendScope, OaNoticeSendScopeEnum.ALL.getScope())); |
| | | Set<Long> allNoticeIds = allPublishedNotices.stream() |
| | | .map(OaNoticeDO::getId).collect(Collectors.toSet()); |
| | | |
| | | // æå®äººè½çå°çæ¬ç¨æ·ç¸å
³å
ŒID |
| | | List<OaNoticeDO> specifiedNotices = oaNoticeMapper.selectList( |
| | | new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<OaNoticeDO>() |
| | | .eq(OaNoticeDO::getSendScope, OaNoticeSendScopeEnum.SPECIFIED.getScope())); |
| | | for (OaNoticeDO notice : specifiedNotices) { |
| | | // å建è
å¯è§ |
| | | if (userId.equals(notice.getPublishUserId())) { |
| | | allNoticeIds.add(notice.getId()); |
| | | continue; |
| | | } |
| | | // 被æå®ç¨æ·å¯è§ |
| | | Long count = oaNoticeUserMapper.selectCount( |
| | | new cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX<OaNoticeUserDO>() |
| | | .eq(OaNoticeUserDO::getNoticeId, notice.getId()) |
| | | .eq(OaNoticeUserDO::getUserId, userId)); |
| | | if (count > 0) { |
| | | allNoticeIds.add(notice.getId()); |
| | | } |
| | | } |
| | | |
| | | return allNoticeIds; |
| | | } |
| | | |
| | | /** |
| | | * æ 记已读 |
| | | */ |
| | | private void markAsRead(Long noticeId, Long userId) { |
| | | OaNoticeReadDO existing = oaNoticeReadMapper.selectByNoticeIdAndUserId(noticeId, userId); |
| | | if (existing == null) { |
| | | OaNoticeReadDO readRecord = new OaNoticeReadDO(); |
| | | readRecord.setNoticeId(noticeId); |
| | | readRecord.setUserId(userId); |
| | | readRecord.setReadTime(LocalDateTime.now()); |
| | | oaNoticeReadMapper.insert(readRecord); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ä¿åæå®äººå
³è |
| | | */ |
| | | private void saveNoticeUsers(Long noticeId, List<Long> userIds) { |
| | | List<OaNoticeUserDO> userList = userIds.stream().map(uid -> { |
| | | OaNoticeUserDO user = new OaNoticeUserDO(); |
| | | user.setNoticeId(noticeId); |
| | | user.setUserId(uid); |
| | | return user; |
| | | }).collect(Collectors.toList()); |
| | | oaNoticeUserMapper.insertBatch(userList); |
| | | } |
| | | |
| | | /** |
| | | * åéåå¸éç¥ç»ç®æ ç¨æ· |
| | | */ |
| | | private void sendPublishNotify(OaNoticeDO notice) { |
| | | Map<String, Object> templateParams = new HashMap<>(); |
| | | templateParams.put("title", notice.getTitle()); |
| | | templateParams.put("content", notice.getContent()); |
| | | templateParams.put("noticeId", notice.getId()); |
| | | List<Long> targetUserIds; |
| | | if (OaNoticeSendScopeEnum.ALL.getScope().equals(notice.getSendScope())) { |
| | | // éç¥ææäººï¼æ¥è¯¢ææå¯ç¨ç管çåç¨æ· |
| | | targetUserIds = adminUserService.getUserListByStatus(CommonStatusEnum.ENABLE.getStatus()) |
| | | .stream().map(cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO::getId) |
| | | .collect(Collectors.toList()); |
| | | } else { |
| | | // æå®äºº |
| | | targetUserIds = oaNoticeUserMapper.selectListByNoticeId(notice.getId()).stream() |
| | | .map(OaNoticeUserDO::getUserId) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | for (Long userId : targetUserIds) { |
| | | try { |
| | | notifySendService.sendSingleNotifyToAdmin(userId, "oa-notice-publish", templateParams); |
| | | } catch (Exception e) { |
| | | log.warn("åééç¥å
¬å[{}]ç»ç¨æ·[{}]失败", notice.getId(), userId, e); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private OaNoticeDO validateNoticeExists(Long id) { |
| | | OaNoticeDO notice = oaNoticeMapper.selectById(id); |
| | | if (notice == null) { |
| | | throw exception(OA_NOTICE_NOT_EXISTS); |
| | | } |
| | | return notice; |
| | | } |
| | | |
| | | } |
| | |
| | | <version>2026.06-jdk25-SNAPSHOT</version> |
| | | <scope>compile</scope> |
| | | </dependency> |
| | | |
| | | <!-- AI 大模å API --> |
| | | <dependency> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao-module-ai-api</artifactId> |
| | | <version>${revision}</version> |
| | | </dependency> |
| | | |
| | | <!-- PDF è§£æ --> |
| | | <dependency> |
| | | <groupId>org.apache.pdfbox</groupId> |
| | | <artifactId>pdfbox</artifactId> |
| | | <version>3.0.4</version> |
| | | </dependency> |
| | | </dependencies> |
| | | </project> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.controller.admin.quotation.ai; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.ai.vo.CrmSaleQuotationOcrReqVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.ai.vo.CrmSaleQuotationOcrRespVO; |
| | | import cn.iocoder.yudao.module.crm.service.quotation.ai.CrmSaleQuotationAiService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | |
| | | @Tag(name = "管çåå° - CRM é宿¥ä»·å AI OCR") |
| | | @RestController |
| | | @RequestMapping("/crm/sale-quotation/ai") |
| | | @Validated |
| | | public class CrmSaleQuotationAiController { |
| | | |
| | | @Resource |
| | | private CrmSaleQuotationAiService saleQuotationAiService; |
| | | |
| | | @PostMapping("/ocr") |
| | | @Operation(summary = "AI OCR è¯å«æ¥ä»·æä»¶") |
| | | @PreAuthorize("@ss.hasPermission('crm:sale-quotation:create')") |
| | | public CommonResult<CrmSaleQuotationOcrRespVO> ocrQuotation(@Valid @RequestBody CrmSaleQuotationOcrReqVO reqVO) { |
| | | return success(saleQuotationAiService.ocrQuotation(reqVO)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.controller.admin.quotation.ai.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - AI OCR è¯å«æ¥ä»·æä»¶è¯·æ± VO") |
| | | @Data |
| | | public class CrmSaleQuotationOcrReqVO { |
| | | |
| | | @Schema(description = "å·²ä¸ä¼ æä»¶ç blob ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "blob ID ä¸è½ä¸ºç©º") |
| | | private Long blobId; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.controller.admin.quotation.ai.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管çåå° - AI OCR è¯å«æ¥ä»·æä»¶ååº VO") |
| | | @Data |
| | | public class CrmSaleQuotationOcrRespVO { |
| | | |
| | | @Schema(description = "æ¥ä»·ååç§°", example = "XXå
¬å¸æ¥ä»·å") |
| | | private String name; |
| | | |
| | | @Schema(description = "客æ·åç§°", example = "XXç§ææéå
¬å¸") |
| | | private String customerName; |
| | | |
| | | @Schema(description = "æ¥ä»·æ¥æ", example = "2026-07-31") |
| | | private LocalDate quotationTime; |
| | | |
| | | @Schema(description = "æææè³", example = "2026-08-30") |
| | | private LocalDate validUntil; |
| | | |
| | | @Schema(description = "ç¨çï¼ç¾åæ¯ï¼", example = "13.0") |
| | | private BigDecimal taxRate; |
| | | |
| | | @Schema(description = "ææ£çï¼ç¾åæ¯ï¼", example = "5.0") |
| | | private BigDecimal discountPercent; |
| | | |
| | | @Schema(description = "夿³¨", example = "å«ç¨å«è¿è´¹") |
| | | private String remark; |
| | | |
| | | @Schema(description = "è¯å«åºçç©ææç»") |
| | | private List<Item> items; |
| | | |
| | | @Schema(description = "åå§è¯å«ææ¬ï¼å端å¯éå±ç¤ºï¼") |
| | | private String rawText; |
| | | |
| | | @Schema(description = "ç©ææç»é¡¹") |
| | | @Data |
| | | public static class Item { |
| | | |
| | | @Schema(description = "ç©æåç§°", example = "伺æçµæº") |
| | | private String itemName; |
| | | |
| | | @Schema(description = "ç©æè§æ ¼", example = "SV-1000W") |
| | | private String itemSpec; |
| | | |
| | | @Schema(description = "æ°é", example = "10") |
| | | private BigDecimal count; |
| | | |
| | | @Schema(description = "åä»·", example = "1500.00") |
| | | private BigDecimal quotationPrice; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.service.quotation.ai; |
| | | |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.ai.vo.CrmSaleQuotationOcrReqVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.ai.vo.CrmSaleQuotationOcrRespVO; |
| | | |
| | | public interface CrmSaleQuotationAiService { |
| | | |
| | | CrmSaleQuotationOcrRespVO ocrQuotation(CrmSaleQuotationOcrReqVO reqVO); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.service.quotation.ai; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.io.FileUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.hutool.json.JSONUtil; |
| | | import cn.iocoder.yudao.module.ai.api.chat.AiChatApi; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.ai.vo.CrmSaleQuotationOcrReqVO; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.ai.vo.CrmSaleQuotationOcrRespVO; |
| | | import cn.iocoder.yudao.module.system.dal.dataobject.storage.SystemStorageBlobDO; |
| | | import cn.iocoder.yudao.module.system.service.storage.SystemStorageBlobService; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.pdfbox.Loader; |
| | | import org.apache.pdfbox.pdmodel.PDDocument; |
| | | import org.apache.pdfbox.text.PDFTextStripper; |
| | | import org.apache.poi.ss.usermodel.*; |
| | | import org.apache.poi.xwpf.extractor.XWPFWordExtractor; |
| | | import org.apache.poi.xwpf.usermodel.XWPFDocument; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.File; |
| | | import java.io.IOException; |
| | | import java.math.BigDecimal; |
| | | import java.nio.file.Files; |
| | | import java.time.LocalDate; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @Slf4j |
| | | @Service |
| | | public class CrmSaleQuotationAiServiceImpl implements CrmSaleQuotationAiService { |
| | | |
| | | private static final Set<String> SUPPORTED_EXTENSIONS = Set.of( |
| | | "pdf", "doc", "docx", "xls", "xlsx", "png", "jpg", "jpeg", "gif", "bmp", "txt", "csv" |
| | | ); |
| | | |
| | | private static final Set<String> IMAGE_EXTENSIONS = Set.of("png", "jpg", "jpeg", "gif", "bmp"); |
| | | |
| | | private static final String SYSTEM_PROMPT = """ |
| | | ä½ æ¯ä¸ä½ä¸ä¸çéå®åæ®æ°æ®æåä¸å®¶ãä½ éè¦ä»æ¥ä»·æä»¶ä¸æåç»æåä¿¡æ¯ã |
| | | |
| | | ## æåè§å |
| | | 1. ä»ç»è¯å«ææ¡£ä¸çæ¥ä»·åä¿¡æ¯ï¼å®¢æ·åç§°ãæ¥ä»·æ¥æãæææãç©ææç»ç |
| | | 2. 对äºç©ææç»ï¼æ¯ä¸è¡æåï¼ç©æåç§°ãè§æ ¼åå·ãæ°éãåä»· |
| | | 3. ç¨çãææ£çç妿件䏿ªæç¡®æ 注ï¼ä¸å¡« |
| | | 4. æ¥æç»ä¸è½¬æ¢ä¸º yyyy-MM-dd æ ¼å¼ |
| | | 5. æ°ååæ®µä»
ä¿çæ°åï¼å»æåä½ |
| | | |
| | | ## è¾åºæ ¼å¼ |
| | | 请æä»¥ä¸JSONæ ¼å¼è¿åï¼ä¸è¦å
å«å
¶ä»å
容ï¼ï¼ |
| | | {"name": "æ¥ä»·ååç§°", "customerName": "客æ·åç§°", "quotationTime": "2026-01-01", "validUntil": "2026-12-31", "taxRate": 13.0, "discountPercent": 5.0, "remark": "夿³¨ä¿¡æ¯", "items": [{"itemName": "ç©æåç§°", "itemSpec": "è§æ ¼åå·", "count": 10.0, "quotationPrice": 100.0}]} |
| | | |
| | | ææå段å为å¯éï¼æªæåå°çåæ®µä¸è¦è¿åã |
| | | 妿æä»¶å
å®¹æ æ³è¯å«ä¸ºæ¥ä»·åï¼è¿å空JSONï¼{} |
| | | """; |
| | | |
| | | private static final String SYSTEM_PROMPT_IMAGE = """ |
| | | ä½ æ¯ä¸ä½ä¸ä¸çéå®åæ®OCRè¯å«ä¸å®¶ãä½ éè¦ä»æ¥ä»·æä»¶å¾ç䏿åç»æåä¿¡æ¯ã |
| | | |
| | | ## æåè§å |
| | | 1. ä»ç»è¯å«å¾çä¸çæåä¿¡æ¯ |
| | | 2. æåæ¥ä»·åç¸å
³ä¿¡æ¯ï¼å®¢æ·åç§°ãæ¥ä»·æ¥æãæææãç©ææç»ç |
| | | 3. 对äºç©ææç»ï¼æ¯ä¸è¡æåï¼ç©æåç§°ãè§æ ¼åå·ãæ°éãåä»· |
| | | 4. ç¨çãææ£ççå¦å¾ç䏿ªæç¡®æ 注ï¼ä¸å¡« |
| | | 5. æ¥æç»ä¸è½¬æ¢ä¸º yyyy-MM-dd æ ¼å¼ |
| | | |
| | | ## è¾åºæ ¼å¼ |
| | | 请æä»¥ä¸JSONæ ¼å¼è¿åï¼ä¸è¦å
å«å
¶ä»å
容ï¼ï¼ |
| | | {"name": "æ¥ä»·ååç§°", "customerName": "客æ·åç§°", "quotationTime": "2026-01-01", "validUntil": "2026-12-31", "taxRate": 13.0, "discountPercent": 5.0, "remark": "夿³¨ä¿¡æ¯", "items": [{"itemName": "ç©æåç§°", "itemSpec": "è§æ ¼åå·", "count": 10.0, "quotationPrice": 100.0}]} |
| | | |
| | | ææå段å为å¯éï¼æªæåå°çåæ®µä¸è¦è¿åã |
| | | 妿å¾çå
å®¹æ æ³è¯å«ä¸ºæ¥ä»·åï¼è¿å空JSONï¼{} |
| | | """; |
| | | |
| | | @Resource |
| | | private SystemStorageBlobService blobService; |
| | | @Resource |
| | | private AiChatApi aiChatApi; |
| | | |
| | | @Override |
| | | public CrmSaleQuotationOcrRespVO ocrQuotation(CrmSaleQuotationOcrReqVO reqVO) { |
| | | File tempFile = null; |
| | | try { |
| | | // 1. ä¸è½½æä»¶ |
| | | SystemStorageBlobDO blob = blobService.getStorageBlob(reqVO.getBlobId()); |
| | | if (blob == null) { |
| | | CrmSaleQuotationOcrRespVO error = new CrmSaleQuotationOcrRespVO(); |
| | | error.setRawText("æä»¶ä¸åå¨"); |
| | | return error; |
| | | } |
| | | |
| | | // 2. æ ¡éªæä»¶ç±»å |
| | | String ext = FileUtil.extName(blob.getOriginalFilename()).toLowerCase(); |
| | | if (!SUPPORTED_EXTENSIONS.contains(ext)) { |
| | | CrmSaleQuotationOcrRespVO error = new CrmSaleQuotationOcrRespVO(); |
| | | error.setRawText("䏿¯æçæä»¶ç±»åï¼è¯·ä¸ä¼ PDF/Word/Excel/å¾çæä»¶"); |
| | | return error; |
| | | } |
| | | |
| | | // 3. è·åæä»¶åè |
| | | tempFile = blobService.getPublicFile(blob.getUidFilename(), blob.getResourceKey()); |
| | | if (tempFile == null || !tempFile.exists()) { |
| | | CrmSaleQuotationOcrRespVO error = new CrmSaleQuotationOcrRespVO(); |
| | | error.setRawText("æä»¶è¯»å失败"); |
| | | return error; |
| | | } |
| | | byte[] fileBytes = Files.readAllBytes(tempFile.toPath()); |
| | | |
| | | // 4. AI è¯å«ï¼å¾çèµ°å¤æ¨¡æï¼ææ¬èµ°çº¯ææ¬ï¼ |
| | | boolean isImage = IMAGE_EXTENSIONS.contains(ext); |
| | | String systemPrompt = isImage ? SYSTEM_PROMPT_IMAGE : SYSTEM_PROMPT; |
| | | String aiResponse; |
| | | if (isImage) { |
| | | String imageBase64 = Base64.getEncoder().encodeToString(fileBytes); |
| | | String mimeType = getMimeType(ext); |
| | | aiResponse = aiChatApi.chatWithImage(systemPrompt, "请è¯å«å¾çä¸çæ¥ä»·ä¿¡æ¯", imageBase64, mimeType); |
| | | } else { |
| | | String extractedText = extractTextByType(ext, fileBytes); |
| | | if (StrUtil.isEmpty(extractedText)) { |
| | | CrmSaleQuotationOcrRespVO error = new CrmSaleQuotationOcrRespVO(); |
| | | error.setRawText("æä»¶å
å®¹ä¸ºç©ºï¼æ æ³è¯å«"); |
| | | return error; |
| | | } |
| | | aiResponse = aiChatApi.chat(systemPrompt, extractedText); |
| | | } |
| | | log.info("AI OCR è¯å«ç»æ: blobId={}, file={}, response={}", |
| | | reqVO.getBlobId(), blob.getOriginalFilename(), aiResponse); |
| | | |
| | | // 5. è§£æååº |
| | | CrmSaleQuotationOcrRespVO result = parseOcrResponse(aiResponse); |
| | | return result; |
| | | |
| | | } catch (Exception e) { |
| | | log.warn("AI OCR è¯å«å¤±è´¥ï¼blobId={}", reqVO.getBlobId(), e); |
| | | CrmSaleQuotationOcrRespVO fallback = new CrmSaleQuotationOcrRespVO(); |
| | | fallback.setRawText("AI è¯å«ææ¶ä¸å¯ç¨ï¼è¯·æå¨å½å
¥"); |
| | | return fallback; |
| | | } finally { |
| | | if (tempFile != null && tempFile.exists()) { |
| | | tempFile.delete(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private String extractTextByType(String ext, byte[] bytes) throws IOException { |
| | | return switch (ext) { |
| | | case "pdf" -> extractPdfText(bytes); |
| | | case "docx", "doc" -> extractDocxText(bytes); |
| | | case "xlsx", "xls" -> extractExcelText(bytes); |
| | | case "txt", "csv" -> new String(bytes, java.nio.charset.StandardCharsets.UTF_8); |
| | | default -> ""; |
| | | }; |
| | | } |
| | | |
| | | private String extractPdfText(byte[] bytes) throws IOException { |
| | | try (PDDocument doc = Loader.loadPDF(bytes)) { |
| | | PDFTextStripper stripper = new PDFTextStripper(); |
| | | stripper.setSortByPosition(true); |
| | | String text = stripper.getText(doc); |
| | | return StrUtil.isNotBlank(text) ? text.trim() : ""; |
| | | } |
| | | } |
| | | |
| | | private String extractDocxText(byte[] bytes) throws IOException { |
| | | try (XWPFDocument doc = new XWPFDocument(new ByteArrayInputStream(bytes))) { |
| | | try (XWPFWordExtractor extractor = new XWPFWordExtractor(doc)) { |
| | | String text = extractor.getText(); |
| | | return StrUtil.isNotBlank(text) ? text.trim() : ""; |
| | | } |
| | | } |
| | | } |
| | | |
| | | private String extractExcelText(byte[] bytes) throws IOException { |
| | | StringBuilder sb = new StringBuilder(); |
| | | try (Workbook wb = WorkbookFactory.create(new ByteArrayInputStream(bytes))) { |
| | | for (int i = 0; i < wb.getNumberOfSheets(); i++) { |
| | | Sheet sheet = wb.getSheetAt(i); |
| | | if (i > 0) sb.append("\n--- Sheet: ").append(sheet.getSheetName()).append(" ---\n"); |
| | | for (Row row : sheet) { |
| | | List<String> cellValues = new ArrayList<>(); |
| | | for (Cell cell : row) { |
| | | cellValues.add(getCellStringValue(cell)); |
| | | } |
| | | sb.append(String.join("\t", cellValues)).append("\n"); |
| | | } |
| | | } |
| | | } |
| | | return sb.toString().trim(); |
| | | } |
| | | |
| | | private String getCellStringValue(Cell cell) { |
| | | return switch (cell.getCellType()) { |
| | | case STRING -> cell.getStringCellValue().trim(); |
| | | case NUMERIC -> { |
| | | if (DateUtil.isCellDateFormatted(cell)) { |
| | | yield cell.getLocalDateTimeCellValue().toLocalDate().toString(); |
| | | } |
| | | double v = cell.getNumericCellValue(); |
| | | if (v == Math.floor(v) && !Double.isInfinite(v)) { |
| | | yield String.valueOf((long) v); |
| | | } |
| | | yield String.valueOf(v); |
| | | } |
| | | case BOOLEAN -> String.valueOf(cell.getBooleanCellValue()); |
| | | case FORMULA -> { |
| | | try { |
| | | yield String.valueOf(cell.getNumericCellValue()); |
| | | } catch (Exception e) { |
| | | yield cell.getStringCellValue().trim(); |
| | | } |
| | | } |
| | | default -> ""; |
| | | }; |
| | | } |
| | | |
| | | private String getMimeType(String ext) { |
| | | return switch (ext) { |
| | | case "jpg", "jpeg" -> "image/jpeg"; |
| | | case "gif" -> "image/gif"; |
| | | case "bmp" -> "image/bmp"; |
| | | default -> "image/png"; |
| | | }; |
| | | } |
| | | |
| | | private CrmSaleQuotationOcrRespVO parseOcrResponse(String aiResponse) { |
| | | CrmSaleQuotationOcrRespVO respVO = new CrmSaleQuotationOcrRespVO(); |
| | | try { |
| | | String json = aiResponse; |
| | | int start = json.indexOf("{"); |
| | | int end = json.lastIndexOf("}"); |
| | | if (start >= 0 && end > start) { |
| | | json = json.substring(start, end + 1); |
| | | } |
| | | Map<String, Object> parsed = JSONUtil.toBean(json, Map.class); |
| | | |
| | | respVO.setName((String) parsed.get("name")); |
| | | respVO.setCustomerName((String) parsed.get("customerName")); |
| | | respVO.setRemark((String) parsed.get("remark")); |
| | | |
| | | Object qt = parsed.get("quotationTime"); |
| | | if (qt instanceof String s && StrUtil.isNotBlank(s)) { |
| | | try { respVO.setQuotationTime(LocalDate.parse(s)); } catch (Exception ignored) {} |
| | | } |
| | | Object vu = parsed.get("validUntil"); |
| | | if (vu instanceof String s && StrUtil.isNotBlank(s)) { |
| | | try { respVO.setValidUntil(LocalDate.parse(s)); } catch (Exception ignored) {} |
| | | } |
| | | Object tr = parsed.get("taxRate"); |
| | | if (tr instanceof Number n) respVO.setTaxRate(BigDecimal.valueOf(n.doubleValue())); |
| | | Object dp = parsed.get("discountPercent"); |
| | | if (dp instanceof Number n) respVO.setDiscountPercent(BigDecimal.valueOf(n.doubleValue())); |
| | | |
| | | @SuppressWarnings("unchecked") |
| | | List<Map<String, Object>> items = (List<Map<String, Object>>) parsed.get("items"); |
| | | if (CollUtil.isNotEmpty(items)) { |
| | | respVO.setItems(items.stream().map(m -> { |
| | | CrmSaleQuotationOcrRespVO.Item item = new CrmSaleQuotationOcrRespVO.Item(); |
| | | item.setItemName((String) m.get("itemName")); |
| | | item.setItemSpec((String) m.get("itemSpec")); |
| | | Object count = m.get("count"); |
| | | if (count instanceof Number n) item.setCount(BigDecimal.valueOf(n.doubleValue())); |
| | | Object price = m.get("quotationPrice"); |
| | | if (price instanceof Number n) item.setQuotationPrice(BigDecimal.valueOf(n.doubleValue())); |
| | | return item; |
| | | }).collect(Collectors.toList())); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("è§£æ AI OCR ååºå¤±è´¥: {}", aiResponse, e); |
| | | respVO.setRawText(aiResponse); |
| | | } |
| | | return respVO; |
| | | } |
| | | |
| | | private String truncate(String text, int maxLen) { |
| | | if (text == null) return ""; |
| | | return text.length() > maxLen ? text.substring(0, maxLen) + "..." : text; |
| | | } |
| | | |
| | | } |
| | |
| | | <version>${revision}</version> |
| | | </dependency> |
| | | |
| | | <!-- AI 大模å API --> |
| | | <dependency> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao-module-ai-api</artifactId> |
| | | <version>${revision}</version> |
| | | </dependency> |
| | | |
| | | <!-- ä¸å¡ç»ä»¶ --> |
| | | |
| | | <!-- Web ç¸å
³ --> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.pro.ai; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo.*; |
| | | import cn.iocoder.yudao.module.mes.service.pro.ai.MesProAiService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | |
| | | @Tag(name = "管çåå° - MES ç产 AI è¾
å©åæ") |
| | | @RestController |
| | | @RequestMapping("/mes/pro/ai") |
| | | public class MesProAiController { |
| | | |
| | | @Resource |
| | | private MesProAiService proAiService; |
| | | |
| | | @PostMapping("/predict-material") |
| | | @Operation(summary = "AI ç©æçç¼ºé¢æµ") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')") |
| | | public CommonResult<MesProAiMaterialPredictRespVO> predictMaterial(@Valid @RequestBody MesProAiMaterialPredictReqVO reqVO) { |
| | | return success(proAiService.predictMaterial(reqVO)); |
| | | } |
| | | |
| | | @PostMapping("/predict-duration") |
| | | @Operation(summary = "AI çäº§è®¡åæ¶é¿é¢æµ") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')") |
| | | public CommonResult<MesProAiDurationPredictRespVO> predictDuration(@Valid @RequestBody MesProAiDurationPredictReqVO reqVO) { |
| | | return success(proAiService.predictDuration(reqVO)); |
| | | } |
| | | |
| | | @PostMapping("/predict-risk") |
| | | @Operation(summary = "AI ç产é£é©é¢æµ") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')") |
| | | public CommonResult<MesProAiRiskPredictRespVO> predictRisk(@Valid @RequestBody MesProAiRiskPredictReqVO reqVO) { |
| | | return success(proAiService.predictRisk(reqVO)); |
| | | } |
| | | |
| | | @PostMapping("/predict-delivery") |
| | | @Operation(summary = "AI æ¯å¦è½ææ¶äº¤ä»") |
| | | @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')") |
| | | public CommonResult<MesProAiDeliveryPredictRespVO> predictDelivery(@Valid @RequestBody MesProAiDeliveryPredictReqVO reqVO) { |
| | | return success(proAiService.predictDelivery(reqVO)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - AI ææ¶äº¤ä»é¢æµè¯·æ± VO") |
| | | @Data |
| | | public class MesProAiDeliveryPredictReqVO { |
| | | |
| | | @Schema(description = "å·¥å ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "å·¥å ID ä¸è½ä¸ºç©º") |
| | | private Long workOrderId; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管çåå° - AI ææ¶äº¤ä»é¢æµååº VO") |
| | | @Data |
| | | public class MesProAiDeliveryPredictRespVO { |
| | | |
| | | @Schema(description = "æ¯å¦è½ææ¶äº¤ä»", example = "true") |
| | | private Boolean onTime; |
| | | |
| | | @Schema(description = "置信度ï¼0-100ï¼", example = "85.5") |
| | | private Double confidence; |
| | | |
| | | @Schema(description = "AI æ¨çåæ") |
| | | private String reasoning; |
| | | |
| | | @Schema(description = "å»¶æå ç´ ") |
| | | private List<String> delayFactors; |
| | | |
| | | @Schema(description = "ç产è¿åº¦ç¾åæ¯", example = "60.0") |
| | | private BigDecimal progressPercent; |
| | | |
| | | @Schema(description = "è·ç¦»éæ±æ¥æå©ä½å¤©æ°", example = "10") |
| | | private Integer remainingDays; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - AI ç产æ¶é¿é¢æµè¯·æ± VO") |
| | | @Data |
| | | public class MesProAiDurationPredictReqVO { |
| | | |
| | | @Schema(description = "å·¥å ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "å·¥å ID ä¸è½ä¸ºç©º") |
| | | private Long workOrderId; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管çåå° - AI ç产æ¶é¿é¢æµååº VO") |
| | | @Data |
| | | public class MesProAiDurationPredictRespVO { |
| | | |
| | | @Schema(description = "颿µæ»æ¶é¿ï¼å°æ¶ï¼", example = "48.5") |
| | | private BigDecimal predictedTotalHours; |
| | | |
| | | @Schema(description = "æ åæ»æ¶é¿ï¼å°æ¶ï¼", example = "40.0") |
| | | private BigDecimal standardTotalHours; |
| | | |
| | | @Schema(description = "AI æ¨çåæ") |
| | | private String reasoning; |
| | | |
| | | @Schema(description = "å
³é®å
³æ³¨ç¹") |
| | | private List<String> keyPoints; |
| | | |
| | | @Schema(description = "åå·¥åºæ¶é¿æç»") |
| | | private List<ProcessTimeBreakdown> processBreakdown; |
| | | |
| | | @Schema(description = "å·¥åºæ¶é¿æç»") |
| | | @Data |
| | | public static class ProcessTimeBreakdown { |
| | | |
| | | @Schema(description = "å·¥åºåç§°", example = "车å") |
| | | private String processName; |
| | | |
| | | @Schema(description = "å·¥åºé¡ºåº", example = "1") |
| | | private Integer sort; |
| | | |
| | | @Schema(description = "æ åå夿¶é´ï¼åéï¼", example = "30") |
| | | private Integer standardPrepareTime; |
| | | |
| | | @Schema(description = "æ åçå¾
æ¶é´ï¼åéï¼", example = "15") |
| | | private Integer standardWaitTime; |
| | | |
| | | @Schema(description = "æ åç产æ¶é´ï¼å°æ¶ï¼", example = "8.0") |
| | | private BigDecimal standardProductionTime; |
| | | |
| | | @Schema(description = "颿µç产æ¶é´ï¼å°æ¶ï¼", example = "10.5") |
| | | private BigDecimal predictedProductionTime; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - AI ç©æçç¼ºé¢æµè¯·æ± VO") |
| | | @Data |
| | | public class MesProAiMaterialPredictReqVO { |
| | | |
| | | @Schema(description = "å·¥å ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "å·¥å ID ä¸è½ä¸ºç©º") |
| | | private Long workOrderId; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管çåå° - AI ç©æçç¼ºé¢æµååº VO") |
| | | @Data |
| | | public class MesProAiMaterialPredictRespVO { |
| | | |
| | | @Schema(description = "æ´ä½é£é©ç级ï¼1=ä½é£é© 2=ä¸é£é© 3=é«é£é©", example = "2") |
| | | private Integer riskLevel; |
| | | |
| | | @Schema(description = "AI 综ååæ") |
| | | private String reasoning; |
| | | |
| | | @Schema(description = "ç©æé£é©æç»") |
| | | private List<RiskItem> riskItems; |
| | | |
| | | @Schema(description = "ç©æé£é©é¡¹") |
| | | @Data |
| | | public static class RiskItem { |
| | | |
| | | @Schema(description = "ç©æç¼ç ", example = "MAT-001") |
| | | private String itemCode; |
| | | |
| | | @Schema(description = "ç©æåç§°", example = "èºä¸") |
| | | private String itemName; |
| | | |
| | | @Schema(description = "éæ±é", example = "1000") |
| | | private BigDecimal requiredQuantity; |
| | | |
| | | @Schema(description = "åºåå¯ç¨é", example = "500") |
| | | private BigDecimal availableQuantity; |
| | | |
| | | @Schema(description = "ç缺æ°é", example = "500") |
| | | private BigDecimal shortageQuantity; |
| | | |
| | | @Schema(description = "é£é©ç级ï¼å
è¶³/ç´§å¼ /ç缺", example = "ç缺") |
| | | private String riskLevel; |
| | | |
| | | @Schema(description = "å»ºè®®æªæ½", example = "建议紧æ¥éè´") |
| | | private String suggestion; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - AI ç产é£é©é¢æµè¯·æ± VO") |
| | | @Data |
| | | public class MesProAiRiskPredictReqVO { |
| | | |
| | | @Schema(description = "å·¥å ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "å·¥å ID ä¸è½ä¸ºç©º") |
| | | private Long workOrderId; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管çåå° - AI ç产é£é©é¢æµååº VO") |
| | | @Data |
| | | public class MesProAiRiskPredictRespVO { |
| | | |
| | | @Schema(description = "æ´ä½é£é©ç级ï¼1=ä½é£é© 2=ä¸é£é© 3=é«é£é©", example = "2") |
| | | private Integer overallRiskLevel; |
| | | |
| | | @Schema(description = "AI 综ååæ") |
| | | private String reasoning; |
| | | |
| | | @Schema(description = "å
³é®å
³æ³¨ç¹") |
| | | private List<String> keyPoints; |
| | | |
| | | @Schema(description = "é£é©æç»å表") |
| | | private List<RiskItem> risks; |
| | | |
| | | @Schema(description = "é£é©é¡¹") |
| | | @Data |
| | | public static class RiskItem { |
| | | |
| | | @Schema(description = "é£é©ç±»å«ï¼è´¨é/设å¤/ç©æ/å·¥èºç¶é¢", example = "设å¤") |
| | | private String category; |
| | | |
| | | @Schema(description = "é£é©æè¿°", example = "设å¤A䏿¬¡ä¿å
»è·ä»å·²è¶
è¿30天") |
| | | private String description; |
| | | |
| | | @Schema(description = "严éç¨åº¦ï¼é«/ä¸/ä½", example = "ä¸") |
| | | private String severity; |
| | | |
| | | @Schema(description = "å»ºè®®æªæ½", example = "建议å¨å¼å§ç产åè¿è¡è®¾å¤ä¿å
»") |
| | | private String suggestion; |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.qc.ai; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ai.vo.MesQcAiSuggestReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ai.vo.MesQcAiSuggestRespVO; |
| | | import cn.iocoder.yudao.module.mes.service.qc.ai.MesQcAiService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | |
| | | @Tag(name = "管çåå° - MES QC AI è¾
å©å¤å®") |
| | | @RestController |
| | | @RequestMapping("/mes/qc/ai") |
| | | public class MesQcAiController { |
| | | |
| | | @Resource |
| | | private MesQcAiService qcAiService; |
| | | |
| | | @PostMapping("/suggest-result") |
| | | @Operation(summary = "AI è¾
å©å¤å®æ£éªç»æ") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc:update')") |
| | | public CommonResult<MesQcAiSuggestRespVO> suggestCheckResult(@Valid @RequestBody MesQcAiSuggestReqVO reqVO) { |
| | | return success(qcAiService.suggestCheckResult(reqVO)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.qc.ai.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - AI è¾
å©å¤å®è¯·æ± VO") |
| | | @Data |
| | | public class MesQcAiSuggestReqVO { |
| | | |
| | | @Schema(description = "è´¨æ£å ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "è´¨æ£å ID ä¸è½ä¸ºç©º") |
| | | private Long qcId; |
| | | |
| | | @Schema(description = "è´¨æ£ç±»å", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "è´¨æ£ç±»åä¸è½ä¸ºç©º") |
| | | private Integer qcType; |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.qc.ai.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Schema(description = "管çåå° - AI è¾
å©å¤å®ååº VO") |
| | | @Data |
| | | public class MesQcAiSuggestRespVO { |
| | | |
| | | @Schema(description = "å»ºè®®çæ£éªç»æç¼ç ", example = "1") |
| | | private Integer suggestedResult; |
| | | |
| | | @Schema(description = "å»ºè®®çæ£éªç»æåç§°", example = "åæ ¼") |
| | | private String suggestedResultName; |
| | | |
| | | @Schema(description = "AI æ¨ç便®") |
| | | private String reasoning; |
| | | |
| | | @Schema(description = "å
³é®å
³æ³¨ç¹") |
| | | private List<String> keyPoints; |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * MES æ£éªç»æè®°å½ Mapper |
| | | * |
| | |
| | | .eq(MesQcIndicatorResultDO::getQcType, qcType)); |
| | | } |
| | | |
| | | default List<MesQcIndicatorResultDO> selectListByQcIdAndType(Long qcId, Integer qcType) { |
| | | return selectList(new LambdaQueryWrapperX<MesQcIndicatorResultDO>() |
| | | .eq(MesQcIndicatorResultDO::getQcId, qcId) |
| | | .eq(MesQcIndicatorResultDO::getQcType, qcType)); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.pro.ai; |
| | | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo.*; |
| | | |
| | | public interface MesProAiService { |
| | | |
| | | MesProAiMaterialPredictRespVO predictMaterial(MesProAiMaterialPredictReqVO reqVO); |
| | | |
| | | MesProAiDurationPredictRespVO predictDuration(MesProAiDurationPredictReqVO reqVO); |
| | | |
| | | MesProAiRiskPredictRespVO predictRisk(MesProAiRiskPredictReqVO reqVO); |
| | | |
| | | MesProAiDeliveryPredictRespVO predictDelivery(MesProAiDeliveryPredictReqVO reqVO); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.pro.ai; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.hutool.json.JSONUtil; |
| | | import cn.iocoder.yudao.module.ai.api.chat.AiChatApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.ai.vo.*; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationMachineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.mps.MesProMpsDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskIssueDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.md.workstation.MesMdWorkstationMachineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskIssueMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderBomMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper; |
| | | import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_WORK_ORDER_NOT_EXISTS; |
| | | |
| | | @Slf4j |
| | | @Service |
| | | public class MesProAiServiceImpl implements MesProAiService { |
| | | |
| | | // ==================== System Prompts ==================== |
| | | |
| | | private static final String SYSTEM_PROMPT_MATERIAL = """ |
| | | ä½ æ¯ä¸ä½èµæ·±çå¶é ä¸ç©æç®¡çä¸å®¶ï¼æ
é¿åæç产工åçç©æé½å¥æ§åç缺é£é©ã |
| | | ä½ éè¦ç»¼ååæä»¥ä¸ä¿¡æ¯ï¼ç»åºä¸ä¸çç©æé¢æµå»ºè®®ï¼ |
| | | |
| | | ## åæç»´åº¦ |
| | | 1. BOM éæ± vs åºåå¯ç¨éï¼å¯¹æ¯æ¯ä¸ªç©æçé¢è®¡ä½¿ç¨éä¸å½ååºåå¯ç¨é |
| | | 2. åºåå¯ç¨é = å¨åºæ°é - å»ç»æ°é - å·²å ç¨æ°é |
| | | 3. å·²æææ
åµï¼å·²ç»åæ¾å°å·¥ä½çç©ææ¯å¦å
è¶³ |
| | | 4. é£é©è¯ä¼°ï¼å¯¹æç缺é£é©çç©ææ æ³¨é£é©ç级 |
| | | |
| | | ## é£é©ç级å®ä¹ |
| | | - å
è¶³ï¼å¯ç¨é >= éæ±é |
| | | - ç´§å¼ ï¼å¯ç¨é >= éæ±éç50% ä½ < éæ±é |
| | | - ç缺ï¼å¯ç¨é < éæ±éç50% æå¯ç¨é为0 |
| | | |
| | | ## è¾åºæ ¼å¼ |
| | | 请æä»¥ä¸JSONæ ¼å¼è¿åï¼ä¸è¦å
å«å
¶ä»å
容ï¼ï¼ |
| | | {"riskLevel": 1, "reasoning": "综ååæ...", "riskItems": [{"itemCode": "...", "itemName": "...", "requiredQuantity": 100.0, "availableQuantity": 50.0, "shortageQuantity": 50.0, "riskLevel": "ç´§å¼ ", "suggestion": "建议紧æ¥éè´æè°æ¨"}]} |
| | | |
| | | riskLevel åå¼ï¼1=ä½é£é© 2=ä¸é£é© 3=é«é£é© |
| | | """; |
| | | |
| | | private static final String SYSTEM_PROMPT_DURATION = """ |
| | | ä½ æ¯ä¸ä½èµæ·±çç产计åä¸å®¶ï¼æ
é¿é¢æµç产工åçå®é
ç产æ¶é¿ã |
| | | ä½ éè¦ç»¼ååæä»¥ä¸ä¿¡æ¯ï¼ç»åºä¸ä¸çæ¶é¿é¢æµå»ºè®®ï¼ |
| | | |
| | | ## åæä¾æ® |
| | | 1. æ åå·¥æ¶ï¼å·¥èºè·¯çº¿è®¾å®çæ åå夿¶é´ãçå¾
æ¶é´ãç产æ¶é´ |
| | | 2. è®¾å¤æçï¼å
³è设å¤çæ¯å°æ¶äº§éï¼ä¼ é履带åç |
| | | 3. ç产æ°éï¼å·¥åè®¡åæ°é |
| | | 4. å®é
å岿°æ®ï¼å·²å®æå·¥åºçå®é
ç产æ¶é¿ |
| | | 5. 计ç®å
¬å¼ï¼é¢æµæ¶é¿ = å夿¶é´ + çå¾
æ¶é´ + (è®¡åæ°é / (è®¾å¤æç * ä¼ é履带系æ°))ï¼æçåä½ä¸ºæ¯å°æ¶äº§éï¼ |
| | | |
| | | ## è¾åºæ ¼å¼ |
| | | 请æä»¥ä¸JSONæ ¼å¼è¿åï¼ä¸è¦å
å«å
¶ä»å
容ï¼ï¼ |
| | | {"predictedTotalHours": 48.5, "standardTotalHours": 40.0, "reasoning": "...", "keyPoints": ["å
³é®å·¥åºAæ¯ç¶é¢"], "processBreakdown": [{"processName": "...", "sort": 1, "standardPrepareTime": 30, "standardWaitTime": 15, "standardProductionTime": 8.0, "predictedProductionTime": 10.5}]} |
| | | |
| | | predictedTotalHours å standardTotalHours ä¸ºå°æ° |
| | | """; |
| | | |
| | | private static final String SYSTEM_PROMPT_RISK = """ |
| | | ä½ æ¯ä¸ä½èµæ·±çå¶é é£é©ç®¡çä¸å®¶ï¼æ
é¿è¯å«ç产è¿ç¨ä¸çåç±»é£é©ã |
| | | ä½ éè¦ç»¼ååæä»¥ä¸ä¿¡æ¯ï¼ç»åºä¸ä¸çé£é©è¯ä¼°ï¼ |
| | | |
| | | ## åæç»´åº¦ |
| | | 1. è´¨éé£é©ï¼è¿ç¨æ£éª(IPQC)ç缺é·çæ°æ®ï¼è´å½/严é/轻微缺é·çï¼ |
| | | 2. 设å¤é£é©ï¼è®¾å¤ç¶æã䏿¬¡ä¿å
»æ¶é´ã䏿¬¡ç¹æ£æ¶é´ãç产æç |
| | | 3. ç©æé£é©ï¼ç©æåºåå¯ç¨éä¸éæ±éçå¯¹æ¯ |
| | | 4. å·¥èºç¶é¢ï¼å
³é®å·¥åºç设å¤äº§è½ã任塿éæ
åµ |
| | | |
| | | ## é£é©ç级å®ä¹ |
| | | - é«ï¼åå¨è´å½ç¼ºé·ç>5% æ è®¾å¤æ
é æ å
³é®ç©æç缺>50% æ å
³é®å·¥åºä¸¥é积å |
| | | - ä¸ï¼åå¨ä¸¥é缺é·ç>10% æ 设å¤é¿ææªä¿å
» æ ç©æç´§å¼ |
| | | - ä½ï¼åé¡¹ææ æ£å¸¸ |
| | | |
| | | ## è¾åºæ ¼å¼ |
| | | 请æä»¥ä¸JSONæ ¼å¼è¿åï¼ä¸è¦å
å«å
¶ä»å
容ï¼ï¼ |
| | | {"overallRiskLevel": 2, "reasoning": "...", "keyPoints": ["å
³æ³¨ç¹1"], "risks": [{"category": "设å¤", "description": "设å¤A䏿¬¡ä¿å
»è·ä»å·²è¶
30天", "severity": "ä¸", "suggestion": "建议ç产åä¿å
»"}]} |
| | | |
| | | overallRiskLevel åå¼ï¼1=ä½é£é© 2=ä¸é£é© 3=é«é£é© |
| | | riskLevel åå¼ï¼å
è¶³/ç´§å¼ /ç缺 |
| | | severity åå¼ï¼é«/ä¸/ä½ |
| | | category åå¼ï¼è´¨é/设å¤/ç©æ/å·¥èºç¶é¢ |
| | | """; |
| | | |
| | | private static final String SYSTEM_PROMPT_DELIVERY = """ |
| | | ä½ æ¯ä¸ä½èµæ·±ç订å交ä»ç®¡çä¸å®¶ï¼æ
é¿è¯ä¼°å·¥åæ¯å¦è½ææ¶äº¤ä»ã |
| | | ä½ éè¦ç»¼ååæä»¥ä¸ä¿¡æ¯ï¼ç»åºä¸ä¸ç交ä»è¯ä¼°ï¼ |
| | | |
| | | ## åæä¾æ® |
| | | 1. å½åè¿åº¦ï¼å·²ç产æ°é / è®¡åæ°éçç¾åæ¯ |
| | | 2. å©ä½æ¶é´ï¼è·ç¦»éæ±æ¥æç天æ°ï¼å·¥ä½æ¥ææ¯å¤©8å°æ¶è®¡ç®ï¼ |
| | | 3. é¢è®¡å©ä½å·¥æ¶ï¼æ ¹æ®å·¥èºè·¯çº¿åè®¾å¤æç颿µçå©ä½ç产æ¶é¿ |
| | | 4. ç产éåº¦ï¼æ ¹æ®å·²å®æä»»å¡çå®é
ç产éç |
| | | 5. ä»»å¡ç¶æï¼åå·¥åºç宿æ
åµ |
| | | |
| | | ## å¤å®é»è¾ |
| | | - ææ¶äº¤ä»ï¼å©ä½å¤©æ° * 8å°æ¶ >= é¢è®¡å©ä½å·¥æ¶ï¼ä¸è¿åº¦æ£å¸¸ |
| | | - æå»¶æé£é©ï¼å©ä½å¤©æ° * 8å°æ¶ >= é¢è®¡å©ä½å·¥æ¶ç80%ï¼ä½ä¸è¶³100% |
| | | - æ æ³ææ¶äº¤ä»ï¼å©ä½å¤©æ° * 8å°æ¶ < é¢è®¡å©ä½å·¥æ¶ç80%ï¼æå
³é®å·¥åºå¡é» |
| | | |
| | | ## è¾åºæ ¼å¼ |
| | | 请æä»¥ä¸JSONæ ¼å¼è¿åï¼ä¸è¦å
å«å
¶ä»å
容ï¼ï¼ |
| | | {"onTime": true, "confidence": 85.5, "reasoning": "...", "delayFactors": [], "progressPercent": 60.0, "remainingDays": 10} |
| | | |
| | | onTime: true=坿æ¶äº¤ä» false=æ æ³ææ¶äº¤ä» |
| | | confidence: 0-100çæ°åï¼è¡¨ç¤ºè¯ä¼°ç½®ä¿¡åº¦ |
| | | """; |
| | | |
| | | // ==================== Dependencies ==================== |
| | | |
| | | @Resource |
| | | private MesProWorkOrderService workOrderService; |
| | | @Resource |
| | | private MesProWorkOrderBomMapper workOrderBomMapper; |
| | | @Resource |
| | | private MesProWorkOrderProcessService workOrderProcessService; |
| | | @Resource |
| | | private MesProTaskMapper taskMapper; |
| | | @Resource |
| | | private MesProTaskIssueMapper taskIssueMapper; |
| | | @Resource |
| | | private MesProFeedbackMapper feedbackMapper; |
| | | @Resource |
| | | private MesQcIpqcMapper ipqcMapper; |
| | | @Resource |
| | | private MesWmMaterialStockMapper materialStockMapper; |
| | | @Resource |
| | | private MesMdWorkstationMachineMapper workstationMachineMapper; |
| | | @Resource |
| | | private MesDvMachineryMapper machineryMapper; |
| | | @Resource |
| | | private MesProMpsMapper mpsMapper; |
| | | @Resource |
| | | private MesProProcessService processService; |
| | | @Resource |
| | | private MdmItemApi mdmItemApi; |
| | | @Resource |
| | | private CrmCustomerApi crmCustomerApi; |
| | | @Resource |
| | | private AiChatApi aiChatApi; |
| | | |
| | | // ==================== 1. ç©æçç¼ºé¢æµ ==================== |
| | | |
| | | @Override |
| | | public MesProAiMaterialPredictRespVO predictMaterial(MesProAiMaterialPredictReqVO reqVO) { |
| | | try { |
| | | Map<String, Object> data = gatherMaterialData(reqVO.getWorkOrderId()); |
| | | String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_MATERIAL, JSONUtil.toJsonPrettyStr(data)); |
| | | log.info("AI ç©æé¢æµ: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse); |
| | | return parseMaterialResponse(aiResponse); |
| | | } catch (Exception e) { |
| | | log.warn("AI ç©æé¢æµå¤±è´¥ï¼workOrderId={}", reqVO.getWorkOrderId(), e); |
| | | MesProAiMaterialPredictRespVO fallback = new MesProAiMaterialPredictRespVO(); |
| | | fallback.setReasoning("AI åæææ¶ä¸å¯ç¨ï¼è¯·ç¨åéè¯"); |
| | | return fallback; |
| | | } |
| | | } |
| | | |
| | | private Map<String, Object> gatherMaterialData(Long workOrderId) { |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(workOrderId); |
| | | if (workOrder == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS); |
| | | |
| | | data.put("å·¥åç¼å·", workOrder.getCode()); |
| | | data.put("å·¥ååç§°", workOrder.getName()); |
| | | data.put("è®¡åæ°é", workOrder.getQuantity()); |
| | | data.put("å·²ç产æ°é", workOrder.getQuantityProduced()); |
| | | fillItemInfo(data, workOrder.getProductId()); |
| | | |
| | | // BOM ç©æéæ± |
| | | List<MesProWorkOrderBomDO> bomList = workOrderBomMapper.selectListByWorkOrderId(workOrderId); |
| | | if (CollUtil.isNotEmpty(bomList)) { |
| | | List<Map<String, Object>> materials = new ArrayList<>(); |
| | | for (MesProWorkOrderBomDO bom : bomList) { |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("ç©æID", bom.getItemId()); |
| | | String itemName = resolveItemName(bom.getItemId()); |
| | | item.put("ç©æåç§°", itemName); |
| | | item.put("éæ±é", bom.getQuantity()); |
| | | |
| | | // æ¥è¯¢åºåå¯ç¨é |
| | | List<MesWmMaterialStockDO> stocks = materialStockMapper.selectList( |
| | | new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesWmMaterialStockDO>() |
| | | .eq(MesWmMaterialStockDO::getItemId, bom.getItemId()) |
| | | .ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO)); |
| | | BigDecimal available = BigDecimal.ZERO; |
| | | if (CollUtil.isNotEmpty(stocks)) { |
| | | for (MesWmMaterialStockDO stock : stocks) { |
| | | BigDecimal frozen = stock.getFrozenQuantity() != null ? stock.getFrozenQuantity() : BigDecimal.ZERO; |
| | | BigDecimal reserved = stock.getReservedQuantity() != null ? stock.getReservedQuantity() : BigDecimal.ZERO; |
| | | available = available.add(stock.getQuantity().subtract(frozen).subtract(reserved)); |
| | | } |
| | | } |
| | | item.put("åºåå¯ç¨é", available.compareTo(BigDecimal.ZERO) > 0 ? available : BigDecimal.ZERO); |
| | | materials.add(item); |
| | | } |
| | | data.put("ç©æéæ±æ¸
å", materials); |
| | | } |
| | | |
| | | // å·²æææ
åµ |
| | | List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(workOrderId); |
| | | if (CollUtil.isNotEmpty(tasks)) { |
| | | List<Map<String, Object>> issuedMaterials = new ArrayList<>(); |
| | | for (MesProTaskDO task : tasks) { |
| | | List<MesProTaskIssueDO> issues = taskIssueMapper.selectListByTaskId(task.getId()); |
| | | for (MesProTaskIssueDO issue : issues) { |
| | | Map<String, Object> issued = new LinkedHashMap<>(); |
| | | issued.put("ç©æID", issue.getItemId()); |
| | | issued.put("å·²æææ°é", issue.getIssuedQuantity()); |
| | | issued.put("å·²ä½¿ç¨æ°é", issue.getUsedQuantity()); |
| | | issued.put("å¯ç¨æ°é", issue.getAvailableQuantity()); |
| | | issuedMaterials.add(issued); |
| | | } |
| | | } |
| | | if (!issuedMaterials.isEmpty()) { |
| | | data.put("å·²ææè®°å½", issuedMaterials); |
| | | } |
| | | } |
| | | |
| | | return data; |
| | | } |
| | | |
| | | private MesProAiMaterialPredictRespVO parseMaterialResponse(String aiResponse) { |
| | | MesProAiMaterialPredictRespVO respVO = new MesProAiMaterialPredictRespVO(); |
| | | try { |
| | | String json = extractJson(aiResponse); |
| | | Map<String, Object> parsed = JSONUtil.toBean(json, Map.class); |
| | | Object riskLevel = parsed.get("riskLevel"); |
| | | if (riskLevel instanceof Number num) respVO.setRiskLevel(num.intValue()); |
| | | respVO.setReasoning((String) parsed.get("reasoning")); |
| | | @SuppressWarnings("unchecked") |
| | | List<Map<String, Object>> items = (List<Map<String, Object>>) parsed.get("riskItems"); |
| | | if (CollUtil.isNotEmpty(items)) { |
| | | respVO.setRiskItems(items.stream().map(m -> { |
| | | MesProAiMaterialPredictRespVO.RiskItem ri = new MesProAiMaterialPredictRespVO.RiskItem(); |
| | | ri.setItemCode((String) m.get("itemCode")); |
| | | ri.setItemName((String) m.get("itemName")); |
| | | ri.setRequiredQuantity(toBigDecimal(m.get("requiredQuantity"))); |
| | | ri.setAvailableQuantity(toBigDecimal(m.get("availableQuantity"))); |
| | | ri.setShortageQuantity(toBigDecimal(m.get("shortageQuantity"))); |
| | | ri.setRiskLevel((String) m.get("riskLevel")); |
| | | ri.setSuggestion((String) m.get("suggestion")); |
| | | return ri; |
| | | }).collect(Collectors.toList())); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("è§£æ AI ç©æé¢æµååºå¤±è´¥: {}", aiResponse, e); |
| | | respVO.setReasoning(aiResponse); |
| | | } |
| | | return respVO; |
| | | } |
| | | |
| | | // ==================== 2. ç产æ¶é¿é¢æµ ==================== |
| | | |
| | | @Override |
| | | public MesProAiDurationPredictRespVO predictDuration(MesProAiDurationPredictReqVO reqVO) { |
| | | try { |
| | | Map<String, Object> data = gatherDurationData(reqVO.getWorkOrderId()); |
| | | String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_DURATION, JSONUtil.toJsonPrettyStr(data)); |
| | | log.info("AI æ¶é¿é¢æµ: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse); |
| | | return parseDurationResponse(aiResponse); |
| | | } catch (Exception e) { |
| | | log.warn("AI æ¶é¿é¢æµå¤±è´¥ï¼workOrderId={}", reqVO.getWorkOrderId(), e); |
| | | MesProAiDurationPredictRespVO fallback = new MesProAiDurationPredictRespVO(); |
| | | fallback.setReasoning("AI åæææ¶ä¸å¯ç¨ï¼è¯·ç¨åéè¯"); |
| | | return fallback; |
| | | } |
| | | } |
| | | |
| | | private Map<String, Object> gatherDurationData(Long workOrderId) { |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(workOrderId); |
| | | if (workOrder == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS); |
| | | |
| | | data.put("å·¥åç¼å·", workOrder.getCode()); |
| | | data.put("è®¡åæ°é", workOrder.getQuantity()); |
| | | data.put("å·²ç产æ°é", workOrder.getQuantityProduced()); |
| | | fillItemInfo(data, workOrder.getProductId()); |
| | | |
| | | // å·¥åºä¿¡æ¯ |
| | | List<MesProWorkOrderProcessDO> processes = workOrderProcessService.getWorkOrderProcessListByWorkOrderId(workOrderId); |
| | | List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(workOrderId); |
| | | Map<Long, MesProTaskDO> taskByProcessId = new HashMap<>(); |
| | | if (CollUtil.isNotEmpty(tasks)) { |
| | | for (MesProTaskDO task : tasks) { |
| | | if (task.getProcessId() != null) { |
| | | taskByProcessId.put(task.getProcessId(), task); |
| | | } |
| | | } |
| | | } |
| | | |
| | | List<Map<String, Object>> processList = new ArrayList<>(); |
| | | BigDecimal standardTotalHours = BigDecimal.ZERO; |
| | | for (MesProWorkOrderProcessDO proc : processes) { |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | String processName = resolveProcessName(proc.getProcessId()); |
| | | item.put("å·¥åºåç§°", processName); |
| | | item.put("顺åº", proc.getSort()); |
| | | item.put("æ åå夿¶é´(åé)", proc.getPrepareTime() != null ? proc.getPrepareTime() : 0); |
| | | item.put("æ åçå¾
æ¶é´(åé)", proc.getWaitTime() != null ? proc.getWaitTime() : 0); |
| | | item.put("æ¯å¦å
³é®å·¥åº", proc.getKeyFlag() != null && proc.getKeyFlag()); |
| | | item.put("æ¯å¦è´¨æ£å·¥åº", proc.getCheckFlag() != null && proc.getCheckFlag()); |
| | | |
| | | // è®¾å¤æç |
| | | MesProTaskDO task = taskByProcessId.get(proc.getProcessId()); |
| | | if (task != null && task.getWorkstationId() != null) { |
| | | BigDecimal totalEfficiency = getWorkstationEfficiency(task.getWorkstationId()); |
| | | item.put("è®¾å¤æ»æç(ä»¶/å°æ¶)", totalEfficiency); |
| | | if (totalEfficiency.compareTo(BigDecimal.ZERO) > 0) { |
| | | BigDecimal productionHours = workOrder.getQuantity() |
| | | .subtract(workOrder.getQuantityProduced() != null ? workOrder.getQuantityProduced() : BigDecimal.ZERO) |
| | | .divide(totalEfficiency, 4, RoundingMode.HALF_UP); |
| | | item.put("é¢è®¡ç产æ¶é´(å°æ¶)", productionHours); |
| | | } |
| | | item.put("ä»»å¡ç¶æ", task.getStatus()); |
| | | if (task.getDuration() != null) item.put("å·²ç¨æ¶é¿(å°æ¶)", task.getDuration()); |
| | | } else { |
| | | item.put("è®¾å¤æ»æç(ä»¶/å°æ¶)", "æªæäº§æ è®¾å¤æ°æ®"); |
| | | } |
| | | |
| | | // æ åæ¶é´ |
| | | int prepareMin = proc.getPrepareTime() != null ? proc.getPrepareTime() : 0; |
| | | int waitMin = proc.getWaitTime() != null ? proc.getWaitTime() : 0; |
| | | BigDecimal standardHours = BigDecimal.valueOf(prepareMin + waitMin).divide(BigDecimal.valueOf(60), 4, RoundingMode.HALF_UP); |
| | | standardTotalHours = standardTotalHours.add(standardHours); |
| | | item.put("æ ååå¤+çå¾
æ¶é´(å°æ¶)", standardHours); |
| | | |
| | | processList.add(item); |
| | | } |
| | | data.put("å·¥åºæç»", processList); |
| | | data.put("æ åæ»åå¤+çå¾
æ¶é´(å°æ¶)", standardTotalHours); |
| | | |
| | | return data; |
| | | } |
| | | |
| | | private BigDecimal getWorkstationEfficiency(Long workstationId) { |
| | | BigDecimal totalEfficiency = BigDecimal.ZERO; |
| | | List<MesMdWorkstationMachineDO> machines = workstationMachineMapper.selectListByWorkstationId(workstationId); |
| | | if (CollUtil.isNotEmpty(machines)) { |
| | | for (MesMdWorkstationMachineDO wm : machines) { |
| | | MesDvMachineryDO machinery = machineryMapper.selectById(wm.getMachineryId()); |
| | | if (machinery != null && machinery.getProductionEfficiency() != null) { |
| | | BigDecimal efficiency = machinery.getProductionEfficiency(); |
| | | if (machinery.getConveyorBelt() != null && machinery.getConveyorBelt() > 0) { |
| | | efficiency = efficiency.multiply(BigDecimal.valueOf(machinery.getConveyorBelt())); |
| | | } |
| | | totalEfficiency = totalEfficiency.add(efficiency); |
| | | } |
| | | } |
| | | } |
| | | return totalEfficiency; |
| | | } |
| | | |
| | | private MesProAiDurationPredictRespVO parseDurationResponse(String aiResponse) { |
| | | MesProAiDurationPredictRespVO respVO = new MesProAiDurationPredictRespVO(); |
| | | try { |
| | | String json = extractJson(aiResponse); |
| | | Map<String, Object> parsed = JSONUtil.toBean(json, Map.class); |
| | | respVO.setPredictedTotalHours(toBigDecimal(parsed.get("predictedTotalHours"))); |
| | | respVO.setStandardTotalHours(toBigDecimal(parsed.get("standardTotalHours"))); |
| | | respVO.setReasoning((String) parsed.get("reasoning")); |
| | | @SuppressWarnings("unchecked") |
| | | List<String> keyPoints = (List<String>) parsed.get("keyPoints"); |
| | | respVO.setKeyPoints(keyPoints); |
| | | @SuppressWarnings("unchecked") |
| | | List<Map<String, Object>> breakdown = (List<Map<String, Object>>) parsed.get("processBreakdown"); |
| | | if (CollUtil.isNotEmpty(breakdown)) { |
| | | respVO.setProcessBreakdown(breakdown.stream().map(m -> { |
| | | MesProAiDurationPredictRespVO.ProcessTimeBreakdown pt = new MesProAiDurationPredictRespVO.ProcessTimeBreakdown(); |
| | | pt.setProcessName((String) m.get("processName")); |
| | | pt.setSort(toInteger(m.get("sort"))); |
| | | pt.setStandardPrepareTime(toInteger(m.get("standardPrepareTime"))); |
| | | pt.setStandardWaitTime(toInteger(m.get("standardWaitTime"))); |
| | | pt.setStandardProductionTime(toBigDecimal(m.get("standardProductionTime"))); |
| | | pt.setPredictedProductionTime(toBigDecimal(m.get("predictedProductionTime"))); |
| | | return pt; |
| | | }).collect(Collectors.toList())); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("è§£æ AI æ¶é¿é¢æµååºå¤±è´¥: {}", aiResponse, e); |
| | | respVO.setReasoning(aiResponse); |
| | | } |
| | | return respVO; |
| | | } |
| | | |
| | | // ==================== 3. ç产é£é©é¢æµ ==================== |
| | | |
| | | @Override |
| | | public MesProAiRiskPredictRespVO predictRisk(MesProAiRiskPredictReqVO reqVO) { |
| | | try { |
| | | Map<String, Object> data = gatherRiskData(reqVO.getWorkOrderId()); |
| | | String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_RISK, JSONUtil.toJsonPrettyStr(data)); |
| | | log.info("AI é£é©é¢æµ: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse); |
| | | return parseRiskResponse(aiResponse); |
| | | } catch (Exception e) { |
| | | log.warn("AI é£é©é¢æµå¤±è´¥ï¼workOrderId={}", reqVO.getWorkOrderId(), e); |
| | | MesProAiRiskPredictRespVO fallback = new MesProAiRiskPredictRespVO(); |
| | | fallback.setReasoning("AI åæææ¶ä¸å¯ç¨ï¼è¯·ç¨åéè¯"); |
| | | return fallback; |
| | | } |
| | | } |
| | | |
| | | private Map<String, Object> gatherRiskData(Long workOrderId) { |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(workOrderId); |
| | | if (workOrder == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS); |
| | | |
| | | data.put("å·¥åç¼å·", workOrder.getCode()); |
| | | data.put("å·¥ååç§°", workOrder.getName()); |
| | | data.put("è®¡åæ°é", workOrder.getQuantity()); |
| | | data.put("å·²ç产æ°é", workOrder.getQuantityProduced()); |
| | | fillItemInfo(data, workOrder.getProductId()); |
| | | |
| | | // è´¨éé£é© - IPQC |
| | | List<MesQcIpqcDO> ipqcList = ipqcMapper.selectList( |
| | | new LambdaQueryWrapper<MesQcIpqcDO>() |
| | | .eq(MesQcIpqcDO::getWorkOrderId, workOrderId)); |
| | | if (CollUtil.isNotEmpty(ipqcList)) { |
| | | List<Map<String, Object>> qcData = ipqcList.stream().map(ipqc -> { |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("æ£éªåç¼å·", ipqc.getCode()); |
| | | item.put("è´å½ç¼ºé·ç", ipqc.getCriticalRate() != null ? ipqc.getCriticalRate() + "%" : "0%"); |
| | | item.put("严é缺é·ç", ipqc.getMajorRate() != null ? ipqc.getMajorRate() + "%" : "0%"); |
| | | item.put("轻微缺é·ç", ipqc.getMinorRate() != null ? ipqc.getMinorRate() + "%" : "0%"); |
| | | item.put("è´å½ç¼ºé·æ°", ipqc.getCriticalQuantity() != null ? ipqc.getCriticalQuantity() : 0); |
| | | item.put("严éç¼ºé·æ°", ipqc.getMajorQuantity() != null ? ipqc.getMajorQuantity() : 0); |
| | | item.put("è½»å¾®ç¼ºé·æ°", ipqc.getMinorQuantity() != null ? ipqc.getMinorQuantity() : 0); |
| | | item.put("æ£éªç»æ", ipqc.getCheckResult()); |
| | | return item; |
| | | }).collect(Collectors.toList()); |
| | | data.put("è¿ç¨æ£éª(IPQC)è®°å½", qcData); |
| | | } |
| | | |
| | | // 设å¤é£é© |
| | | List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(workOrderId); |
| | | if (CollUtil.isNotEmpty(tasks)) { |
| | | Set<Long> machineryIds = new HashSet<>(); |
| | | for (MesProTaskDO task : tasks) { |
| | | if (task.getWorkstationId() != null) { |
| | | List<MesMdWorkstationMachineDO> wms = workstationMachineMapper.selectListByWorkstationId(task.getWorkstationId()); |
| | | for (MesMdWorkstationMachineDO wm : wms) { |
| | | machineryIds.add(wm.getMachineryId()); |
| | | } |
| | | } |
| | | } |
| | | if (!machineryIds.isEmpty()) { |
| | | List<Map<String, Object>> equipmentData = new ArrayList<>(); |
| | | for (Long mid : machineryIds) { |
| | | MesDvMachineryDO machinery = machineryMapper.selectById(mid); |
| | | if (machinery != null) { |
| | | Map<String, Object> eq = new LinkedHashMap<>(); |
| | | eq.put("设å¤ç¼å·", machinery.getCode()); |
| | | eq.put("设å¤åç§°", machinery.getName()); |
| | | eq.put("设å¤ç¶æ", machinery.getStatus()); |
| | | eq.put("ç产æç(ä»¶/å°æ¶)", machinery.getProductionEfficiency()); |
| | | eq.put("ä¼ é履另忰", machinery.getConveyorBelt()); |
| | | eq.put("䏿¬¡ä¿å
»æ¶é´", machinery.getLastMaintenTime() != null |
| | | ? machinery.getLastMaintenTime().toString() : "æ è®°å½"); |
| | | eq.put("䏿¬¡ç¹æ£æ¶é´", machinery.getLastCheckTime() != null |
| | | ? machinery.getLastCheckTime().toString() : "æ è®°å½"); |
| | | equipmentData.add(eq); |
| | | } |
| | | } |
| | | data.put("å
³è设å¤ä¿¡æ¯", equipmentData); |
| | | } |
| | | } |
| | | |
| | | // ç©æé£é©ï¼è½»é级ï¼ä»
æ è®°å¯ç¨é < 20%çç©æï¼ |
| | | List<MesProWorkOrderBomDO> bomList = workOrderBomMapper.selectListByWorkOrderId(workOrderId); |
| | | if (CollUtil.isNotEmpty(bomList)) { |
| | | List<Map<String, Object>> materialRisks = new ArrayList<>(); |
| | | for (MesProWorkOrderBomDO bom : bomList) { |
| | | List<MesWmMaterialStockDO> stocks = materialStockMapper.selectList( |
| | | new LambdaQueryWrapper<MesWmMaterialStockDO>() |
| | | .eq(MesWmMaterialStockDO::getItemId, bom.getItemId()) |
| | | .ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO)); |
| | | BigDecimal available = BigDecimal.ZERO; |
| | | if (CollUtil.isNotEmpty(stocks)) { |
| | | for (MesWmMaterialStockDO stock : stocks) { |
| | | BigDecimal frozen = stock.getFrozenQuantity() != null ? stock.getFrozenQuantity() : BigDecimal.ZERO; |
| | | BigDecimal reserved = stock.getReservedQuantity() != null ? stock.getReservedQuantity() : BigDecimal.ZERO; |
| | | available = available.add(stock.getQuantity().subtract(frozen).subtract(reserved)); |
| | | } |
| | | } |
| | | if (bom.getQuantity() != null && bom.getQuantity().compareTo(BigDecimal.ZERO) > 0 |
| | | && available.compareTo(bom.getQuantity().multiply(BigDecimal.valueOf(0.2))) < 0) { |
| | | Map<String, Object> risk = new LinkedHashMap<>(); |
| | | risk.put("ç©æID", bom.getItemId()); |
| | | risk.put("ç©æåç§°", resolveItemName(bom.getItemId())); |
| | | risk.put("éæ±é", bom.getQuantity()); |
| | | risk.put("å¯ç¨é", available); |
| | | risk.put("ç¼ºå£æ¯ä¾", available.compareTo(BigDecimal.ZERO) > 0 |
| | | ? BigDecimal.ONE.subtract(available.divide(bom.getQuantity(), 2, RoundingMode.HALF_UP)).multiply(BigDecimal.valueOf(100)) + "%" |
| | | : "100%"); |
| | | materialRisks.add(risk); |
| | | } |
| | | } |
| | | if (!materialRisks.isEmpty()) { |
| | | data.put("ç©æç缺é£é©", materialRisks); |
| | | } |
| | | } |
| | | |
| | | // å·¥èºç¶é¢ - å
³é®å·¥åºä¿¡æ¯ |
| | | List<MesProWorkOrderProcessDO> processes = workOrderProcessService.getWorkOrderProcessListByWorkOrderId(workOrderId); |
| | | if (CollUtil.isNotEmpty(processes)) { |
| | | List<Map<String, Object>> keyProcesses = new ArrayList<>(); |
| | | for (MesProWorkOrderProcessDO proc : processes) { |
| | | if (proc.getKeyFlag() != null && proc.getKeyFlag()) { |
| | | Map<String, Object> kp = new LinkedHashMap<>(); |
| | | kp.put("å·¥åºåç§°", resolveProcessName(proc.getProcessId())); |
| | | kp.put("顺åº", proc.getSort()); |
| | | kp.put("æ åå夿¶é´(åé)", proc.getPrepareTime()); |
| | | kp.put("æ åçå¾
æ¶é´(åé)", proc.getWaitTime()); |
| | | keyProcesses.add(kp); |
| | | } |
| | | } |
| | | if (!keyProcesses.isEmpty()) { |
| | | data.put("å
³é®å·¥åº", keyProcesses); |
| | | } |
| | | } |
| | | |
| | | return data; |
| | | } |
| | | |
| | | private MesProAiRiskPredictRespVO parseRiskResponse(String aiResponse) { |
| | | MesProAiRiskPredictRespVO respVO = new MesProAiRiskPredictRespVO(); |
| | | try { |
| | | String json = extractJson(aiResponse); |
| | | Map<String, Object> parsed = JSONUtil.toBean(json, Map.class); |
| | | Object riskLevel = parsed.get("overallRiskLevel"); |
| | | if (riskLevel instanceof Number num) respVO.setOverallRiskLevel(num.intValue()); |
| | | respVO.setReasoning((String) parsed.get("reasoning")); |
| | | @SuppressWarnings("unchecked") |
| | | List<String> keyPoints = (List<String>) parsed.get("keyPoints"); |
| | | respVO.setKeyPoints(keyPoints); |
| | | @SuppressWarnings("unchecked") |
| | | List<Map<String, Object>> risks = (List<Map<String, Object>>) parsed.get("risks"); |
| | | if (CollUtil.isNotEmpty(risks)) { |
| | | respVO.setRisks(risks.stream().map(m -> { |
| | | MesProAiRiskPredictRespVO.RiskItem ri = new MesProAiRiskPredictRespVO.RiskItem(); |
| | | ri.setCategory((String) m.get("category")); |
| | | ri.setDescription((String) m.get("description")); |
| | | ri.setSeverity((String) m.get("severity")); |
| | | ri.setSuggestion((String) m.get("suggestion")); |
| | | return ri; |
| | | }).collect(Collectors.toList())); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("è§£æ AI é£é©é¢æµååºå¤±è´¥: {}", aiResponse, e); |
| | | respVO.setReasoning(aiResponse); |
| | | } |
| | | return respVO; |
| | | } |
| | | |
| | | // ==================== 4. æ¯å¦è½ææ¶äº¤ä» ==================== |
| | | |
| | | @Override |
| | | public MesProAiDeliveryPredictRespVO predictDelivery(MesProAiDeliveryPredictReqVO reqVO) { |
| | | try { |
| | | Map<String, Object> data = gatherDeliveryData(reqVO.getWorkOrderId()); |
| | | String aiResponse = aiChatApi.chat(SYSTEM_PROMPT_DELIVERY, JSONUtil.toJsonPrettyStr(data)); |
| | | log.info("AI 交ä»é¢æµ: workOrderId={}, response={}", reqVO.getWorkOrderId(), aiResponse); |
| | | return parseDeliveryResponse(aiResponse); |
| | | } catch (Exception e) { |
| | | log.warn("AI 交ä»é¢æµå¤±è´¥ï¼workOrderId={}", reqVO.getWorkOrderId(), e); |
| | | MesProAiDeliveryPredictRespVO fallback = new MesProAiDeliveryPredictRespVO(); |
| | | fallback.setReasoning("AI åæææ¶ä¸å¯ç¨ï¼è¯·ç¨åéè¯"); |
| | | return fallback; |
| | | } |
| | | } |
| | | |
| | | private Map<String, Object> gatherDeliveryData(Long workOrderId) { |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(workOrderId); |
| | | if (workOrder == null) throw exception(PRO_WORK_ORDER_NOT_EXISTS); |
| | | |
| | | data.put("å·¥åç¼å·", workOrder.getCode()); |
| | | data.put("å·¥ååç§°", workOrder.getName()); |
| | | data.put("è®¡åæ°é", workOrder.getQuantity()); |
| | | data.put("å·²ç产æ°é", workOrder.getQuantityProduced()); |
| | | data.put("å·¥åç¶æ", workOrder.getStatus()); |
| | | data.put("éæ±æ¥æ", workOrder.getRequestDate() != null ? workOrder.getRequestDate().toString() : "æªè®¾ç½®"); |
| | | data.put("å®ææ¥æ", workOrder.getFinishDate() != null ? workOrder.getFinishDate().toString() : "æªå®æ"); |
| | | fillItemInfo(data, workOrder.getProductId()); |
| | | |
| | | // è¿åº¦è®¡ç® |
| | | BigDecimal progressPercent = BigDecimal.ZERO; |
| | | if (workOrder.getQuantity() != null && workOrder.getQuantity().compareTo(BigDecimal.ZERO) > 0 |
| | | && workOrder.getQuantityProduced() != null) { |
| | | progressPercent = workOrder.getQuantityProduced() |
| | | .divide(workOrder.getQuantity(), 4, RoundingMode.HALF_UP) |
| | | .multiply(BigDecimal.valueOf(100)); |
| | | } |
| | | data.put("ç产è¿åº¦(%)", progressPercent.setScale(1, RoundingMode.HALF_UP)); |
| | | |
| | | // å©ä½å¤©æ° |
| | | int remainingDays = 0; |
| | | if (workOrder.getRequestDate() != null) { |
| | | remainingDays = (int) ChronoUnit.DAYS.between(LocalDate.now(), workOrder.getRequestDate().toLocalDate()); |
| | | } |
| | | data.put("è·ç¦»éæ±æ¥æå©ä½å¤©æ°", Math.max(0, remainingDays)); |
| | | |
| | | // ä»»å¡å®ææ
åµ |
| | | List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(workOrderId); |
| | | if (CollUtil.isNotEmpty(tasks)) { |
| | | List<Map<String, Object>> taskData = new ArrayList<>(); |
| | | long finishedTasks = 0; |
| | | long totalDuration = 0; |
| | | for (MesProTaskDO task : tasks) { |
| | | Map<String, Object> t = new LinkedHashMap<>(); |
| | | t.put("ä»»å¡ç¼å·", task.getCode()); |
| | | t.put("ä»»å¡åç§°", task.getName()); |
| | | t.put("ä»»å¡ç¶æ", task.getStatus()); |
| | | t.put("è®¡åæ°é", task.getQuantity()); |
| | | t.put("å·²ç产æ°é", task.getProducedQuantity()); |
| | | t.put("å®é
æ¶é¿(å°æ¶)", task.getDuration()); |
| | | if (task.getStartTime() != null) t.put("å¼å§æ¶é´", task.getStartTime().toString()); |
| | | if (task.getEndTime() != null) t.put("ç»ææ¶é´", task.getEndTime().toString()); |
| | | if (task.getLastFeedbackTime() != null) |
| | | t.put("æåæ¥å·¥æ¶é´", task.getLastFeedbackTime().toString()); |
| | | taskData.add(t); |
| | | if (task.getStatus() != null && task.getStatus() >= 3) finishedTasks++; |
| | | if (task.getDuration() != null) totalDuration += task.getDuration(); |
| | | } |
| | | data.put("ä»»å¡å表", taskData); |
| | | data.put("已宿任塿°", finishedTasks); |
| | | data.put("æ»ä»»å¡æ°", tasks.size()); |
| | | data.put("å·²å®ææ»æ¶é¿(å°æ¶)", totalDuration); |
| | | } |
| | | |
| | | // MPS å客æ·ä¿¡æ¯ |
| | | MesProMpsDO mps = mpsMapper.selectOne( |
| | | new LambdaQueryWrapper<MesProMpsDO>() |
| | | .eq(MesProMpsDO::getWorkOrderId, workOrderId)); |
| | | if (mps != null) { |
| | | data.put("MPS ç¼å·", mps.getCode()); |
| | | data.put("éå®è®¢åå·", mps.getSaleOrderNo()); |
| | | data.put("MPS éæ±æ¥æ", mps.getRequestDate() != null ? mps.getRequestDate().toString() : ""); |
| | | data.put("MPS ä¸åæ¥æ", mps.getIssueDate() != null ? mps.getIssueDate().toString() : ""); |
| | | fillClientInfo(data, mps.getClientId()); |
| | | } |
| | | |
| | | return data; |
| | | } |
| | | |
| | | private MesProAiDeliveryPredictRespVO parseDeliveryResponse(String aiResponse) { |
| | | MesProAiDeliveryPredictRespVO respVO = new MesProAiDeliveryPredictRespVO(); |
| | | try { |
| | | String json = extractJson(aiResponse); |
| | | Map<String, Object> parsed = JSONUtil.toBean(json, Map.class); |
| | | Object onTime = parsed.get("onTime"); |
| | | if (onTime instanceof Boolean b) respVO.setOnTime(b); |
| | | Object confidence = parsed.get("confidence"); |
| | | if (confidence instanceof Number num) respVO.setConfidence(num.doubleValue()); |
| | | respVO.setReasoning((String) parsed.get("reasoning")); |
| | | @SuppressWarnings("unchecked") |
| | | List<String> delayFactors = (List<String>) parsed.get("delayFactors"); |
| | | respVO.setDelayFactors(delayFactors); |
| | | respVO.setProgressPercent(toBigDecimal(parsed.get("progressPercent"))); |
| | | respVO.setRemainingDays(toInteger(parsed.get("remainingDays"))); |
| | | } catch (Exception e) { |
| | | log.warn("è§£æ AI 交ä»é¢æµååºå¤±è´¥: {}", aiResponse, e); |
| | | respVO.setReasoning(aiResponse); |
| | | } |
| | | return respVO; |
| | | } |
| | | |
| | | // ==================== Shared Utilities ==================== |
| | | |
| | | private String extractJson(String aiResponse) { |
| | | int start = aiResponse.indexOf("{"); |
| | | int end = aiResponse.lastIndexOf("}"); |
| | | if (start >= 0 && end > start) { |
| | | return aiResponse.substring(start, end + 1); |
| | | } |
| | | return aiResponse; |
| | | } |
| | | |
| | | private BigDecimal toBigDecimal(Object value) { |
| | | if (value == null) return BigDecimal.ZERO; |
| | | if (value instanceof BigDecimal v) return v; |
| | | if (value instanceof Number n) return BigDecimal.valueOf(n.doubleValue()); |
| | | try { |
| | | return new BigDecimal(value.toString()); |
| | | } catch (Exception e) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | } |
| | | |
| | | private Integer toInteger(Object value) { |
| | | if (value == null) return 0; |
| | | if (value instanceof Integer v) return v; |
| | | if (value instanceof Number n) return n.intValue(); |
| | | try { |
| | | return Integer.valueOf(value.toString()); |
| | | } catch (Exception e) { |
| | | return 0; |
| | | } |
| | | } |
| | | |
| | | private String resolveItemName(Long itemId) { |
| | | if (itemId == null) return ""; |
| | | try { |
| | | MdmItemRespDTO item = mdmItemApi.getItem(itemId).getCheckedData(); |
| | | if (item != null && StrUtil.isNotBlank(item.getName())) return item.getName(); |
| | | } catch (Exception ignored) {} |
| | | return "ç©æ" + itemId; |
| | | } |
| | | |
| | | private String resolveProcessName(Long processId) { |
| | | if (processId == null) return ""; |
| | | try { |
| | | MesProProcessDO process = processService.getProcess(processId); |
| | | if (process != null && StrUtil.isNotBlank(process.getName())) return process.getName(); |
| | | } catch (Exception ignored) {} |
| | | return "å·¥åº" + processId; |
| | | } |
| | | |
| | | private void fillItemInfo(Map<String, Object> data, Long itemId) { |
| | | if (itemId == null) return; |
| | | try { |
| | | MdmItemRespDTO item = mdmItemApi.getItem(itemId).getCheckedData(); |
| | | if (item != null) { |
| | | data.put("ç©æç¼ç ", item.getCode() != null ? item.getCode() : itemId); |
| | | data.put("ç©æåç§°", item.getName() != null ? item.getName() : ""); |
| | | } |
| | | } catch (Exception e) { |
| | | data.put("ç©æID", itemId); |
| | | } |
| | | } |
| | | |
| | | private void fillClientInfo(Map<String, Object> data, Long clientId) { |
| | | if (clientId == null) return; |
| | | try { |
| | | CrmCustomerRespDTO customer = crmCustomerApi.getCustomer(clientId); |
| | | if (customer != null) { |
| | | data.put("客æ·åç§°", customer.getName() != null ? customer.getName() : ""); |
| | | } |
| | | } catch (Exception e) { |
| | | data.put("客æ·ID", clientId); |
| | | } |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ai; |
| | | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ai.vo.MesQcAiSuggestReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ai.vo.MesQcAiSuggestRespVO; |
| | | |
| | | /** |
| | | * MES QC AI è¾
å©å¤å® Service æ¥å£ |
| | | */ |
| | | public interface MesQcAiService { |
| | | |
| | | /** |
| | | * AI è¾
å©å¤å®æ£éªç»æ |
| | | * |
| | | * @param reqVO 请æ±åæ° |
| | | * @return AI å¤å®å»ºè®® |
| | | */ |
| | | MesQcAiSuggestRespVO suggestCheckResult(MesQcAiSuggestReqVO reqVO); |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ai; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.json.JSONUtil; |
| | | import cn.iocoder.yudao.module.ai.api.chat.AiChatApi; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ai.vo.MesQcAiSuggestReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ai.vo.MesQcAiSuggestRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.indicatorresult.MesQcIndicatorResultDetailDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.indicatorresult.MesQcIndicatorResultMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.indicatorresult.MesQcIndicatorResultDetailMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.defectrecord.MesQcDefectRecordMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicator.MesQcIndicatorService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.ipqc.MesQcIpqcService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.rqc.MesQcRqcService; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO; |
| | | import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi; |
| | | import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; |
| | | |
| | | @Service |
| | | @Slf4j |
| | | public class MesQcAiServiceImpl implements MesQcAiService { |
| | | |
| | | private static final String SYSTEM_PROMPT = """ |
| | | ä½ æ¯ä¸ä½èµæ·±çè´¨æ£ä¸å®¶ï¼æ
é¿æ ¹æ®æ£éªæ°æ®å¤å®äº§åæ¯å¦åæ ¼ã |
| | | ä½ éè¦ç»¼ååæä»¥ä¸ä¿¡æ¯ï¼ç»åºä¸ä¸çå¤å®å»ºè®®ï¼ |
| | | |
| | | ## å¤å®è§å |
| | | 1. åæ ¼ï¼ç¼ç 1ï¼ï¼ææææ åå¨éå¼èå´å
ï¼æ è´å½/严éç¼ºé· |
| | | 2. ç¹éï¼ç¼ç 2ï¼ï¼ä¸ªå«ææ 轻微è¶
å·®ï¼å¯è®©æ¥æ¥æ¶ |
| | | 3. ä¸åæ ¼éè´§ï¼ç¼ç 3ï¼ï¼å¤é¡¹ææ è¶
å·®æåå¨ä¸¥é缺é·ï¼ä¸å¯æ¥æ¶ |
| | | 4. ä¸åæ ¼æ¥åºï¼ç¼ç 4ï¼ï¼åå¨è´å½ç¼ºé·æå¤é¡¹ä¸¥éè¶
å·®ï¼æ æ³ä½¿ç¨ |
| | | |
| | | ## è¾åºæ ¼å¼ |
| | | 请æä»¥ä¸JSONæ ¼å¼è¿åï¼ä¸è¦å
å«å
¶ä»å
容ï¼ï¼ |
| | | {"suggestedResult": 1, "reasoning": "å¤å®çç±...", "keyPoints": ["å
³æ³¨ç¹1", "å
³æ³¨ç¹2"]} |
| | | |
| | | suggestedResult åå¼ï¼1åæ ¼ 2ç¹é 3ä¸åæ ¼éè´§ 4ä¸åæ ¼æ¥åº |
| | | reasoningï¼ç®ææ¼è¦çå¤å®çç± |
| | | keyPointsï¼éè¦éç¹å
³æ³¨çææ æç¼ºé·æ¸
å"""; |
| | | |
| | | @Resource |
| | | private MesQcIqcService iqcService; |
| | | @Resource |
| | | private MesQcIpqcService ipqcService; |
| | | @Resource |
| | | private MesQcOqcService oqcService; |
| | | @Resource |
| | | private MesQcRqcService rqcService; |
| | | |
| | | @Resource |
| | | private MesQcIndicatorResultMapper indicatorResultMapper; |
| | | @Resource |
| | | private MesQcIndicatorResultDetailMapper indicatorResultDetailMapper; |
| | | @Resource |
| | | private MesQcDefectRecordMapper defectRecordMapper; |
| | | @Resource |
| | | private MesQcIndicatorService indicatorService; |
| | | |
| | | @Resource |
| | | private MdmItemApi mdmItemApi; |
| | | @Resource |
| | | private SrmSupplierApi srmSupplierApi; |
| | | @Resource |
| | | private CrmCustomerApi crmCustomerApi; |
| | | |
| | | @Resource |
| | | private AiChatApi aiChatApi; |
| | | |
| | | @Override |
| | | public MesQcAiSuggestRespVO suggestCheckResult(MesQcAiSuggestReqVO reqVO) { |
| | | try { |
| | | // 1. æ¶éè´¨æ£æ°æ® |
| | | Map<String, Object> qcData = gatherQcData(reqVO.getQcId(), reqVO.getQcType()); |
| | | |
| | | // 2. æå»º Prompt å¹¶è°ç¨ AI |
| | | String userMessage = JSONUtil.toJsonPrettyStr(qcData); |
| | | String aiResponse = aiChatApi.chat(SYSTEM_PROMPT, userMessage); |
| | | log.info("AI å¤å®ç»æ: qcId={}, qcType={}, response={}", |
| | | reqVO.getQcId(), reqVO.getQcType(), aiResponse); |
| | | |
| | | // 3. è§£æ AI è¿å |
| | | return parseAiResponse(aiResponse); |
| | | } catch (Exception e) { |
| | | log.warn("AI è¾
å©å¤å®å¤±è´¥ï¼qcId={}, qcType={}", reqVO.getQcId(), reqVO.getQcType(), e); |
| | | MesQcAiSuggestRespVO fallback = new MesQcAiSuggestRespVO(); |
| | | fallback.setReasoning("AI å¤å®ææ¶ä¸å¯ç¨ï¼è¯·äººå·¥å¤å®"); |
| | | return fallback; |
| | | } |
| | | } |
| | | |
| | | private Map<String, Object> gatherQcData(Long qcId, Integer qcType) { |
| | | Map<String, Object> data = new LinkedHashMap<>(); |
| | | |
| | | // è´¨æ£åå¤´ä¿¡æ¯ |
| | | if (MesBizTypeConstants.QC_IQC == qcType) { |
| | | data.put("è´¨æ£ç±»å", "æ¥ææ£éª"); |
| | | MesQcIqcDO iqc = iqcService.getIqc(qcId); |
| | | if (iqc == null) throw exception(QC_IQC_NOT_EXISTS); |
| | | data.put("æ£éªåç¼å·", iqc.getCode()); |
| | | data.put("æ¥æ¶æ°é", iqc.getReceivedQuantity()); |
| | | data.put("æ£æµæ°é", iqc.getCheckQuantity()); |
| | | data.put("åæ ¼æ°é", iqc.getQualifiedQuantity()); |
| | | data.put("ä¸åæ ¼æ°é", iqc.getUnqualifiedQuantity()); |
| | | data.put("缺é·ç»è®¡", Map.of( |
| | | "è´å½ç¼ºé·ç", iqc.getCriticalRate() != null ? iqc.getCriticalRate() + "%" : "0%", |
| | | "严é缺é·ç", iqc.getMajorRate() != null ? iqc.getMajorRate() + "%" : "0%", |
| | | "轻微缺é·ç", iqc.getMinorRate() != null ? iqc.getMinorRate() + "%" : "0%", |
| | | "è´å½ç¼ºé·æ°", iqc.getCriticalQuantity() != null ? iqc.getCriticalQuantity() : 0, |
| | | "严éç¼ºé·æ°", iqc.getMajorQuantity() != null ? iqc.getMajorQuantity() : 0, |
| | | "è½»å¾®ç¼ºé·æ°", iqc.getMinorQuantity() != null ? iqc.getMinorQuantity() : 0 |
| | | )); |
| | | fillItemInfo(data, iqc.getItemId()); |
| | | fillVendorInfo(data, iqc.getVendorId()); |
| | | } else if (MesBizTypeConstants.QC_IPQC == qcType) { |
| | | data.put("è´¨æ£ç±»å", "è¿ç¨æ£éª"); |
| | | MesQcIpqcDO ipqc = ipqcService.getIpqc(qcId); |
| | | if (ipqc == null) throw exception(QC_IPQC_NOT_EXISTS); |
| | | data.put("æ£éªåç¼å·", ipqc.getCode()); |
| | | data.put("æ£æµæ°é", ipqc.getCheckQuantity()); |
| | | data.put("åæ ¼æ°é", ipqc.getQualifiedQuantity()); |
| | | data.put("ä¸åæ ¼æ°é", ipqc.getUnqualifiedQuantity()); |
| | | data.put("å·¥åID", ipqc.getWorkOrderId()); |
| | | data.put("å·¥åºID", ipqc.getProcessId()); |
| | | fillItemInfo(data, ipqc.getItemId()); |
| | | } else if (MesBizTypeConstants.QC_OQC == qcType) { |
| | | data.put("è´¨æ£ç±»å", "åºè´§æ£éª"); |
| | | MesQcOqcDO oqc = oqcService.getOqc(qcId); |
| | | if (oqc == null) throw exception(QC_OQC_NOT_EXISTS); |
| | | data.put("æ£éªåç¼å·", oqc.getCode()); |
| | | data.put("åºè´§æ°é", oqc.getOutQuantity()); |
| | | data.put("æ£æµæ°é", oqc.getCheckQuantity()); |
| | | data.put("åæ ¼æ°é", oqc.getQualifiedQuantity()); |
| | | data.put("ä¸åæ ¼æ°é", oqc.getUnqualifiedQuantity()); |
| | | data.put("æå°æ£æµæ°é", oqc.getMinCheckQuantity()); |
| | | data.put("æå¤§ä¸åæ ¼æ°é", oqc.getMaxUnqualifiedQuantity()); |
| | | fillItemInfo(data, oqc.getItemId()); |
| | | fillClientInfo(data, oqc.getClientId()); |
| | | } else if (MesBizTypeConstants.QC_RQC == qcType) { |
| | | data.put("è´¨æ£ç±»å", "éè´§æ£éª"); |
| | | MesQcRqcDO rqc = rqcService.getRqc(qcId); |
| | | if (rqc == null) throw exception(QC_RQC_NOT_EXISTS); |
| | | data.put("æ£éªåç¼å·", rqc.getCode()); |
| | | data.put("æ£æµæ°é", rqc.getCheckQuantity()); |
| | | data.put("åæ ¼æ°é", rqc.getQualifiedQuantity()); |
| | | data.put("ä¸åæ ¼æ°é", rqc.getUnqualifiedQuantity()); |
| | | fillItemInfo(data, rqc.getItemId()); |
| | | } |
| | | |
| | | // ææ æ£éªç»æ |
| | | List<MesQcIndicatorResultDO> results = indicatorResultMapper.selectListByQcIdAndType(qcId, qcType); |
| | | if (CollUtil.isNotEmpty(results)) { |
| | | List<Map<String, Object>> indicatorData = new ArrayList<>(); |
| | | for (MesQcIndicatorResultDO result : results) { |
| | | List<MesQcIndicatorResultDetailDO> details = |
| | | indicatorResultDetailMapper.selectListByResultId(result.getId()); |
| | | if (CollUtil.isEmpty(details)) continue; |
| | | |
| | | Map<String, Object> sample = new LinkedHashMap<>(); |
| | | sample.put("æ ·åç¼å·", result.getCode()); |
| | | sample.put("SN", result.getSn() != null ? result.getSn() : ""); |
| | | List<Map<String, String>> values = new ArrayList<>(); |
| | | for (MesQcIndicatorResultDetailDO detail : details) { |
| | | Map<String, String> item = new LinkedHashMap<>(); |
| | | String indicatorName = indicatorService.getIndicator(detail.getIndicatorId()).getName(); |
| | | item.put("ææ ", indicatorName); |
| | | item.put("宿µå¼", detail.getValue()); |
| | | values.add(item); |
| | | } |
| | | sample.put("æ£æµå¼", values); |
| | | indicatorData.add(sample); |
| | | } |
| | | data.put("æ£éªææ ç»æ", indicatorData); |
| | | } |
| | | |
| | | // 缺é·è®°å½ |
| | | List<MesQcDefectRecordDO> defects = defectRecordMapper.selectListByQcTypeAndQcId(qcType, qcId); |
| | | if (CollUtil.isNotEmpty(defects)) { |
| | | List<Map<String, Object>> defectData = defects.stream().map(d -> { |
| | | Map<String, Object> item = new LinkedHashMap<>(); |
| | | item.put("缺é·åç§°", d.getName()); |
| | | item.put("缺é·ç级", d.getLevel()); |
| | | item.put("ç¼ºé·æ°é", d.getQuantity()); |
| | | item.put("夿³¨", d.getRemark() != null ? d.getRemark() : ""); |
| | | return item; |
| | | }).collect(Collectors.toList()); |
| | | data.put("缺é·è®°å½", defectData); |
| | | } |
| | | |
| | | return data; |
| | | } |
| | | |
| | | private void fillItemInfo(Map<String, Object> data, Long itemId) { |
| | | if (itemId == null) return; |
| | | try { |
| | | MdmItemRespDTO item = mdmItemApi.getItem(itemId).getCheckedData(); |
| | | if (item != null) { |
| | | data.put("ç©æç¼ç ", item.getCode() != null ? item.getCode() : itemId); |
| | | data.put("ç©æåç§°", item.getName() != null ? item.getName() : ""); |
| | | } |
| | | } catch (Exception e) { |
| | | data.put("ç©æID", itemId); |
| | | } |
| | | } |
| | | |
| | | private void fillVendorInfo(Map<String, Object> data, Long vendorId) { |
| | | if (vendorId == null) return; |
| | | try { |
| | | SrmSupplierRespDTO vendor = srmSupplierApi.getSupplier(vendorId).getCheckedData(); |
| | | if (vendor != null) { |
| | | data.put("ä¾åºååç§°", vendor.getName() != null ? vendor.getName() : ""); |
| | | } |
| | | } catch (Exception e) { |
| | | data.put("ä¾åºåID", vendorId); |
| | | } |
| | | } |
| | | |
| | | private void fillClientInfo(Map<String, Object> data, Long clientId) { |
| | | if (clientId == null) return; |
| | | try { |
| | | CrmCustomerRespDTO customer = crmCustomerApi.getCustomer(clientId); |
| | | if (customer != null) { |
| | | data.put("客æ·åç§°", customer.getName() != null ? customer.getName() : ""); |
| | | } |
| | | } catch (Exception e) { |
| | | data.put("客æ·ID", clientId); |
| | | } |
| | | } |
| | | |
| | | private MesQcAiSuggestRespVO parseAiResponse(String aiResponse) { |
| | | MesQcAiSuggestRespVO respVO = new MesQcAiSuggestRespVO(); |
| | | try { |
| | | // æå JSON é¨åï¼AI å¯è½å
è£¹å¨ markdown 代ç åä¸ï¼ |
| | | String json = aiResponse; |
| | | int start = json.indexOf("{"); |
| | | int end = json.lastIndexOf("}"); |
| | | if (start >= 0 && end > start) { |
| | | json = json.substring(start, end + 1); |
| | | } |
| | | Map<String, Object> parsed = JSONUtil.toBean(json, Map.class); |
| | | Object result = parsed.get("suggestedResult"); |
| | | if (result instanceof Number num) { |
| | | respVO.setSuggestedResult(num.intValue()); |
| | | // æ å°ç»æåç§° |
| | | for (MesQcCheckResultEnum e : MesQcCheckResultEnum.values()) { |
| | | if (e.getType().equals(num.intValue())) { |
| | | respVO.setSuggestedResultName(e.getName()); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | respVO.setReasoning((String) parsed.get("reasoning")); |
| | | @SuppressWarnings("unchecked") |
| | | List<String> keyPoints = (List<String>) parsed.get("keyPoints"); |
| | | respVO.setKeyPoints(keyPoints); |
| | | } catch (Exception e) { |
| | | log.warn("è§£æ AI ååºå¤±è´¥ï¼è¿ååå§å
容: {}", aiResponse, e); |
| | | respVO.setReasoning(aiResponse); |
| | | } |
| | | return respVO; |
| | | } |
| | | |
| | | } |
| | |
| | | .eq(NotifyMessageDO::getUserType, userType)); |
| | | } |
| | | |
| | | default int deleteByTemplateCodeAndNoticeId(String templateCode, Long noticeId) { |
| | | return delete(new QueryWrapperX<NotifyMessageDO>() |
| | | .eq("template_code", templateCode) |
| | | .apply("JSON_EXTRACT(template_params, '$.noticeId') = {0}", noticeId)); |
| | | } |
| | | |
| | | } |
| | |
| | | </dependency> |
| | | |
| | | <!-- AI 大模åç¸å
³æ¨¡åãé»è®¤æ³¨éï¼ä¿è¯ç¼è¯é度 --> |
| | | <!-- <dependency>--> |
| | | <!-- <groupId>cn.iocoder.boot</groupId>--> |
| | | <!-- <artifactId>yudao-module-ai</artifactId>--> |
| | | <!-- <version>${revision}</version>--> |
| | | <!-- </dependency>--> |
| | | <dependency> |
| | | <groupId>cn.iocoder.boot</groupId> |
| | | <artifactId>yudao-module-ai</artifactId> |
| | | <version>${revision}</version> |
| | | </dependency> |
| | | |
| | | <!-- IoT ç©èç½ç¸å
³æ¨¡åãé»è®¤æ³¨éï¼ä¿è¯ç¼è¯é度 --> |
| | | <!-- <dependency>--> |
| | |
| | | - org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration # ç¦ç¨ AI 模åç Redisï¼æå¨å建 |
| | | - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # ç¦ç¨ AI 模åç Milvusï¼æå¨å建 |
| | | - org.dromara.trans.config.TransServiceConfig # TODO èè¿ï¼easy-trans å
¼å®¹ Spring Boot 4 åç§»é¤ï¼TransServiceConfig å¼ç¨äºå·²ç§»é¤ç RestTemplateBuilderï¼ |
| | | # TODO èè¿ï¼spring-ai-alibaba 2.0.0-M1.1 ç DashScope èªå¨é
ç½®æä¸å
¼å®¹ Spring AI 2.0.0 |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAgentAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeVideoAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAudioSpeechAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAudioTranscriptionAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeRerankAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeMultimodalEmbeddingAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAsyncToolCallingManagerAutoConfiguration |
| | | # æ°æ®æºé
置项 |
| | | datasource: |
| | | druid: # Druid ãçæ§ãç¸å
³çå
¨å±é
ç½® |
| | |
| | | - org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration # ç¦ç¨ AI 模åç Redisï¼æå¨å建 |
| | | - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # ç¦ç¨ AI 模åç Milvusï¼æå¨å建 |
| | | - org.dromara.trans.config.TransServiceConfig # TODO èè¿ï¼easy-trans å
¼å®¹ Spring Boot 4 åç§»é¤ï¼TransServiceConfig å¼ç¨äºå·²ç§»é¤ç RestTemplateBuilderï¼ |
| | | # TODO èè¿ï¼spring-ai-alibaba 2.0.0-M1.1 ç DashScope èªå¨é
ç½®æä¸å
¼å®¹ Spring AI 2.0.0 |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAgentAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeVideoAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAudioSpeechAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAudioTranscriptionAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeRerankAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeMultimodalEmbeddingAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAsyncToolCallingManagerAutoConfiguration |
| | | # ç¦ç¨æªé
ç½® API Key çæ¨¡åèªå¨é
ç½®ï¼åªä¿çéä¹åé® Embedding çæå¨é
ç½®ï¼ |
| | | - org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration |
| | | - org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration |
| | | - org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration |
| | | - org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechAutoConfiguration |
| | | - org.springframework.ai.model.openai.autoconfigure.OpenAiAudioTranscriptionAutoConfiguration |
| | | - org.springframework.ai.model.openai.autoconfigure.OpenAiModerationAutoConfiguration |
| | | - org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration |
| | | - org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration |
| | | - org.springframework.ai.model.google.genai.autoconfigure.chat.GoogleGenAiChatAutoConfiguration |
| | | - org.springframework.ai.model.google.genai.autoconfigure.embedding.GoogleGenAiEmbeddingConnectionAutoConfiguration |
| | | - org.springframework.ai.model.google.genai.autoconfigure.embedding.GoogleGenAiTextEmbeddingAutoConfiguration |
| | | - org.springframework.ai.model.ollama.autoconfigure.OllamaApiAutoConfiguration |
| | | - org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration |
| | | - org.springframework.ai.model.ollama.autoconfigure.OllamaEmbeddingAutoConfiguration |
| | | - org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration |
| | | # æ°æ®æºé
置项 |
| | | datasource: |
| | | druid: # Druid ãçæ§ãç¸å
³çå
¨å±é
ç½® |
| | |
| | | cn.iocoder.yudao.module.im.dal.mysql: debug |
| | | org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO èè¿ï¼å
ç¦ç¨ï¼Spring Boot 3.X åå¨é¨åé误ç WARN æç¤º |
| | | |
| | | --- #################### AI ç¸å
³é
ç½® #################### |
| | | |
| | | debug: false |
| | | |
| | | --- #################### 微信å
¬ä¼å·ãå°ç¨åºç¸å
³é
ç½® #################### |
| | |
| | | - org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration # ç¦ç¨ AI 模åç Redisï¼æå¨å建 |
| | | - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # ç¦ç¨ AI 模åç Milvusï¼æå¨å建 |
| | | - org.dromara.trans.config.TransServiceConfig # TODO èè¿ï¼easy-trans å
¼å®¹ Spring Boot 4 åç§»é¤ï¼TransServiceConfig å¼ç¨äºå·²ç§»é¤ç RestTemplateBuilderï¼ |
| | | # TODO èè¿ï¼spring-ai-alibaba 2.0.0-M1.1 ç DashScope èªå¨é
ç½®æä¸å
¼å®¹ Spring AI 2.0.0 |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAgentAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeVideoAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAudioSpeechAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAudioTranscriptionAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeRerankAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeMultimodalEmbeddingAutoConfiguration |
| | | - com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAsyncToolCallingManagerAutoConfiguration |
| | | # æ°æ®æºé
置项 |
| | | datasource: |
| | | druid: # Druid ãçæ§ãç¸å
³çå
¨å±é
ç½® |
| | |
| | | database-name: default # Milvus 䏿°æ®åºçåç§° |
| | | collection-name: knowledge_segment # Milvus ä¸éåçåç§°ï¼ç¨äºåå¨åéæ°æ®çéåæ è¯ç¬¦ï¼ææç¸å
³çåéæä½é½ä¼å¨è¿ä¸ªéåä¸è¿è¡ |
| | | client: |
| | | host: 127.0.0.1 |
| | | host: 36.213.180.217 |
| | | port: 19530 |
| | | openai: # OpenAI 宿¹ |
| | | base-url: ${OPENAI_BASE_URL} |
| | | api-key: ${OPENAI_API_KEY} |
| | | anthropic: # Anthropic Claude |
| | | base-url: ${ANTHROPIC_BASE_URL} |
| | | api-key: ${ANTHROPIC_API_KEY} |
| | | ollama: |
| | | base-url: http://127.0.0.1:11434 |
| | | chat: |
| | | model: llama3 |
| | | stabilityai: |
| | | api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx |
| | | dashscope: # éä¹åé® |
| | | api-key: ${DASHSCOPE_API_KEY} |
| | | deepseek: # DeepSeek |
| | | api-key: ${DEEPSEEK_API_KEY} |
| | | chat: |
| | | options: |
| | | model: deepseek-v4-flash |
| | | google: |
| | | genai: # è°·æ Gemini |
| | | api-key: ${GEMINI_API_KEY} |
| | | chat: |
| | | model: gemini-2.5-flash |
| | | model: |
| | | rerank: false # æ¯å¦å¼å¯âéä¹åé®âç Rerank 模åï¼å¡«å dashscope å¼å¯ |
| | | rerank: false # æ¯å¦å¼å¯ Rerank 模å |
| | | mcp: |
| | | server: |
| | | enabled: false |
| | |
| | | |
| | | yudao: |
| | | ai: |
| | | doubao: # åèè±å
|
| | | enable: true |
| | | api-key: ${DOUBAO_API_KEY} |
| | | model: doubao-seed-2-1-turbo-260628 |
| | | hunyuan: # è
¾è®¯æ··å
|
| | | enable: true |
| | | api-key: ${HUNYUAN_API_KEY} |
| | | model: hy3-preview |
| | | siliconflow: # ç¡
åºæµå¨ |
| | | enable: true |
| | | api-key: ${SILICONFLOW_API_KEY} |
| | | model: deepseek-ai/DeepSeek-V4-Pro |
| | | xinghuo: # è®¯é£æç« |
| | | enable: true |
| | | api-key: ${XINGHUO_API_KEY} |
| | | model: x2-flash |
| | | baichuan: # ç¾å·æºè½ |
| | | enable: true |
| | | api-key: ${BAICHUAN_API_KEY} |
| | | model: Baichuan-M3 |
| | | yiyan: # æå¿ä¸è¨ |
| | | enable: false |
| | | api-key: ${YIYAN_API_KEY} |
| | | model: ernie-5.1 |
| | | zhipu: # æºè°± AIï¼GLMï¼ |
| | | enable: false |
| | | api-key: ${ZHIPU_API_KEY} |
| | | model: glm-5.2 |
| | | minimax: # MiniMax |
| | | enable: false |
| | | api-key: ${MINIMAX_API_KEY} |
| | | model: MiniMax-M3 |
| | | moonshot: # æä¹æé¢ï¼KIMIï¼ |
| | | enable: false |
| | | api-key: ${MOONSHOT_API_KEY} |
| | | model: kimi-k2.6 |
| | | stepfun: # é¶è·æè¾° |
| | | enable: false |
| | | api-key: ${STEPFUN_API_KEY} |
| | | model: step-3.7-flash |
| | | grok: # Grok |
| | | enable: false |
| | | api-key: ${GROK_API_KEY} |
| | | model: grok-4.3 |
| | | midjourney: |
| | | enable: true |
| | | # base-url: https://api.holdai.top/mj-relax/mj |
| | | base-url: https://api.holdai.top/mj |
| | | api-key: sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf |
| | | notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify |
| | | suno: |
| | | enable: true |
| | | # base-url: https://suno-55ishh05u-status2xxs-projects.vercel.app |
| | | base-url: http://127.0.0.1:3001 |
| | | web-search: |
| | | enable: true |
| | | api-key: sk-40500e52840f4d24b956d0b1d80d9abe |