|
|
@@ -58,6 +58,7 @@ import org.springframework.stereotype.Service;
|
|
58
|
58
|
import org.springframework.web.multipart.MultipartFile;
|
|
59
|
59
|
|
|
60
|
60
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
61
|
+import com.ruoyi.llm.domain.Block;
|
|
61
|
62
|
import com.ruoyi.llm.domain.ChapterStreamResponse;
|
|
62
|
63
|
|
|
63
|
64
|
import java.io.*;
|
|
|
@@ -201,7 +202,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
201
|
202
|
|
|
202
|
203
|
// 提取输出文件名(不含路径)
|
|
203
|
204
|
String outputFileNameOnly = new File(outputFilename).getName();
|
|
204
|
|
-
|
|
|
205
|
+
|
|
205
|
206
|
// 生成详细目录结构(包含二三级标题)并写入文件
|
|
206
|
207
|
JSONArray detailedDirectoryTree = generateDetailedDirectory(outputFileNameOnly, agentName, segments);
|
|
207
|
208
|
String detailedDirectory = buildDirectoryText(detailedDirectoryTree);
|
|
|
@@ -324,7 +325,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
324
|
325
|
|
|
325
|
326
|
// 保存文档到本地文件系统
|
|
326
|
327
|
try (InputStream fileInputStream = new FileInputStream(profilePath + "/" + file.getOriginalFilename());
|
|
327
|
|
- XWPFDocument doc = new XWPFDocument(fileInputStream)) {
|
|
|
328
|
+ XWPFDocument doc = new XWPFDocument(fileInputStream)) {
|
|
328
|
329
|
try (FileOutputStream out = new FileOutputStream(profilePath + "/" + file.getOriginalFilename())) {
|
|
329
|
330
|
doc.write(out);
|
|
330
|
331
|
}
|
|
|
@@ -732,14 +733,17 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
732
|
733
|
|
|
733
|
734
|
// 输出格式要求
|
|
734
|
735
|
prompt.append("【输出格式】:\n");
|
|
735
|
|
- prompt.append("请直接输出章节的正文内容(不要包含标题),正文内容为总-分结构,别写成总-分-总结构。");
|
|
|
736
|
+ prompt.append("请直接输出章节的正文内容(不要包含三级标题),正文内容为总-分结构,别写成总-分-总结构。");
|
|
736
|
737
|
prompt.append("【标题编号规范】:\n");
|
|
737
|
|
- prompt.append("如果需要在章节内添加子标题,必须严格遵循层级编号格式(如当前章节为6.1.1,则子标题应为6.1.1.1、6.1.1.2、6.1.1.3等)。\n");
|
|
738
|
|
- // prompt.append("严禁使用以下编号格式:\n");
|
|
739
|
|
- // prompt.append("- 阿拉伯数字加点或顿号(如 1. 2. 3. 或 1、2、3、)\n");
|
|
740
|
|
- // prompt.append("- 带括号的数字(如 1)2)3)或 (1)(2)(3))\n");
|
|
741
|
|
- // prompt.append("- 中文数字(如 一、二、三 或 第一、第二、第三)\n");
|
|
742
|
|
- // prompt.append("- 字母编号(如 A. B. C. 或 a) b) c))\n");
|
|
|
738
|
+ prompt.append("如果需要在章节内添加子标题,必须严格遵循层级编号格式,最多到第六级标题(如6.1,6.1.1,6.1.1.1,6.1.1.1.1,6.1.1.1.1.1,6.1.1.1.1.1.1),且子标题后应添加换行符。\n");
|
|
|
739
|
+ prompt.append("各级标题下,正文文字叙述部份的分点说明序号分为六级,\n");
|
|
|
740
|
+ prompt.append("第一级按阿拉伯数字序号编排,如1,2,3\n");
|
|
|
741
|
+ prompt.append("第二级按带右圆括号的阿拉伯数字序号编排,如1),2),3)\n");
|
|
|
742
|
+ prompt.append("第三级按带圆括号的英文小写字母顺序编排(1),(2),(3)\n");
|
|
|
743
|
+ prompt.append("第四级按英文小写字母顺序编排,如a,b,c\n");
|
|
|
744
|
+ prompt.append("第五级按带右圆括号的英文小写字母顺序编排,如a),b),c)\n");
|
|
|
745
|
+ prompt.append("第六级按带圆括号的英文小写字母顺序编排,如(a),(b),(c)\n");
|
|
|
746
|
+ prompt.append("分点序号前均空两个汉字,无括号后空一个汉字,有括号的紧挨编写内容,所有的括号均为全角符号。\n");
|
|
743
|
747
|
prompt.append("所有子标题必须基于父标题编号逐级递增,保持编号体系的一致性。\n\n");
|
|
744
|
748
|
|
|
745
|
749
|
prompt.append("请开始生成当前章节 ").append(title).append(" 的详细内容:");
|
|
|
@@ -802,8 +806,24 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
802
|
806
|
}
|
|
803
|
807
|
}
|
|
804
|
808
|
|
|
805
|
|
- // 从后往前处理,避免位置偏移
|
|
806
|
|
- for (int i = titlePositions.size() - 1; i >= 0; i--) {
|
|
|
809
|
+ // 按标题在文档中的位置降序排序(与 contents 同步排序),确保从后往前处理避免索引偏移
|
|
|
810
|
+ if (titlePositions.size() > 1) {
|
|
|
811
|
+ for (int i = 0; i < titlePositions.size() - 1; i++) {
|
|
|
812
|
+ for (int j = i + 1; j < titlePositions.size(); j++) {
|
|
|
813
|
+ if (titlePositions.get(i) < titlePositions.get(j)) {
|
|
|
814
|
+ int tmpPos = titlePositions.get(i);
|
|
|
815
|
+ titlePositions.set(i, titlePositions.get(j));
|
|
|
816
|
+ titlePositions.set(j, tmpPos);
|
|
|
817
|
+ String tmpContent = contents.get(i);
|
|
|
818
|
+ contents.set(i, contents.get(j));
|
|
|
819
|
+ contents.set(j, tmpContent);
|
|
|
820
|
+ }
|
|
|
821
|
+ }
|
|
|
822
|
+ }
|
|
|
823
|
+ }
|
|
|
824
|
+
|
|
|
825
|
+ // 统一处理所有匹配到的标题:先删除旧内容,再插入新内容
|
|
|
826
|
+ for (int i = 0; i < titlePositions.size(); i++) {
|
|
807
|
827
|
int titlePos = titlePositions.get(i);
|
|
808
|
828
|
String content = contents.get(i);
|
|
809
|
829
|
|
|
|
@@ -825,15 +845,12 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
825
|
845
|
// 在标题后面插入新内容
|
|
826
|
846
|
int insertPos = titlePos + 1;
|
|
827
|
847
|
generateWordDocument(content, document, insertPos);
|
|
828
|
|
-
|
|
829
|
|
- // 更新进度
|
|
830
|
848
|
}
|
|
831
|
|
-
|
|
832
|
849
|
// 保存文档(此时输入流已关闭)
|
|
833
|
850
|
try (FileOutputStream out = new FileOutputStream(absolutePath)) {
|
|
834
|
851
|
document.write(out);
|
|
835
|
852
|
}
|
|
836
|
|
-
|
|
|
853
|
+
|
|
837
|
854
|
// 标记写入完成
|
|
838
|
855
|
processValue = "文档写入完成:100%";
|
|
839
|
856
|
} finally {
|
|
|
@@ -846,12 +863,13 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
846
|
863
|
/**
|
|
847
|
864
|
* 调用LLM生成目录结构
|
|
848
|
865
|
*/
|
|
849
|
|
- public JSONArray generateDetailedDirectory(String fileName, String agentName, List<TextSegment> segments) throws IOException {
|
|
|
866
|
+ public JSONArray generateDetailedDirectory(String fileName, String agentName, List<TextSegment> segments)
|
|
|
867
|
+ throws IOException {
|
|
850
|
868
|
StringBuilder sb = new StringBuilder("招标文件内容:\n\n");
|
|
851
|
869
|
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
|
|
852
|
870
|
List<Embedding> embeddings = embeddingModel.embedAll(segments).content();
|
|
853
|
871
|
embeddingStore.addAll(embeddings, segments);
|
|
854
|
|
- Embedding queryEmbedding = embeddingModel.embed("工作大纲/工作范围/招标范围/服务范围").content();
|
|
|
872
|
+ Embedding queryEmbedding = embeddingModel.embed("技术文件工作大纲要求").content();
|
|
855
|
873
|
EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
|
|
856
|
874
|
.queryEmbedding(queryEmbedding)
|
|
857
|
875
|
.maxResults(5)
|
|
|
@@ -865,7 +883,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
865
|
883
|
}
|
|
866
|
884
|
// 根据招标文件工作大纲要求生成二级标题
|
|
867
|
885
|
sb.append("请基于上述招标文件中提到的")
|
|
868
|
|
- .append("工作大纲/工作范围/招标范围/服务范围")
|
|
|
886
|
+ .append("技术文件工作大纲要求")
|
|
869
|
887
|
.append(",先列出二级章节标题,严格按以下格式,仅输出标题列表:\n")
|
|
870
|
888
|
.append("6.1 XX\n" +
|
|
871
|
889
|
"6.2 XX\n" +
|
|
|
@@ -1106,25 +1124,27 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1106
|
1124
|
}
|
|
1107
|
1125
|
|
|
1108
|
1126
|
// /**
|
|
1109
|
|
- // * 调用LLM生成回答
|
|
1110
|
|
- // *
|
|
1111
|
|
- // * @return
|
|
1112
|
|
- // */
|
|
|
1127
|
+ // * 调用LLM生成回答
|
|
|
1128
|
+ // *
|
|
|
1129
|
+ // * @return
|
|
|
1130
|
+ // */
|
|
1113
|
1131
|
// public String generateAnswer(String prompt) throws IOException {
|
|
1114
|
|
- // ChatModel chatModel = ChatModel.of("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions")
|
|
1115
|
|
- // .model("qwen3-vl-32b-instruct")
|
|
1116
|
|
- // .apiKey("sk-750a17cc723847f28b31fa1bc17c255c")
|
|
1117
|
|
- // .timeout(Duration.ofSeconds(240))
|
|
1118
|
|
- // .build();
|
|
1119
|
|
-
|
|
1120
|
|
- // List<ChatMessage> messages = new ArrayList<>();
|
|
1121
|
|
- // messages.add(ChatMessage.ofUser(prompt));
|
|
1122
|
|
- // ChatSession chatSession = InMemoryChatSession.builder().messages(messages).build();
|
|
1123
|
|
-
|
|
1124
|
|
- // Prompt prompt1 = Prompt.of(prompt).attrPut("session", chatSession);
|
|
1125
|
|
- // ChatResponse response = chatModel.prompt(prompt1).call();
|
|
1126
|
|
- // String content = response.lastChoice().getMessage().getContent();
|
|
1127
|
|
- // return content;
|
|
|
1132
|
+ // ChatModel chatModel =
|
|
|
1133
|
+ // ChatModel.of("https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions")
|
|
|
1134
|
+ // .model("qwen3-vl-32b-instruct")
|
|
|
1135
|
+ // .apiKey("sk-750a17cc723847f28b31fa1bc17c255c")
|
|
|
1136
|
+ // .timeout(Duration.ofSeconds(240))
|
|
|
1137
|
+ // .build();
|
|
|
1138
|
+
|
|
|
1139
|
+ // List<ChatMessage> messages = new ArrayList<>();
|
|
|
1140
|
+ // messages.add(ChatMessage.ofUser(prompt));
|
|
|
1141
|
+ // ChatSession chatSession =
|
|
|
1142
|
+ // InMemoryChatSession.builder().messages(messages).build();
|
|
|
1143
|
+
|
|
|
1144
|
+ // Prompt prompt1 = Prompt.of(prompt).attrPut("session", chatSession);
|
|
|
1145
|
+ // ChatResponse response = chatModel.prompt(prompt1).call();
|
|
|
1146
|
+ // String content = response.lastChoice().getMessage().getContent();
|
|
|
1147
|
+ // return content;
|
|
1128
|
1148
|
// }
|
|
1129
|
1149
|
|
|
1130
|
1150
|
/**
|
|
|
@@ -1172,8 +1192,9 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1172
|
1192
|
/**
|
|
1173
|
1193
|
* 流式调用LLM生成回答 - 使用Flux(包含usage信息)
|
|
1174
|
1194
|
*
|
|
1175
|
|
- * @param prompt 提示词
|
|
1176
|
|
- * @param tokenUsage 用于收集token用量的数组 [promptTokens, completionTokens, totalTokens]
|
|
|
1195
|
+ * @param prompt 提示词
|
|
|
1196
|
+ * @param tokenUsage 用于收集token用量的数组 [promptTokens, completionTokens,
|
|
|
1197
|
+ * totalTokens]
|
|
1177
|
1198
|
* @return Flux<String> 流式输出(包含内容)
|
|
1178
|
1199
|
*/
|
|
1179
|
1200
|
public Flux<String> generateAnswerFluxWithUsage(String prompt, AtomicLong[] tokenUsage) {
|
|
|
@@ -1186,26 +1207,29 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1186
|
1207
|
ChatSession chatSession = InMemoryChatSession.builder().messages(messages).build();
|
|
1187
|
1208
|
|
|
1188
|
1209
|
Prompt prompt1 = Prompt.of(prompt).attrPut("session", chatSession);
|
|
1189
|
|
-
|
|
|
1210
|
+
|
|
1190
|
1211
|
// 估算prompt的token数量(中文大约1个字符=1个token)
|
|
1191
|
1212
|
if (tokenUsage != null && tokenUsage.length >= 3 && tokenUsage[0].get() == 0) {
|
|
1192
|
1213
|
tokenUsage[0].set((long) prompt.length());
|
|
1193
|
1214
|
}
|
|
1194
|
|
-
|
|
|
1215
|
+
|
|
1195
|
1216
|
return chatModel.prompt(prompt1).stream()
|
|
1196
|
1217
|
.doOnNext(resp -> {
|
|
1197
|
1218
|
if (tokenUsage != null && tokenUsage.length >= 3) {
|
|
1198
|
1219
|
try {
|
|
1199
|
1220
|
// 优先使用vLLM返回的usage信息(在最后一个chunk中)
|
|
1200
|
1221
|
AiUsage usage = resp.getUsage();
|
|
1201
|
|
- if (usage != null) {
|
|
|
1222
|
+ if (usage != null) {
|
|
1202
|
1223
|
Long pt = usage.promptTokens();
|
|
1203
|
1224
|
Long ct = usage.completionTokens();
|
|
1204
|
1225
|
Long tt = usage.totalTokens();
|
|
1205
|
|
-
|
|
1206
|
|
- if (pt > 0) tokenUsage[0].set(pt);
|
|
1207
|
|
- if (ct > 0) tokenUsage[1].set(ct);
|
|
1208
|
|
- if (tt > 0) tokenUsage[2].set(tt);
|
|
|
1226
|
+
|
|
|
1227
|
+ if (pt > 0)
|
|
|
1228
|
+ tokenUsage[0].set(pt);
|
|
|
1229
|
+ if (ct > 0)
|
|
|
1230
|
+ tokenUsage[1].set(ct);
|
|
|
1231
|
+ if (tt > 0)
|
|
|
1232
|
+ tokenUsage[2].set(tt);
|
|
1209
|
1233
|
} else {
|
|
1210
|
1234
|
// 如果没有usage信息(中间chunk),估算completion tokens
|
|
1211
|
1235
|
String content = resp.getContent();
|
|
|
@@ -1348,69 +1372,6 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1348
|
1372
|
}
|
|
1349
|
1373
|
}
|
|
1350
|
1374
|
|
|
1351
|
|
- /**
|
|
1352
|
|
- * 写入章节内容
|
|
1353
|
|
- *
|
|
1354
|
|
- * @return
|
|
1355
|
|
- */
|
|
1356
|
|
- public void writeContent(String content, List<String> titles, String absolutePath) throws IOException {
|
|
1357
|
|
- String[] contentLines = content.split("\n");
|
|
1358
|
|
- Map<String, String> map = new HashMap<>();
|
|
1359
|
|
- File file = new File(absolutePath);
|
|
1360
|
|
- try (FileInputStream fileInputStream = new FileInputStream(file);
|
|
1361
|
|
- XWPFDocument document = new XWPFDocument(fileInputStream)) {
|
|
1362
|
|
- for (int i = 0; i < titles.size(); i++) {
|
|
1363
|
|
- int startIndex = Arrays.asList(contentLines).indexOf(titles.get(i));
|
|
1364
|
|
- StringBuilder text = new StringBuilder();
|
|
1365
|
|
- if (startIndex >= 0) {
|
|
1366
|
|
- if (i < titles.size() - 1) {
|
|
1367
|
|
- int endIndex = Arrays.asList(contentLines).indexOf(titles.get(i + 1));
|
|
1368
|
|
- for (int c = startIndex + 1; c < endIndex; c++) {
|
|
1369
|
|
- text.append(contentLines[c]).append("\n\n");
|
|
1370
|
|
- }
|
|
1371
|
|
- } else {
|
|
1372
|
|
- if (startIndex + 1 < contentLines.length) {
|
|
1373
|
|
- for (int c = startIndex + 1; c < contentLines.length; c++) {
|
|
1374
|
|
- text.append(contentLines[c]).append("\n\n");
|
|
1375
|
|
- }
|
|
1376
|
|
- }
|
|
1377
|
|
- }
|
|
1378
|
|
- } else
|
|
1379
|
|
- text.append(content);
|
|
1380
|
|
- map.put(titles.get(i), text.toString());
|
|
1381
|
|
- }
|
|
1382
|
|
-
|
|
1383
|
|
- List<Integer> positions = new ArrayList<>();
|
|
1384
|
|
- List<String> contents = new ArrayList<>();
|
|
1385
|
|
- List<XWPFParagraph> paragraphs = document.getParagraphs();
|
|
1386
|
|
-
|
|
1387
|
|
- for (int i = 0; i < paragraphs.size(); i++) {
|
|
1388
|
|
- XWPFParagraph paragraph = paragraphs.get(i);
|
|
1389
|
|
- String paragraphText = paragraph.getText().trim();
|
|
1390
|
|
- for (String title : titles) {
|
|
1391
|
|
- if (paragraphText.equals(toParagraphText(title))) {
|
|
1392
|
|
- positions.add(i);
|
|
1393
|
|
- contents.add(map.get(title));
|
|
1394
|
|
- break;
|
|
1395
|
|
- }
|
|
1396
|
|
- }
|
|
1397
|
|
- }
|
|
1398
|
|
-
|
|
1399
|
|
- for (int i = positions.size() - 1; i >= 0; i--) {
|
|
1400
|
|
- int insertPos = positions.get(i) + 1;
|
|
1401
|
|
- XmlCursor xmlCursor = paragraphs.get(insertPos).getCTP().newCursor();
|
|
1402
|
|
- XWPFParagraph contentParagraph = document.insertNewParagraph(xmlCursor);
|
|
1403
|
|
- contentParagraph.setStyle("1");
|
|
1404
|
|
- XWPFRun run = contentParagraph.createRun();
|
|
1405
|
|
- run.setText(contents.get(i));
|
|
1406
|
|
- }
|
|
1407
|
|
-
|
|
1408
|
|
- try (FileOutputStream out = new FileOutputStream(absolutePath)) {
|
|
1409
|
|
- document.write(out);
|
|
1410
|
|
- }
|
|
1411
|
|
- }
|
|
1412
|
|
- }
|
|
1413
|
|
-
|
|
1414
|
1375
|
/**
|
|
1415
|
1376
|
* 将带编号的标题转为 Word 段落文本(自动编号模板下段落 text 不含编号前缀)
|
|
1416
|
1377
|
*/
|
|
|
@@ -1552,7 +1513,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1552
|
1513
|
List<XWPFParagraph> currentPath = new ArrayList<>();
|
|
1553
|
1514
|
|
|
1554
|
1515
|
try (InputStream fileInputStream = new FileInputStream(filename);
|
|
1555
|
|
- XWPFDocument document = new XWPFDocument(fileInputStream)) {
|
|
|
1516
|
+ XWPFDocument document = new XWPFDocument(fileInputStream)) {
|
|
1556
|
1517
|
|
|
1557
|
1518
|
for (XWPFParagraph paragraph : document.getParagraphs()) {
|
|
1558
|
1519
|
String text = paragraph.getText();
|
|
|
@@ -1619,7 +1580,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
1619
|
1580
|
public String extractTitles(String filename) throws IOException {
|
|
1620
|
1581
|
StringBuilder subTitles = new StringBuilder();
|
|
1621
|
1582
|
try (InputStream fileInputStream = new FileInputStream(filename);
|
|
1622
|
|
- XWPFDocument document = new XWPFDocument(fileInputStream)) {
|
|
|
1583
|
+ XWPFDocument document = new XWPFDocument(fileInputStream)) {
|
|
1623
|
1584
|
for (XWPFParagraph paragraph : document.getParagraphs()) {
|
|
1624
|
1585
|
String text = paragraph.getText().trim();
|
|
1625
|
1586
|
int level = getDocxOutlineLevel(paragraph, document);
|
|
|
@@ -2027,94 +1988,27 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2027
|
1988
|
List<String> tableLines = new ArrayList<>();
|
|
2028
|
1989
|
|
|
2029
|
1990
|
// 将内容解析为块(普通段落/表格/标题/列表项等),然后倒序插入,确保顺序正确
|
|
2030
|
|
- class Block {
|
|
2031
|
|
- String type; // para | table | h4 | list1 | list2 | imageText | boldOnly | numberedBold
|
|
2032
|
|
- String text;
|
|
2033
|
|
- List<String> table;
|
|
2034
|
|
- Integer h4Index; // 预计算的四级标题编号
|
|
2035
|
|
- Integer list1Index; // 预计算的一级列表编号 1),2),3)
|
|
2036
|
|
- Integer list2Index; // 预计算的二级列表编号 (1),(2),(3)
|
|
2037
|
|
- String boldText; // 加粗文本部分
|
|
2038
|
|
- String suffixText; // 后缀文本(如冒号及后续内容)
|
|
2039
|
|
- String numberPrefix; // 数字前缀,如 "1. "
|
|
2040
|
|
-
|
|
2041
|
|
- Block(String t, String x) {
|
|
2042
|
|
- type = t;
|
|
2043
|
|
- text = x;
|
|
2044
|
|
- }
|
|
2045
|
|
-
|
|
2046
|
|
- Block(List<String> tbl) {
|
|
2047
|
|
- type = "table";
|
|
2048
|
|
- table = new ArrayList<>(tbl);
|
|
2049
|
|
- }
|
|
2050
|
|
-
|
|
2051
|
|
- Block(String t, String x, Integer h4Idx, Integer l1Idx, Integer l2Idx) {
|
|
2052
|
|
- type = t;
|
|
2053
|
|
- text = x;
|
|
2054
|
|
- h4Index = h4Idx;
|
|
2055
|
|
- list1Index = l1Idx;
|
|
2056
|
|
- list2Index = l2Idx;
|
|
2057
|
|
- }
|
|
2058
|
|
-
|
|
2059
|
|
- Block(String t, String bold, String suffix) {
|
|
2060
|
|
- type = t;
|
|
2061
|
|
- boldText = bold;
|
|
2062
|
|
- suffixText = suffix;
|
|
2063
|
|
- }
|
|
2064
|
|
-
|
|
2065
|
|
- Block(String t, String numPrefix, String bold, String suffix) {
|
|
2066
|
|
- type = t;
|
|
2067
|
|
- numberPrefix = numPrefix;
|
|
2068
|
|
- boldText = bold;
|
|
2069
|
|
- suffixText = suffix;
|
|
2070
|
|
- }
|
|
2071
|
|
- }
|
|
2072
|
1991
|
List<Block> blocks = new ArrayList<>();
|
|
2073
|
1992
|
|
|
2074
|
|
- // 预计算编号(按正序扫描)
|
|
2075
|
|
- int h4Counter = 0;
|
|
2076
|
|
- int list1Counter = 0;
|
|
2077
|
|
- int list2Counter = 0;
|
|
2078
|
|
- String previousLine = ""; // 记录上一行内容
|
|
2079
|
|
- boolean wasListItem = false; // 记录上一行是否是列表项
|
|
2080
|
|
-
|
|
2081
|
1993
|
for (String raw : lines) {
|
|
2082
|
1994
|
String line = raw.trim();
|
|
2083
|
|
- boolean isCurrentListItem = false;
|
|
2084
|
1995
|
if (line.isEmpty()) {
|
|
2085
|
1996
|
blocks.add(new Block("para", "")); // 空段落
|
|
2086
|
|
- // 空行后重置列表编号
|
|
2087
|
|
- if (!wasListItem) {
|
|
2088
|
|
- list1Counter = 0;
|
|
2089
|
|
- list2Counter = 0;
|
|
2090
|
|
- }
|
|
2091
|
|
- previousLine = line;
|
|
2092
|
|
- wasListItem = false;
|
|
2093
|
1997
|
continue;
|
|
2094
|
1998
|
}
|
|
2095
|
1999
|
|
|
2096
|
|
- // 检查上一行是否以中文冒号结尾,如果是则重置列表编号
|
|
2097
|
|
- if (previousLine.endsWith(":") || previousLine.endsWith(":")) {
|
|
2098
|
|
- list1Counter = 0;
|
|
2099
|
|
- list2Counter = 0;
|
|
2100
|
|
- }
|
|
2101
|
|
-
|
|
2102
|
|
- if (line.startsWith("#")) {
|
|
2103
|
|
- // 跳过前三级标题
|
|
2104
|
|
- previousLine = line;
|
|
2105
|
|
- wasListItem = false;
|
|
|
2000
|
+ if (line.matches("^#{4,6}\\s+.*")) {
|
|
|
2001
|
+ // 四、五、六级标题(####/#####/######),去掉编号前缀,由Word自动编号
|
|
|
2002
|
+ int hashCount = 0;
|
|
|
2003
|
+ while (hashCount < line.length() && line.charAt(hashCount) == '#') {
|
|
|
2004
|
+ hashCount++;
|
|
|
2005
|
+ }
|
|
|
2006
|
+ String title = line.substring(hashCount).trim();
|
|
|
2007
|
+ title = title.replaceFirst("^\\d+(\\.\\d+)*\\.?\\s*", "");
|
|
|
2008
|
+ blocks.add(new Block("heading", title, hashCount));
|
|
|
2009
|
+ } else if (line.startsWith("#")) {
|
|
|
2010
|
+ // 跳过前三级标题(#、##、###)
|
|
2106
|
2011
|
continue;
|
|
2107
|
|
- } else if (line.startsWith("####")) {
|
|
2108
|
|
- // 四级标题,编号采用预计算,保证倒序插入后视觉顺序仍递增
|
|
2109
|
|
- String title = line.replace("####", "").trim();
|
|
2110
|
|
- title = title.replaceFirst("^\\d+\\.\\s*", "");
|
|
2111
|
|
- h4Counter++;
|
|
2112
|
|
- // 碰到新小节,重置列表编号
|
|
2113
|
|
- list1Counter = 0;
|
|
2114
|
|
- list2Counter = 0;
|
|
2115
|
|
- blocks.add(new Block("h4", title, h4Counter, null, null));
|
|
2116
|
|
- previousLine = line;
|
|
2117
|
|
- wasListItem = false;
|
|
2118
|
2012
|
} else if (line.startsWith("|")) {
|
|
2119
|
2013
|
// 表格模式开始
|
|
2120
|
2014
|
if (!inTable) {
|
|
|
@@ -2131,102 +2025,9 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2131
|
2025
|
}
|
|
2132
|
2026
|
// 当前行按普通段落处理
|
|
2133
|
2027
|
blocks.add(new Block("para", line));
|
|
2134
|
|
- // 非列表项,重置计数器
|
|
2135
|
|
- if (wasListItem) {
|
|
2136
|
|
- list1Counter = 0;
|
|
2137
|
|
- list2Counter = 0;
|
|
2138
|
|
- }
|
|
2139
|
|
- wasListItem = false;
|
|
2140
|
2028
|
} else if (inTable) {
|
|
2141
|
|
- tableLines.add(line);
|
|
2142
|
|
- } else if (line.startsWith("- **")) {
|
|
2143
|
|
- // 一级加粗列表项,编号为 1),2),3) —— 预计算编号
|
|
2144
|
|
- if (!wasListItem) {
|
|
2145
|
|
- list1Counter = 0;
|
|
2146
|
|
- list2Counter = 0;
|
|
2147
|
|
- }
|
|
2148
|
|
- String listItem = line.replaceFirst("- \\*\\*", "").replace("**", "");
|
|
2149
|
|
- listItem = listItem.replaceFirst("^\\*\\*", "").replace("**:", ":");
|
|
2150
|
|
- list1Counter++;
|
|
2151
|
|
- // 一级列表开始时,重置二级列表编号
|
|
2152
|
|
- list2Counter = 0;
|
|
2153
|
|
- Block b = new Block("list1", listItem, null, list1Counter, null);
|
|
2154
|
|
- blocks.add(b);
|
|
2155
|
|
- isCurrentListItem = true;
|
|
2156
|
|
- } else if (line.matches("^\\s+-\\s+\\*\\*.*")) {
|
|
2157
|
|
- // 二级加粗列表项,编号为 (1),(2),(3) —— 预计算编号
|
|
2158
|
|
- String listItem = line.replaceFirst("^\\s+-\\s+\\*\\*", "").replace("**", "");
|
|
2159
|
|
- listItem = listItem.replaceFirst("^\\*\\*", "").replace("**:", ":");
|
|
2160
|
|
- list2Counter++;
|
|
2161
|
|
- Block b = new Block("list2", listItem, null, null, list2Counter);
|
|
2162
|
|
- blocks.add(b);
|
|
2163
|
|
- isCurrentListItem = true;
|
|
2164
|
|
- } else if (line.matches("^\\*\\s+.*") && line.contains("**")) {
|
|
2165
|
|
- // 列表项格式:* **文本** 或 * **文本**(*后跟一个或多个空格,然后加粗)
|
|
2166
|
|
- if (!wasListItem) {
|
|
2167
|
|
- list1Counter = 0;
|
|
2168
|
|
- list2Counter = 0;
|
|
2169
|
|
- }
|
|
2170
|
|
- // 提取 ** 之间的内容
|
|
2171
|
|
- Pattern starBoldPattern = Pattern.compile("^\\*\\s+\\*\\*([^*]+)\\*\\*(.*)$");
|
|
2172
|
|
- Matcher sbm = starBoldPattern.matcher(line);
|
|
2173
|
|
- if (sbm.matches()) {
|
|
2174
|
|
- String bold = sbm.group(1);
|
|
2175
|
|
- String suf = sbm.group(2);
|
|
2176
|
|
- if (suf == null) suf = "";
|
|
2177
|
|
- list1Counter++;
|
|
2178
|
|
- list2Counter = 0;
|
|
2179
|
|
- Block b = new Block("list1", bold + suf, null, list1Counter, null);
|
|
2180
|
|
- blocks.add(b);
|
|
2181
|
|
- isCurrentListItem = true;
|
|
2182
|
|
- } else {
|
|
2183
|
|
- // 不匹配 * **文本** 格式,按普通列表项处理
|
|
2184
|
|
- String listItem = line.replaceFirst("^\\*\\s+", "");
|
|
2185
|
|
- list1Counter++;
|
|
2186
|
|
- list2Counter = 0;
|
|
2187
|
|
- Block b = new Block("list1", listItem, null, list1Counter, null);
|
|
2188
|
|
- blocks.add(b);
|
|
2189
|
|
- isCurrentListItem = true;
|
|
2190
|
|
- }
|
|
2191
|
|
- } else if (line.matches("^\\*\\s+.*")) {
|
|
2192
|
|
- // 普通列表项,以 "* " 开头(不带加粗)
|
|
2193
|
|
- if (!wasListItem) {
|
|
2194
|
|
- list1Counter = 0;
|
|
2195
|
|
- list2Counter = 0;
|
|
2196
|
|
- }
|
|
2197
|
|
- String listItem = line.replaceFirst("^\\*\\s+", "");
|
|
2198
|
|
- list1Counter++;
|
|
2199
|
|
- list2Counter = 0;
|
|
2200
|
|
- Block b = new Block("list1", listItem, null, list1Counter, null);
|
|
2201
|
|
- blocks.add(b);
|
|
2202
|
|
- isCurrentListItem = true;
|
|
2203
|
|
- } else if (line.matches("^\\s+\\*\\s+.*")) {
|
|
2204
|
|
- // 缩进的普通列表项,以 " * " 开头(二级列表)
|
|
2205
|
|
- String listItem = line.replaceFirst("^\\s+\\*\\s+", "");
|
|
2206
|
|
- list2Counter++;
|
|
2207
|
|
- Block b = new Block("list2", listItem, null, null, list2Counter);
|
|
2208
|
|
- blocks.add(b);
|
|
2209
|
|
- isCurrentListItem = true;
|
|
2210
|
|
- } else if (line.matches("^-\\s+.*") && !line.matches("^-\\s+\\*\\*.*")) {
|
|
2211
|
|
- // 普通列表项,以 "- " 开头(不带加粗)
|
|
2212
|
|
- if (!wasListItem) {
|
|
2213
|
|
- list1Counter = 0;
|
|
2214
|
|
- list2Counter = 0;
|
|
2215
|
|
- }
|
|
2216
|
|
- String listItem = line.replaceFirst("^-\\s+", "");
|
|
2217
|
|
- list1Counter++;
|
|
2218
|
|
- list2Counter = 0;
|
|
2219
|
|
- Block b = new Block("list1", listItem, null, list1Counter, null);
|
|
2220
|
|
- blocks.add(b);
|
|
2221
|
|
- isCurrentListItem = true;
|
|
|
2029
|
+ tableLines.add(line);
|
|
2222
|
2030
|
} else {
|
|
2223
|
|
- // 非列表项,重置计数器
|
|
2224
|
|
- if (wasListItem) {
|
|
2225
|
|
- list1Counter = 0;
|
|
2226
|
|
- list2Counter = 0;
|
|
2227
|
|
- }
|
|
2228
|
|
- wasListItem = false;
|
|
2229
|
|
- // 匹配格式:1. **文本**(数字前缀+加粗)
|
|
2230
|
2031
|
// 匹配格式:1. **文本**(数字前缀+加粗)
|
|
2231
|
2032
|
Pattern numberedBoldPattern = Pattern.compile("^(\\d+\\.\\s+)\\*\\*([^*]+)\\*\\*(.*)$");
|
|
2232
|
2033
|
Matcher nbm = numberedBoldPattern.matcher(line);
|
|
|
@@ -2237,11 +2038,10 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2237
|
2038
|
if (suf == null)
|
|
2238
|
2039
|
suf = "";
|
|
2239
|
2040
|
blocks.add(new Block("numberedBold", numPrefix, bold, suf));
|
|
2240
|
|
- previousLine = line;
|
|
2241
|
2041
|
continue;
|
|
2242
|
2042
|
}
|
|
2243
|
2043
|
// 匹配格式:**文本**
|
|
2244
|
|
- Pattern boldOnlyPattern = Pattern.compile("^\\*\\*([^*]+)\\*\\*(.*)$");
|
|
|
2044
|
+ Pattern boldOnlyPattern = Pattern.compile("\\*\\*([^*]+)\\*\\*(.*)");
|
|
2245
|
2045
|
Matcher boldMatcher = boldOnlyPattern.matcher(line);
|
|
2246
|
2046
|
if (boldMatcher.matches()) {
|
|
2247
|
2047
|
String boldText = boldMatcher.group(1); // "数据校验"
|
|
|
@@ -2249,7 +2049,6 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2249
|
2049
|
if (suffix == null)
|
|
2250
|
2050
|
suffix = "";
|
|
2251
|
2051
|
blocks.add(new Block("boldOnly", boldText, suffix));
|
|
2252
|
|
- previousLine = line;
|
|
2253
|
2052
|
continue;
|
|
2254
|
2053
|
}
|
|
2255
|
2054
|
// 如果不是 boldOnly 格式,继续后续处理
|
|
|
@@ -2264,10 +2063,6 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2264
|
2063
|
blocks.add(new Block("para", line));
|
|
2265
|
2064
|
}
|
|
2266
|
2065
|
}
|
|
2267
|
|
-
|
|
2268
|
|
- // 更新上一行记录
|
|
2269
|
|
- previousLine = line;
|
|
2270
|
|
- wasListItem = isCurrentListItem;
|
|
2271
|
2066
|
}
|
|
2272
|
2067
|
// 收尾:如果文件最后是表格仍未输出
|
|
2273
|
2068
|
if (inTable && !tableLines.isEmpty()) {
|
|
|
@@ -2287,48 +2082,34 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2287
|
2082
|
}
|
|
2288
|
2083
|
XmlCursor cursor = paragraphs.get(insertPos).getCTP().newCursor();
|
|
2289
|
2084
|
|
|
2290
|
|
- switch (b.type) {
|
|
2291
|
|
- case "h4": {
|
|
2292
|
|
- XWPFParagraph p = document.insertNewParagraph(cursor);
|
|
2293
|
|
- XWPFRun run = p.createRun();
|
|
2294
|
|
- run.setText(b.text);
|
|
2295
|
|
- run.setBold(true);
|
|
2296
|
|
- run.setFontSize(14);
|
|
2297
|
|
- break;
|
|
2298
|
|
- }
|
|
2299
|
|
- case "list1": {
|
|
2300
|
|
- XWPFParagraph p = document.insertNewParagraph(cursor);
|
|
2301
|
|
- XWPFRun run = p.createRun();
|
|
2302
|
|
- int num = (b.list1Index != null ? b.list1Index : 1);
|
|
2303
|
|
- run.setText(num + ") " + b.text);
|
|
2304
|
|
- run.setFontSize(12);
|
|
2305
|
|
- break;
|
|
2306
|
|
- }
|
|
2307
|
|
- case "list2": {
|
|
|
2085
|
+ switch (b.getType()) {
|
|
|
2086
|
+ case "heading": {
|
|
2308
|
2087
|
XWPFParagraph p = document.insertNewParagraph(cursor);
|
|
|
2088
|
+ // 使用模板文件的对应等级标题样式,由Word自动编号
|
|
|
2089
|
+ if (b.getHeadingLevel() != null) {
|
|
|
2090
|
+ p.setStyle(String.valueOf(b.getHeadingLevel() + 1));
|
|
|
2091
|
+ }
|
|
2309
|
2092
|
XWPFRun run = p.createRun();
|
|
2310
|
|
- int num = (b.list2Index != null ? b.list2Index : 1);
|
|
2311
|
|
- run.setText(" (" + num + ") " + b.text);
|
|
2312
|
|
- run.setFontSize(12);
|
|
|
2093
|
+ run.setText(b.getText());
|
|
2313
|
2094
|
break;
|
|
2314
|
2095
|
}
|
|
2315
|
2096
|
case "numberedBold": {
|
|
2316
|
2097
|
XWPFParagraph p = document.insertNewParagraph(cursor);
|
|
2317
|
2098
|
// 数字前缀(不加粗)
|
|
2318
|
|
- if (b.numberPrefix != null) {
|
|
|
2099
|
+ if (b.getNumberPrefix() != null) {
|
|
2319
|
2100
|
XWPFRun r1 = p.createRun();
|
|
2320
|
|
- r1.setText(b.numberPrefix);
|
|
|
2101
|
+ r1.setText(b.getNumberPrefix());
|
|
2321
|
2102
|
r1.setFontSize(12);
|
|
2322
|
2103
|
}
|
|
2323
|
2104
|
// 加粗文本
|
|
2324
|
2105
|
XWPFRun r2 = p.createRun();
|
|
2325
|
|
- r2.setText(b.boldText != null ? b.boldText : "");
|
|
|
2106
|
+ r2.setText(b.getBoldText() != null ? b.getBoldText() : "");
|
|
2326
|
2107
|
r2.setFontSize(12);
|
|
2327
|
2108
|
r2.setBold(true);
|
|
2328
|
2109
|
// 后缀(不加粗)
|
|
2329
|
|
- if (b.suffixText != null && !b.suffixText.isEmpty()) {
|
|
|
2110
|
+ if (b.getSuffixText() != null && !b.getSuffixText().isEmpty()) {
|
|
2330
|
2111
|
XWPFRun r3 = p.createRun();
|
|
2331
|
|
- r3.setText(b.suffixText);
|
|
|
2112
|
+ r3.setText(b.getSuffixText());
|
|
2332
|
2113
|
r3.setFontSize(12);
|
|
2333
|
2114
|
}
|
|
2334
|
2115
|
break;
|
|
|
@@ -2339,14 +2120,14 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2339
|
2120
|
|
|
2340
|
2121
|
// 加粗文本部分
|
|
2341
|
2122
|
XWPFRun run1 = p.createRun();
|
|
2342
|
|
- run1.setText(b.boldText);
|
|
|
2123
|
+ run1.setText(b.getBoldText());
|
|
2343
|
2124
|
run1.setFontSize(12);
|
|
2344
|
2125
|
run1.setBold(true);
|
|
2345
|
2126
|
|
|
2346
|
2127
|
// 后缀部分(不加粗,如冒号及后续内容,如果有的话)
|
|
2347
|
|
- if (b.suffixText != null && !b.suffixText.isEmpty()) {
|
|
|
2128
|
+ if (b.getSuffixText() != null && !b.getSuffixText().isEmpty()) {
|
|
2348
|
2129
|
XWPFRun run2 = p.createRun();
|
|
2349
|
|
- run2.setText(b.suffixText);
|
|
|
2130
|
+ run2.setText(b.getSuffixText());
|
|
2350
|
2131
|
run2.setFontSize(12);
|
|
2351
|
2132
|
}
|
|
2352
|
2133
|
break;
|
|
|
@@ -2354,7 +2135,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2354
|
2135
|
case "imageText": {
|
|
2355
|
2136
|
XWPFParagraph p = document.insertNewParagraph(cursor);
|
|
2356
|
2137
|
XWPFRun run = p.createRun();
|
|
2357
|
|
- run.setText(b.text);
|
|
|
2138
|
+ run.setText(b.getText());
|
|
2358
|
2139
|
run.setFontSize(12);
|
|
2359
|
2140
|
run.setItalic(true);
|
|
2360
|
2141
|
run.setColor("808080");
|
|
|
@@ -2364,7 +2145,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2364
|
2145
|
// 解析 markdown 表格并插入在 cursor 位置
|
|
2365
|
2146
|
List<String[]> tableData = new ArrayList<>();
|
|
2366
|
2147
|
Pattern sep = Pattern.compile("^\\|\\s*(-+\\s*\\|\\s*)*-+\\s*\\|?$");
|
|
2367
|
|
- for (String tl : b.table) {
|
|
|
2148
|
+ for (String tl : b.getTable()) {
|
|
2368
|
2149
|
if (sep.matcher(tl).matches())
|
|
2369
|
2150
|
continue;
|
|
2370
|
2151
|
String t = tl.trim();
|
|
|
@@ -2395,6 +2176,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2395
|
2176
|
XWPFTableCell cell = tr.getCell(c);
|
|
2396
|
2177
|
cell.removeParagraph(0);
|
|
2397
|
2178
|
XWPFParagraph cp = cell.addParagraph();
|
|
|
2179
|
+ cp.setIndentationFirstLine(0);
|
|
2398
|
2180
|
String cellText = rowData[c];
|
|
2399
|
2181
|
if (r == 0) {
|
|
2400
|
2182
|
// 表头行整体加粗
|
|
|
@@ -2435,15 +2217,15 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2435
|
2217
|
case "para":
|
|
2436
|
2218
|
default: {
|
|
2437
|
2219
|
XWPFParagraph p = document.insertNewParagraph(cursor);
|
|
2438
|
|
- if (b.text != null && b.text.contains("**")) {
|
|
|
2220
|
+ if (b.getText() != null && b.getText().contains("**")) {
|
|
2439
|
2221
|
// 解析行内 **文本** 加粗
|
|
2440
|
|
- Matcher m = Pattern.compile("\\*\\*([^*]+)\\*\\*").matcher(b.text);
|
|
|
2222
|
+ Matcher m = Pattern.compile("\\*\\*([^*]+)\\*\\*").matcher(b.getText());
|
|
2441
|
2223
|
int lastEnd = 0;
|
|
2442
|
2224
|
while (m.find()) {
|
|
2443
|
2225
|
// 加粗前的普通文本
|
|
2444
|
2226
|
if (m.start() > lastEnd) {
|
|
2445
|
2227
|
XWPFRun run = p.createRun();
|
|
2446
|
|
- run.setText(b.text.substring(lastEnd, m.start()));
|
|
|
2228
|
+ run.setText(b.getText().substring(lastEnd, m.start()));
|
|
2447
|
2229
|
run.setFontSize(12);
|
|
2448
|
2230
|
}
|
|
2449
|
2231
|
// 加粗文本
|
|
|
@@ -2454,15 +2236,15 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2454
|
2236
|
lastEnd = m.end();
|
|
2455
|
2237
|
}
|
|
2456
|
2238
|
// 剩余文本
|
|
2457
|
|
- if (lastEnd < b.text.length()) {
|
|
|
2239
|
+ if (lastEnd < b.getText().length()) {
|
|
2458
|
2240
|
XWPFRun run = p.createRun();
|
|
2459
|
|
- run.setText(b.text.substring(lastEnd));
|
|
|
2241
|
+ run.setText(b.getText().substring(lastEnd));
|
|
2460
|
2242
|
run.setFontSize(12);
|
|
2461
|
2243
|
}
|
|
2462
|
2244
|
} else {
|
|
2463
|
2245
|
XWPFRun run = p.createRun();
|
|
2464
|
|
- if (b.text != null) {
|
|
2465
|
|
- run.setText(b.text);
|
|
|
2246
|
+ if (b.getText() != null) {
|
|
|
2247
|
+ run.setText(b.getText());
|
|
2466
|
2248
|
}
|
|
2467
|
2249
|
run.setFontSize(12);
|
|
2468
|
2250
|
}
|
|
|
@@ -2505,14 +2287,15 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2505
|
2287
|
SseEmitter emitter = new SseEmitter(600000L);
|
|
2506
|
2288
|
|
|
2507
|
2289
|
// 设置回调
|
|
2508
|
|
- emitter.onCompletion(() -> {});
|
|
|
2290
|
+ emitter.onCompletion(() -> {
|
|
|
2291
|
+ });
|
|
2509
|
2292
|
emitter.onTimeout(() -> {
|
|
2510
|
2293
|
emitter.complete();
|
|
2511
|
2294
|
});
|
|
2512
|
2295
|
emitter.onError(e -> System.err.println("SSE连接错误: " + e.getMessage()));
|
|
2513
|
2296
|
|
|
2514
|
2297
|
// 在主线程中预先获取用户ID,避免异步线程中丢失SecurityContext
|
|
2515
|
|
-
|
|
|
2298
|
+
|
|
2516
|
2299
|
Long userId = SecurityUtils.getUserId();
|
|
2517
|
2300
|
|
|
2518
|
2301
|
// 使用独立线程池处理,避免阻塞主线程
|
|
|
@@ -2626,7 +2409,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2626
|
2409
|
try {
|
|
2627
|
2410
|
if (content != null && !content.isEmpty()) {
|
|
2628
|
2411
|
fullContent.append(content);
|
|
2629
|
|
-
|
|
|
2412
|
+
|
|
2630
|
2413
|
// 逐字发送到前端,并带上当前已累计的 token 用量
|
|
2631
|
2414
|
ChapterStreamResponse response = ChapterStreamResponse.content(
|
|
2632
|
2415
|
title, content, false, finalChapterIndex, totalChapters);
|
|
|
@@ -2667,7 +2450,7 @@ public class CmcAgentServiceImpl implements ICmcAgentService {
|
|
2667
|
2450
|
completeResponse.setPromptTokens(tokenUsage[0].get());
|
|
2668
|
2451
|
completeResponse.setCompletionTokens(tokenUsage[1].get());
|
|
2669
|
2452
|
completeResponse.setTotalTokens(tokenUsage[2].get());
|
|
2670
|
|
-
|
|
|
2453
|
+
|
|
2671
|
2454
|
emitter.send(SseEmitter.event()
|
|
2672
|
2455
|
.name("chapter")
|
|
2673
|
2456
|
.data(objectMapper.writeValueAsString(completeResponse)));
|