Merge branch 'dev_New_pro' of http://114.132.189.42:9002/r/product-inventory-management-after into dev_New_pro
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # éè´æºè½ä½ä¼ååç«¯åæ´ææ¡£ |
| | | |
| | | ## 1. åæ´èæ¯ |
| | | |
| | | æ¬æ¬¡é对éè´æºè½ä½åäºå¯¹é½ä¼åï¼åèéå®/审æ¹/å¶é æºè½ä½ï¼ï¼ |
| | | |
| | | 1. æå `quickPrompts` å½ä¸ç¨³å®æ§ã |
| | | 2. å¢å¼ºç¸å¯¹æ¶é´è¯å«ï¼ä»å¤©/æ¨å¤©/æ¬å¨/ä¸å¨/æ¬æ/䏿/ä»å¹´/å»å¹´/è¿N天çï¼ã |
| | | 3. å¢å ä¸å¡æå¾æªè¯å«æ¶çç»æåå
åºååºï¼é¿å
ç¼é æ°æ®ã |
| | | 4. è¡¥å
å¾
仿¬¾æ¥è¯¢çæ±æ»å段ï¼ä¾¿äºåç«¯ç´æ¥æ¸²æç»è®¡å¡çã |
| | | |
| | | ## 2. æ¥å£å½±åæ¦è§ |
| | | |
| | | | æ¥å£ | æ¹æ³ | æ¯å¦æ¹è·¯å¾ | æ¯å¦æ¹å
¥å | æ¯å¦æ¹è¿åç»æ | |
| | | | --- | --- | --- | --- | --- | |
| | | | `/purchase-ai/chat` | POST(SSE) | å¦ | å¦ | æ¯ï¼æ°å¢å
åº JSON ç±»åï¼ | |
| | | | `/purchase-ai/analyze-files` | POST(SSE) | å¦ | å¦ | å¦ï¼ä»
å
é¨æç¤ºè¯å¢å¼ºï¼ | |
| | | |
| | | ## 3. æ°å¢å
åºååºï¼éç¹ï¼ |
| | | |
| | | å½ç¨æ·ææ¾å¨é®éè´ä¸å¡ï¼ä½æ¡ä»¶ä¸å
å䏿ªå½ä¸å¯æ§è¡æå¾æ¶ï¼`/purchase-ai/chat` ä¼ç´æ¥è¿åç»æå JSONï¼è䏿¯èªç±ææ¬ï¼ï¼ |
| | | |
| | | ```json |
| | | { |
| | | "success": false, |
| | | "type": "purchase_intent_not_recognized", |
| | | "description": "æªè¯å«å°å¯æ§è¡çéè´æ¥è¯¢æ¡ä»¶ã为ä¿è¯ç»æåç¡®ï¼å½åä¸ä¼æ¨æµæç¼é æ°æ®ï¼è¯·è¡¥å
æç¡®æ¶é´èå´ãä¾åºåãéè´ååå·æç©æååæ¥è¯¢ã", |
| | | "summary": {}, |
| | | "data": { |
| | | "quickPrompts": [ |
| | | "æ¬æéè´é颿åååçç©ææåªäºï¼", |
| | | "åªäºéè´è®¢åè¿æªå
¥åºï¼", |
| | | "æè¿7天ä¾åºåå°è´§å¼å¸¸æåªäºï¼", |
| | | "帮æç»è®¡å¾
仿¬¾éè´åï¼", |
| | | "ååºæ¬æéè´éè´§æ
åµ" |
| | | ] |
| | | }, |
| | | "charts": {} |
| | | } |
| | | ``` |
| | | |
| | | å端å¤çå»ºè®®ï¼ |
| | | |
| | | 1. å½ `type === "purchase_intent_not_recognized"` æ¶ï¼å±ç¤º `description`ã |
| | | 2. 读å `data.quickPrompts` ä½ä¸ºå¿«æ·æé®æé®ï¼å¯ç´æ¥åå¡«è¾å
¥æ¡ï¼ã |
| | | |
| | | ## 4. å¾
仿¬¾è¿åæ°å¢æ±æ»å段 |
| | | |
| | | æ¥å£ç±»åï¼`type = "purchase_pending_payment_list"` |
| | | ä½ç½®ï¼`summary` |
| | | |
| | | æ°å¢åæ®µï¼ |
| | | |
| | | | åæ®µ | ç±»å | 说æ | |
| | | | --- | --- | --- | |
| | | | pendingOrderCount | number | å¾
仿¬¾è®¢åæ° | |
| | | | totalContractAmount | number | å¾
仿¬¾è®¢åååæ»é¢ | |
| | | | totalPaidAmount | number | 已仿¬¾æ»é¢ | |
| | | | totalPendingAmount | number | å¾
仿¬¾æ»é¢ | |
| | | |
| | | 说æï¼åæå段ä»ä¿çï¼å
¼å®¹ï¼ï¼æ¬æ¬¡ä¸ºå¢éåæ®µï¼ä¸ç ´åç°ææ¸²æã |
| | | |
| | | ## 5. æ¶é´å£å¾ä¼å |
| | | |
| | | éè´æºè½ä½ç°å¨ç»ä¸æä¸å½æ¶åºå¨ææ¥ææ¢ç®ç¸å¯¹æ¶é´ï¼æ¯æï¼ |
| | | |
| | | - ä»å¤©ãæ¨å¤© |
| | | - æ¬å¨ãä¸å¨ |
| | | - æ¬æã䏿 |
| | | - ä»å¹´ãå»å¹´ |
| | | - è¿N天/å¨/æ/å¹´ãè¿åå¹´ãè¿å个æ |
| | | |
| | | å端æ éæ¹ä¼ åï¼ä½å±ç¤ºæ¶é´èå´æ¶è¯·ä»¥å端è¿å `summary.startDate/endDate/timeRange` 为åã |
| | | |
| | | ## 6. å端èè°æ£æ¥æ¸
å |
| | | |
| | | 1. `chat` æµå¼ç»ææ¼æ¥åï¼ä¼å
æ JSON è§£æã |
| | | 2. è¦çæ°ç±»å `purchase_intent_not_recognized` ç UI å¤çã |
| | | 3. å¾
仿¬¾é¡µé¢è¯»åå¹¶å±ç¤º `summary.totalPendingAmount` çæ°å¢å段ã |
| | | 4. éªè¯ä»¥ä¸å¿«æ·é®é¢å¯ç¨³å®è¿åç»æåç»æï¼ |
| | | - æ¬æéè´é颿åååçç©ææåªäºï¼ |
| | | - åªäºéè´è®¢åè¿æªå
¥åºï¼ |
| | | - æè¿7天ä¾åºåå°è´§å¼å¸¸æåªäºï¼ |
| | | - 帮æç»è®¡å¾
仿¬¾éè´åï¼ |
| | | - ååºæ¬æéè´éè´§æ
åµ |
| | |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.time.DayOfWeek; |
| | | import java.time.LocalDate; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.Locale; |
| | | import java.util.regex.Matcher; |
| | | import java.util.regex.Pattern; |
| | | |
| | |
| | | public class PurchaseIntentExecutor { |
| | | |
| | | private static final Pattern ID_PATTERN = Pattern.compile("\\b\\d{1,12}\\b"); |
| | | private static final Pattern LIMIT_PATTERN = Pattern.compile("(å|æè¿)?(\\d{1,2})æ¡"); |
| | | private static final Pattern DATE_PATTERN = Pattern.compile("\\d{4}-\\d{2}-\\d{2}"); |
| | | private static final Pattern RELATIVE_RANGE_PATTERN = Pattern.compile("(è¿|æè¿)(\\d+)(天|å¨|个æ|æ|å¹´)"); |
| | | private static final Pattern LIMIT_PATTERN = Pattern.compile("(å|æè¿)?\\s*(\\d{1,2})\\s*æ¡"); |
| | | private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})"); |
| | | private static final Pattern RELATIVE_RANGE_PATTERN = Pattern.compile("(è¿|æè¿)\\s*(\\d{1,3})\\s*(天|å¨|个æ|æ|å¹´)"); |
| | | private static final Pattern HALF_RANGE_PATTERN = Pattern.compile("(æè¿|è¿)?å(个)?(æ|å¹´)"); |
| | | private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai"); |
| | | |
| | | private final PurchaseAgentTools purchaseAgentTools; |
| | | |
| | |
| | | return null; |
| | | } |
| | | String text = message.trim(); |
| | | String startDate = extractStartDate(text); |
| | | String endDate = extractEndDate(text); |
| | | Integer limit = extractLimit(text); |
| | | String quickPromptResponse = tryExecuteQuickPrompt(memoryId, text); |
| | | if (StringUtils.hasText(quickPromptResponse)) { |
| | | return quickPromptResponse; |
| | | } |
| | | |
| | | if (containsAny(text, "æè¡", "æå", "åå ", "åäº", "åå") && containsAny(text, "ç©æ", "产å", "åææ", "éè´éé¢", "éé¢")) { |
| | | return purchaseAgentTools.rankPurchaseMaterials( |
| | | memoryId, |
| | | startDate, |
| | | endDate, |
| | | text, |
| | | limit |
| | | ); |
| | | String keyword = extractKeyword(text); |
| | | Integer limit = extractLimit(text); |
| | | DateRange dateRange = extractDateRange(text); |
| | | String startDate = dateRange.startDate(); |
| | | String endDate = dateRange.endDate(); |
| | | |
| | | if (containsAny(text, "æè¡", "æå", "åå ", "åäº", "åå") |
| | | && containsAny(text, "ç©æ", "产å", "åææ", "éè´éé¢", "éé¢")) { |
| | | return purchaseAgentTools.rankPurchaseMaterials(memoryId, startDate, endDate, text, limit); |
| | | } |
| | | if (containsAny(text, "æªå
¥åº", "å¾
å
¥åº", "没æå
¥åº", "è¿æªå
¥åº")) { |
| | | return purchaseAgentTools.listUnstockedPurchaseOrders( |
| | | memoryId, |
| | | startDate, |
| | | endDate, |
| | | extractKeyword(text), |
| | | limit |
| | | ); |
| | | return purchaseAgentTools.listUnstockedPurchaseOrders(memoryId, startDate, endDate, keyword, limit); |
| | | } |
| | | if (containsAny(text, "å°è´§å¼å¸¸", "å°è´§æå¼å¸¸", "å¼å¸¸å°è´§", "å°è´§é®é¢", "ä¾åºåå°è´§å¼å¸¸")) { |
| | | return purchaseAgentTools.listArrivalExceptions( |
| | | memoryId, |
| | | startDate, |
| | | endDate, |
| | | text, |
| | | limit |
| | | ); |
| | | return purchaseAgentTools.listArrivalExceptions(memoryId, startDate, endDate, text, limit); |
| | | } |
| | | if (containsAny(text, "å¾
仿¬¾", "æªä»æ¬¾", "æªä»æ¸
", "å¾
æ¯ä»", "åºä»")) { |
| | | return purchaseAgentTools.listPendingPaymentOrders( |
| | | memoryId, |
| | | startDate, |
| | | endDate, |
| | | extractKeyword(text), |
| | | limit |
| | | ); |
| | | return purchaseAgentTools.listPendingPaymentOrders(memoryId, startDate, endDate, keyword, limit); |
| | | } |
| | | if (containsAny(text, "éè´§", "éæ", "ææ¶")) { |
| | | return purchaseAgentTools.listPurchaseReturns( |
| | | memoryId, |
| | | startDate, |
| | | endDate, |
| | | extractKeyword(text), |
| | | limit |
| | | ); |
| | | return purchaseAgentTools.listPurchaseReturns(memoryId, startDate, endDate, keyword, limit); |
| | | } |
| | | if (isStatsIntent(text)) { |
| | | return purchaseAgentTools.getPurchaseStats( |
| | | memoryId, |
| | | startDate, |
| | | endDate, |
| | | text |
| | | ); |
| | | return purchaseAgentTools.getPurchaseStats(memoryId, startDate, endDate, text); |
| | | } |
| | | if (containsAny(text, "详æ
", "æç»") && extractId(text) != null) { |
| | | return purchaseAgentTools.getPurchaseLedgerDetail(memoryId, extractId(text)); |
| | | |
| | | Long ledgerId = extractId(text); |
| | | if (containsAny(text, "详æ
", "æç»") && ledgerId != null) { |
| | | return purchaseAgentTools.getPurchaseLedgerDetail(memoryId, ledgerId); |
| | | } |
| | | if (containsAny(text, "å°è´¦", "éè´å", "éè´è®¢å", "订å", "åå", "å表", "æ¥è¯¢")) { |
| | | return purchaseAgentTools.listPurchaseLedgers( |
| | | memoryId, |
| | | extractKeyword(text), |
| | | startDate, |
| | | endDate, |
| | | limit |
| | | ); |
| | | return purchaseAgentTools.listPurchaseLedgers(memoryId, keyword, startDate, endDate, limit); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private String tryExecuteQuickPrompt(String memoryId, String text) { |
| | | String normalized = normalizeForMatch(text); |
| | | if ("æ¬æéè´é颿åååçç©ææåªäº".equals(normalized)) { |
| | | return purchaseAgentTools.rankPurchaseMaterials(memoryId, null, null, "æ¬æ", 10); |
| | | } |
| | | if ("åªäºéè´è®¢åè¿æªå
¥åº".equals(normalized)) { |
| | | return purchaseAgentTools.listUnstockedPurchaseOrders(memoryId, null, null, null, 10); |
| | | } |
| | | if ("æè¿7天ä¾åºåå°è´§å¼å¸¸æåªäº".equals(normalized)) { |
| | | return purchaseAgentTools.listArrivalExceptions(memoryId, null, null, "æè¿7天", 10); |
| | | } |
| | | if ("帮æç»è®¡å¾
仿¬¾éè´å".equals(normalized)) { |
| | | return purchaseAgentTools.listPendingPaymentOrders(memoryId, null, null, null, 10); |
| | | } |
| | | if ("ååºæ¬æéè´éè´§æ
åµ".equals(normalized)) { |
| | | return purchaseAgentTools.listPurchaseReturns(memoryId, null, null, null, 10); |
| | | } |
| | | return null; |
| | | } |
| | |
| | | } |
| | | boolean queryWord = containsAny(text, "æ¥è¯¢", "æ¥ç", "çä¸", "çç", "è·å"); |
| | | boolean dataWord = containsAny(text, "æ°æ®", "éé¢", "æ°é", "ååé¢", "仿¬¾é¢", "å票é¢"); |
| | | boolean timeWord = containsAny(text, "ä»å¤©", "æ¬å¨", "æ¬æ", "䏿", "ä»å¹´", "å»å¹´", "è¿åå¹´", "æè¿å个æ", "å个æ") |
| | | || DATE_PATTERN.matcher(text).find(); |
| | | boolean timeWord = containsAny(text, "ä»å¤©", "æ¨å¤©", "æ¬å¨", "ä¸å¨", "æ¬æ", "䏿", "ä»å¹´", "å»å¹´", "è¿åå¹´", "æè¿å个æ", "å个æ") |
| | | || DATE_PATTERN.matcher(text).find() |
| | | || RELATIVE_RANGE_PATTERN.matcher(text).find() |
| | | || HALF_RANGE_PATTERN.matcher(text).find(); |
| | | return queryWord && dataWord && timeWord; |
| | | } |
| | | |
| | |
| | | return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10; |
| | | } |
| | | |
| | | private String extractStartDate(String text) { |
| | | private DateRange extractDateRange(String text) { |
| | | Matcher matcher = DATE_PATTERN.matcher(text); |
| | | if (matcher.find()) { |
| | | return matcher.group(); |
| | | String first = matcher.group(1); |
| | | String second = matcher.find() ? matcher.group(1) : first; |
| | | return buildDateRange(first, second); |
| | | } |
| | | DateRange range = extractRelativeDateRange(text); |
| | | return range == null ? null : range.start().format(DATE_FMT); |
| | | } |
| | | |
| | | private String extractEndDate(String text) { |
| | | Matcher matcher = DATE_PATTERN.matcher(text); |
| | | if (!matcher.find()) { |
| | | DateRange range = extractRelativeDateRange(text); |
| | | return range == null ? null : range.end().format(DATE_FMT); |
| | | LocalDate today = LocalDate.now(CHINA_ZONE_ID); |
| | | if (text.contains("ä»å¤©")) { |
| | | return new DateRange(formatDate(today), formatDate(today)); |
| | | } |
| | | return matcher.find() ? matcher.group() : null; |
| | | } |
| | | |
| | | private String extractKeyword(String text) { |
| | | String cleaned = text |
| | | .replace("æ¥è¯¢", "") |
| | | .replace("æ¥ç", "") |
| | | .replace("éè´", "") |
| | | .replace("éè´å", "") |
| | | .replace("éè´è®¢å", "") |
| | | .replace("订å", "") |
| | | .replace("ä»å¹´", "") |
| | | .replace("æ¬å¹´", "") |
| | | .replace("å»å¹´", "") |
| | | .replace("æ¬æ", "") |
| | | .replace("䏿", "") |
| | | .replace("æ¬å¨", "") |
| | | .replace("ä¸å¨", "") |
| | | .replace("ä»å¤©", "") |
| | | .replace("æ¨å¤©", "") |
| | | .replace("è¿åå¹´", "") |
| | | .replace("æè¿åå¹´", "") |
| | | .replace("æè¿å个æ", "") |
| | | .replace("å个æ", "") |
| | | .replace("å°è´¦", "") |
| | | .replace("å表", "") |
| | | .replace("åªäº", "") |
| | | .replace("ä»ä¹", "") |
| | | .replace("æ
åµ", "") |
| | | .replace("ææ²¡æ", "") |
| | | .replace("æå¥", "") |
| | | .replace("ææ ", "") |
| | | .replace("ååº", "") |
| | | .replace("帮æ", "") |
| | | .replace("ç»æ", "") |
| | | .replace("æ", "") |
| | | .replace("æè¿10æ¡", "") |
| | | .replace("å10æ¡", "") |
| | | .trim(); |
| | | cleaned = DATE_PATTERN.matcher(cleaned).replaceAll(""); |
| | | cleaned = RELATIVE_RANGE_PATTERN.matcher(cleaned).replaceAll(""); |
| | | return cleaned.length() >= 2 ? cleaned : null; |
| | | } |
| | | |
| | | private DateRange extractRelativeDateRange(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | String normalized = text.toLowerCase(Locale.ROOT); |
| | | LocalDate today = LocalDate.now(); |
| | | |
| | | if (normalized.contains("ä»å¤©")) { |
| | | return new DateRange(today, today); |
| | | } |
| | | if (normalized.contains("æ¨å¤©")) { |
| | | if (text.contains("æ¨å¤©")) { |
| | | LocalDate yesterday = today.minusDays(1); |
| | | return new DateRange(yesterday, yesterday); |
| | | return new DateRange(formatDate(yesterday), formatDate(yesterday)); |
| | | } |
| | | if (normalized.contains("ä»å¹´") || normalized.contains("æ¬å¹´")) { |
| | | return new DateRange(today.withDayOfYear(1), today); |
| | | if (text.contains("æ¬å¨")) { |
| | | LocalDate start = today.minusDays(today.getDayOfWeek().getValue() - 1L); |
| | | return new DateRange(formatDate(start), formatDate(today)); |
| | | } |
| | | if (normalized.contains("å»å¹´")) { |
| | | LocalDate first = today.minusYears(1).withDayOfYear(1); |
| | | LocalDate last = first.withDayOfYear(first.lengthOfYear()); |
| | | return new DateRange(first, last); |
| | | if (text.contains("ä¸å¨")) { |
| | | LocalDate thisWeekStart = today.minusDays(today.getDayOfWeek().getValue() - 1L); |
| | | LocalDate start = thisWeekStart.minusWeeks(1); |
| | | LocalDate end = start.plusDays(6); |
| | | return new DateRange(formatDate(start), formatDate(end)); |
| | | } |
| | | if (normalized.contains("æ¬æ")) { |
| | | return new DateRange(today.withDayOfMonth(1), today); |
| | | if (text.contains("æ¬æ")) { |
| | | return new DateRange(formatDate(today.withDayOfMonth(1)), formatDate(today)); |
| | | } |
| | | if (normalized.contains("䏿")) { |
| | | LocalDate first = today.minusMonths(1).withDayOfMonth(1); |
| | | LocalDate last = first.withDayOfMonth(first.lengthOfMonth()); |
| | | return new DateRange(first, last); |
| | | if (text.contains("䏿")) { |
| | | LocalDate start = today.minusMonths(1).withDayOfMonth(1); |
| | | return new DateRange(formatDate(start), formatDate(start.withDayOfMonth(start.lengthOfMonth()))); |
| | | } |
| | | if (normalized.contains("æ¬å¨")) { |
| | | LocalDate weekStart = today.with(DayOfWeek.MONDAY); |
| | | return new DateRange(weekStart, today); |
| | | if (text.contains("ä»å¹´") || text.contains("æ¬å¹´")) { |
| | | return new DateRange(formatDate(today.withDayOfYear(1)), formatDate(today)); |
| | | } |
| | | if (normalized.contains("ä¸å¨")) { |
| | | LocalDate weekStart = today.with(DayOfWeek.MONDAY).minusWeeks(1); |
| | | return new DateRange(weekStart, weekStart.plusDays(6)); |
| | | if (text.contains("å»å¹´")) { |
| | | LocalDate start = today.minusYears(1).withDayOfYear(1); |
| | | LocalDate end = start.withDayOfYear(start.lengthOfYear()); |
| | | return new DateRange(formatDate(start), formatDate(end)); |
| | | } |
| | | if (normalized.contains("è¿åå¹´") || normalized.contains("æè¿åå¹´")) { |
| | | return new DateRange(today.minusMonths(6).plusDays(1), today); |
| | | if (containsAny(text, "è¿åå¹´", "æè¿åå¹´")) { |
| | | return new DateRange(formatDate(today.minusMonths(6).plusDays(1)), formatDate(today)); |
| | | } |
| | | if (normalized.contains("è¿å个æ") || normalized.contains("æè¿å个æ") || normalized.contains("å个æ")) { |
| | | return new DateRange(today.minusDays(14), today); |
| | | if (containsAny(text, "è¿å个æ", "æè¿å个æ", "å个æ")) { |
| | | return new DateRange(formatDate(today.minusDays(14)), formatDate(today)); |
| | | } |
| | | |
| | | Matcher matcher = RELATIVE_RANGE_PATTERN.matcher(normalized); |
| | | if (matcher.find()) { |
| | | int amount = Integer.parseInt(matcher.group(2)); |
| | | String unit = matcher.group(3); |
| | | Matcher relativeMatcher = RELATIVE_RANGE_PATTERN.matcher(text); |
| | | if (relativeMatcher.find()) { |
| | | int amount = Integer.parseInt(relativeMatcher.group(2)); |
| | | String unit = relativeMatcher.group(3); |
| | | LocalDate start = switch (unit) { |
| | | case "天" -> today.minusDays(Math.max(amount - 1L, 0)); |
| | | case "å¨" -> today.minusWeeks(Math.max(amount, 1)).plusDays(1); |
| | |
| | | case "å¹´" -> today.minusYears(Math.max(amount, 1)).plusDays(1); |
| | | default -> today.minusDays(29); |
| | | }; |
| | | return new DateRange(start, today); |
| | | return new DateRange(formatDate(start), formatDate(today)); |
| | | } |
| | | return null; |
| | | |
| | | return new DateRange(null, null); |
| | | } |
| | | |
| | | private record DateRange(LocalDate start, LocalDate end) { |
| | | private DateRange buildDateRange(String start, String end) { |
| | | LocalDate startDate = parseDate(start); |
| | | LocalDate endDate = parseDate(end); |
| | | if (startDate == null || endDate == null) { |
| | | return new DateRange(null, null); |
| | | } |
| | | if (startDate.isAfter(endDate)) { |
| | | LocalDate temp = startDate; |
| | | startDate = endDate; |
| | | endDate = temp; |
| | | } |
| | | return new DateRange(formatDate(startDate), formatDate(endDate)); |
| | | } |
| | | |
| | | private LocalDate parseDate(String text) { |
| | | try { |
| | | return LocalDate.parse(text, DATE_FMT); |
| | | } catch (Exception ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private String formatDate(LocalDate date) { |
| | | return date == null ? null : date.format(DATE_FMT); |
| | | } |
| | | |
| | | private String normalizeForMatch(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return ""; |
| | | } |
| | | return text.replace("ï¼", "") |
| | | .replace(",", "") |
| | | .replace("ã", "") |
| | | .replace(".", "") |
| | | .replace("ï¼", "") |
| | | .replace("!", "") |
| | | .replace("ï¼", "") |
| | | .replace("?", "") |
| | | .replace("ï¼", "") |
| | | .replace(":", "") |
| | | .replace("ï¼", "") |
| | | .replace(";", "") |
| | | .replace(" ", "") |
| | | .trim(); |
| | | } |
| | | |
| | | private String extractKeyword(String text) { |
| | | String cleaned = text |
| | | .replace("æ¥è¯¢", "") |
| | | .replace("æ¥ç", "") |
| | | .replace("çä¸", "") |
| | | .replace("çç", "") |
| | | .replace("请", "") |
| | | .replace("ä¸ä¸", "") |
| | | .replace("éè´", "") |
| | | .replace("éè´å", "") |
| | | .replace("éè´è®¢å", "") |
| | | .replace("订å", "") |
| | | .replace("å°è´¦", "") |
| | | .replace("å表", "") |
| | | .replace("åªäº", "") |
| | | .replace("ååº", "") |
| | | .replace("帮æ", "") |
| | | .replace("ç»è®¡", "") |
| | | .replace("åæ", "") |
| | | .replace("æ¬æ", "") |
| | | .replace("䏿", "") |
| | | .replace("æ¬å¹´", "") |
| | | .replace("ä»å¹´", "") |
| | | .replace("å»å¹´", "") |
| | | .replace("æ¬å¨", "") |
| | | .replace("ä¸å¨", "") |
| | | .replace("ä»å¤©", "") |
| | | .replace("æ¨å¤©", "") |
| | | .replace("è¿30天", "") |
| | | .replace("è¿7天", "") |
| | | .replace("è¿15天", "") |
| | | .replace("è¿60天", "") |
| | | .replace("æè¿30天", "") |
| | | .replace("æè¿7天", "") |
| | | .replace("æè¿15天", "") |
| | | .replace("æè¿60天", "") |
| | | .replace("æè¿10æ¡", "") |
| | | .replace("å10æ¡", "") |
| | | .replace("å20æ¡", "") |
| | | .replace("æè¿20æ¡", "") |
| | | .trim(); |
| | | return cleaned.length() >= 2 ? cleaned : null; |
| | | } |
| | | |
| | | private record DateRange(String startDate, String endDate) { |
| | | } |
| | | } |
| | |
| | | package com.ruoyi.ai.service; |
| | | |
| | | import com.alibaba.fastjson2.JSON; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(directResponse); |
| | | } |
| | | |
| | | if (isPurchaseBusinessIntent(userMessage)) { |
| | | String noGuessResponse = buildNoGuessResponse(); |
| | | mongoChatMemoryStore.appendMessages( |
| | | memoryId, |
| | | List.of(UserMessage.from(userMessage), AiMessage.from(noGuessResponse)) |
| | | ); |
| | | aiChatSessionService.refreshSessionStats(memoryId, loginUser); |
| | | return Flux.just(noGuessResponse); |
| | | } |
| | | |
| | | return purchaseAgent.chat(memoryId, userMessage, currentDateForPrompt()) |
| | |
| | | return LocalDate.now(CHINA_ZONE_ID).format(CURRENT_DATE_FMT); |
| | | } |
| | | |
| | | private boolean isPurchaseBusinessIntent(String message) { |
| | | if (!StringUtils.hasText(message)) { |
| | | return false; |
| | | } |
| | | String text = message.trim(); |
| | | boolean hasDomainWord = containsAny(text, |
| | | "éè´", "éè´å°è´¦", "éè´å", "éè´è®¢å", "ä¾åºå", "ç©æ", "å
¥åº", "å°è´§", "å¾
仿¬¾", |
| | | "仿¬¾", "éè´§", "éæ", "å票", "åå"); |
| | | boolean hasIntentWord = containsAny(text, |
| | | "æ¥è¯¢", "æ¥ç", "ç»è®¡", "åæ", "æè¡", "æå", "ååº", "æåªäº", "æ
åµ", "æç»", "详æ
", "æ¥è¡¨"); |
| | | return hasDomainWord && hasIntentWord; |
| | | } |
| | | |
| | | private boolean containsAny(String text, String... keywords) { |
| | | for (String keyword : keywords) { |
| | | if (text.contains(keyword)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private String buildNoGuessResponse() { |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("success", false); |
| | | result.put("type", "purchase_intent_not_recognized"); |
| | | result.put("description", "æªè¯å«å°å¯æ§è¡çéè´æ¥è¯¢æ¡ä»¶ã为ä¿è¯ç»æåç¡®ï¼å½åä¸ä¼æ¨æµæç¼é æ°æ®ï¼è¯·è¡¥å
æç¡®æ¶é´èå´ãä¾åºåãéè´ååå·æç©æååæ¥è¯¢ã"); |
| | | result.put("summary", Map.of()); |
| | | result.put("data", Map.of( |
| | | "quickPrompts", List.of( |
| | | "æ¬æéè´é颿åååçç©ææåªäºï¼", |
| | | "åªäºéè´è®¢åè¿æªå
¥åºï¼", |
| | | "æè¿7天ä¾åºåå°è´§å¼å¸¸æåªäºï¼", |
| | | "帮æç»è®¡å¾
仿¬¾éè´åï¼", |
| | | "ååºæ¬æéè´éè´§æ
åµ" |
| | | ) |
| | | )); |
| | | result.put("charts", Map.of()); |
| | | return JSON.toJSONString(result); |
| | | } |
| | | |
| | | private String buildPurchaseFileAnalyzePrompt(String message, String fileContent) { |
| | | return """ |
| | | ä½ æ¯éè´ä¸å¡æä»¶åæå©æãè¯·ä¸¥æ ¼æ ¹æ®ç¨æ·ä¸ä¼ çå¤ä¸ªæä»¶åç¨æ·è¦æ±æåéè´ä¸å¡æ°æ®ã |
| | |
| | | public class PurchaseAgentTools { |
| | | |
| | | private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai"); |
| | | private static final int DEFAULT_LIMIT = 10; |
| | | private static final int MAX_LIMIT = 30; |
| | | |
| | |
| | | .sorted(Comparator.comparing(item -> (BigDecimal) item.get("pendingAmount"), Comparator.reverseOrder())) |
| | | .limit(normalizeLimit(limit)) |
| | | .collect(Collectors.toList()); |
| | | |
| | | BigDecimal totalContractAmount = items.stream() |
| | | .map(item -> asBigDecimal(item.get("contractAmount"))) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | BigDecimal totalPaidAmount = items.stream() |
| | | .map(item -> asBigDecimal(item.get("paidAmount"))) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | BigDecimal totalPendingAmount = items.stream() |
| | | .map(item -> asBigDecimal(item.get("pendingAmount"))) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | Map<String, Object> summary = rangeSummary(range, items.size()); |
| | | summary.put("pendingOrderCount", items.size()); |
| | | summary.put("totalContractAmount", totalContractAmount); |
| | | summary.put("totalPaidAmount", totalPaidAmount); |
| | | summary.put("totalPendingAmount", totalPendingAmount); |
| | | |
| | | return jsonResponse(true, "purchase_pending_payment_list", "å·²è¿åå¾
仿¬¾éè´åã", |
| | | rangeSummary(range, items.size()), Map.of("items", items), Map.of()); |
| | | summary, Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | @Tool(name = "æ¥è¯¢éè´éè´§æ
åµ", value = "ææ¶é´èå´æ¥è¯¢éè´éè´§åå表åéè´§éé¢ã") |
| | |
| | | return value == null ? BigDecimal.ZERO : value; |
| | | } |
| | | |
| | | private BigDecimal asBigDecimal(Object value) { |
| | | if (value == null) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | if (value instanceof BigDecimal decimal) { |
| | | return decimal; |
| | | } |
| | | if (value instanceof Number number) { |
| | | return new BigDecimal(String.valueOf(number)); |
| | | } |
| | | return BigDecimal.ZERO; |
| | | } |
| | | |
| | | private List<PaymentRegistration> queryPayments(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<PaymentRegistration> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), PaymentRegistration::getTenantId); |
| | |
| | | } |
| | | |
| | | private DateRange resolveDateRange(String startDate, String endDate, String timeRange) { |
| | | LocalDate today = LocalDate.now(); |
| | | LocalDate today = LocalDate.now(CHINA_ZONE_ID); |
| | | LocalDate start = parseLocalDate(startDate); |
| | | LocalDate end = parseLocalDate(endDate); |
| | | if (start != null || end != null) { |
| | |
| | | return new DateRange(today.minusDays(29), today, "è¿30天"); |
| | | } |
| | | String text = timeRange.trim(); |
| | | if (text.contains("ä»å¤©")) { |
| | | return new DateRange(today, today, "ä»å¤©"); |
| | | } |
| | | if (text.contains("æ¨å¤©")) { |
| | | LocalDate yesterday = today.minusDays(1); |
| | | return new DateRange(yesterday, yesterday, "æ¨å¤©"); |
| | | } |
| | | if (text.contains("æ¬å¨")) { |
| | | LocalDate startOfWeek = today.minusDays(today.getDayOfWeek().getValue() - 1L); |
| | | return new DateRange(startOfWeek, today, "æ¬å¨"); |
| | | } |
| | | if (text.contains("ä¸å¨")) { |
| | | LocalDate thisWeekStart = today.minusDays(today.getDayOfWeek().getValue() - 1L); |
| | | LocalDate startOfLastWeek = thisWeekStart.minusWeeks(1); |
| | | return new DateRange(startOfLastWeek, startOfLastWeek.plusDays(6), "ä¸å¨"); |
| | | } |
| | | if (text.contains("ä»å¹´") || text.contains("æ¬å¹´")) { |
| | | return new DateRange(today.withDayOfYear(1), today, "ä»å¹´"); |
| | | } |
| | |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | return LocalDate.parse(text.trim(), DATE_FMT); |
| | | try { |
| | | return LocalDate.parse(text.trim(), DATE_FMT); |
| | | } catch (Exception ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private Date toDate(LocalDate localDate) { |
| | |
| | | 5. ç¨æ·é®âæè¿7天ä¾åºåå°è´§å¼å¸¸ââå°è´§é®é¢ââå°è´§å¼å¸¸âæ¶ï¼è°ç¨âæ¥è¯¢éè´å°è´§å¼å¸¸âã |
| | | 6. ç¨æ·é®âå¾
仿¬¾éè´åââæªä»æ¬¾éè´åââæªä»æ¸
éè´è®¢åâæ¶ï¼è°ç¨âæ¥è¯¢å¾
仿¬¾éè´åâã |
| | | 7. ç¨æ·é®âæ¬æéè´éè´§æ
åµââéè´éè´§å表ââéæ/ææ¶æ
åµâæ¶ï¼è°ç¨âæ¥è¯¢éè´éè´§æ
åµâã |
| | | 8. ç»æç¨ç®æ´ä¸æåçï¼å
ç»ç»è®ºï¼åç»å
³é®æ°æ®ç¹ã |
| | | 9. ä¸è¦ç¼é éè´æ°æ®ï¼ææç»è®ºå¿
é¡»åºäºå·¥å
·è¿åã |
| | | 10. æ æ³ç´æ¥å¾åºç»è®ºæ¶ï¼æç¡®è¯´æç¼ºå°åªäºå段æç鿡件ã |
| | | 11. ç¨æ·æå°âä»å¹´/æ¬æ/ä»å¤©/æè¿/䏿/å»å¹´âçç¸å¯¹æ¶é´æ¶ï¼å¿
é¡»ä¸¥æ ¼åºäºâå½åæ¥æâæ¢ç®ï¼ç¦æ¢èªè¡å设年份ã |
| | | 8. å·¥å
·è¿å JSON æ¶ï¼ç´æ¥è¾åºåå§ JSON å符串ï¼ä¸è¦é¢å¤å
裹 Markdownï¼ä¹ä¸è¦å¨åå追å è§£éææ¬ã |
| | | 9. åªæå¨æªè°ç¨ JSON å·¥å
·æ¶ï¼æä½¿ç¨ç®æ´ä¸æåçï¼å
ç»ç»è®ºï¼åç»å
³é®æ°æ®ç¹ã |
| | | 10. ä¸è¦ç¼é éè´æ°æ®ï¼ææç»è®ºå¿
é¡»åºäºå·¥å
·è¿åã |
| | | 11. æ æ³ç´æ¥å¾åºç»è®ºæ¶ï¼æç¡®è¯´æç¼ºå°åªäºå段æç鿡件ã |
| | | 12. ç¨æ·æå°âä»å¹´/æ¬æ/ä»å¤©/æè¿/䏿/å»å¹´âçç¸å¯¹æ¶é´æ¶ï¼å¿
é¡»ä¸¥æ ¼åºäºâå½åæ¥æâæ¢ç®ï¼ç¦æ¢èªè¡å设年份ã |