flzxsqc-demo 1.4.21 → 1.4.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/agent-backend/pom.xml +15 -0
  2. package/agent-backend/src/main/java/com/example/agent/application/harness/requirements/SkillQuestionRequirementService.java +77 -7
  3. package/agent-backend/src/main/java/com/example/agent/application/service/AgentWaitingFlowService.java +46 -0
  4. package/agent-backend/src/main/java/com/example/agent/application/service/report/MarkdownReportDocxConverter.java +713 -0
  5. package/agent-backend/src/main/java/com/example/agent/application/service/report/ReportDownloadService.java +109 -0
  6. package/agent-backend/src/main/java/com/example/agent/infrastructure/llm/DummyLlmClient.java +55 -12
  7. package/agent-backend/src/main/java/com/example/agent/interfaces/rest/AgentApiExceptionHandler.java +16 -0
  8. package/agent-backend/src/main/java/com/example/agent/interfaces/rest/AgentSessionController.java +20 -0
  9. package/agent-backend/src/main/resources/application.yml +2 -0
  10. package/agent-backend/src/test/java/com/example/agent/application/harness/requirements/SkillQuestionRequirementServiceTest.java +22 -0
  11. package/agent-backend/src/test/java/com/example/agent/application/service/AgentTurnRunnerIntegrationTest.java +2 -1
  12. package/agent-backend/src/test/java/com/example/agent/application/service/AgentWaitingFlowServiceTest.java +69 -0
  13. package/agent-backend/src/test/java/com/example/agent/application/service/report/MarkdownReportDocxConverterTest.java +119 -0
  14. package/agent-backend/src/test/java/com/example/agent/application/service/report/ReportDownloadServiceTest.java +74 -0
  15. package/agent-backend/src/test/java/com/example/agent/infrastructure/llm/DummyLlmClientTest.java +4 -3
  16. package/agent-backend/src/test/java/com/example/agent/interfaces/rest/AgentSessionReportDownloadControllerTest.java +93 -0
  17. package/agent-front-react/doc/analysis-progress-bubble-company-migration.md +394 -0
  18. package/agent-front-react/src/api/client.ts +35 -0
  19. package/agent-front-react/src/pages/AnalysisTaskPage.tsx +35 -7
  20. package/agent-front-react/src/pages/analysis-task/ChatComposer.tsx +13 -0
  21. package/agent-front-react/src/pages/analysis-task/TaskConversationTab.tsx +50 -20
  22. package/agent-front-react/src/pages/analysis-task/TaskReportTab.tsx +12 -0
  23. package/agent-front-react/src/pages/analysis-task/analysisTaskUtils.tsx +0 -22
  24. package/agent-front-react/src/pages/analysis-task/useAnalysisProgress.ts +275 -0
  25. package/agent-front-react/src/styles.css +80 -1
  26. package/package.json +1 -1
@@ -71,6 +71,21 @@
71
71
  <groupId>com.fasterxml.jackson.dataformat</groupId>
72
72
  <artifactId>jackson-dataformat-yaml</artifactId>
73
73
  </dependency>
74
+ <dependency>
75
+ <groupId>org.commonmark</groupId>
76
+ <artifactId>commonmark</artifactId>
77
+ <version>0.21.0</version>
78
+ </dependency>
79
+ <dependency>
80
+ <groupId>org.commonmark</groupId>
81
+ <artifactId>commonmark-ext-gfm-tables</artifactId>
82
+ <version>0.21.0</version>
83
+ </dependency>
84
+ <dependency>
85
+ <groupId>org.apache.poi</groupId>
86
+ <artifactId>poi-ooxml</artifactId>
87
+ <version>5.5.1</version>
88
+ </dependency>
74
89
  <!-- Optional: logback and log4j binding -->
75
90
  <dependency>
76
91
  <groupId>org.springframework.boot</groupId>
@@ -79,12 +79,19 @@ public class SkillQuestionRequirementService {
79
79
  }
80
80
  boolean needsConfirmation = Boolean.TRUE.equals(template.get("confirmation_required")) || hasDefaultRequiringConfirmation(elements, defaultsUsed);
81
81
  if (needsConfirmation) {
82
- return RequirementCheckResult.needsConfirmation(confirmationQuestion(extracted), extracted, defaultsUsed);
82
+ return RequirementCheckResult.needsConfirmation(confirmationQuestion(extracted, defaultsUsed, primarySkill), extracted, defaultsUsed);
83
83
  }
84
84
  extracted.put("confirmed", true);
85
85
  return RequirementCheckResult.ready(extracted);
86
86
  }
87
87
 
88
+ public String analysisSummary(RequirementCheckResult check, SkillDefinition primarySkill) {
89
+ if (check == null) {
90
+ return "已完成原始需求分析,将按原始需求开始后续分析。";
91
+ }
92
+ return analysisSummary(check.requirements(), check.defaultsUsed(), check.missing(), primarySkill);
93
+ }
94
+
88
95
  private Map<String, Object> extractWithLlm(String query, SkillDefinition primarySkill, Map<String, Object> template) {
89
96
  ChatMessage system = new ChatMessage();
90
97
  system.setRole(ChatRole.SYSTEM);
@@ -168,15 +175,78 @@ public class SkillQuestionRequirementService {
168
175
  return false;
169
176
  }
170
177
 
171
- private String confirmationQuestion(Map<String, Object> extracted) {
172
- StringBuilder builder = new StringBuilder("我将按以下要素开始长耗时分析,请确认是否继续:");
178
+ private String confirmationQuestion(Map<String, Object> extracted,
179
+ List<Map<String, Object>> defaultsUsed,
180
+ SkillDefinition primarySkill) {
181
+ return analysisSummary(extracted, defaultsUsed, new ArrayList<String>(), primarySkill)
182
+ + "\n请确认是否继续?";
183
+ }
184
+
185
+ private String analysisSummary(Map<String, Object> extracted,
186
+ List<Map<String, Object>> defaultsUsed,
187
+ List<String> missing,
188
+ SkillDefinition primarySkill) {
189
+ StringBuilder builder = new StringBuilder("已完成原始需求分析,将按以下要素开始后续分析:");
173
190
  appendAnalysisTargets(builder, extracted);
174
- builder.append("\n- 时间范围:").append(stringValue(extracted.get("time_range")));
175
- builder.append("\n- 数据范围:").append(stringValue(extracted.get("data_scope")));
176
- builder.append("\n- 输出形式:").append(stringValue(extracted.get("output_type")));
191
+ builder.append("\n- 时间范围:").append(displayValue(extracted.get("time_range")));
192
+ builder.append("\n- 数据范围:").append(displayValue(extracted.get("data_scope")));
193
+ builder.append("\n- 输出形式:").append(displayValue(extracted.get("output_type")));
194
+ appendDefaultsUsed(builder, defaultsUsed, primarySkill);
195
+ appendMissingElements(builder, missing, primarySkill);
177
196
  return builder.toString();
178
197
  }
179
198
 
199
+ private void appendDefaultsUsed(StringBuilder builder,
200
+ List<Map<String, Object>> defaultsUsed,
201
+ SkillDefinition primarySkill) {
202
+ if (defaultsUsed == null || defaultsUsed.isEmpty()) {
203
+ return;
204
+ }
205
+ List<String> values = new ArrayList<String>();
206
+ for (Map<String, Object> used : defaultsUsed) {
207
+ String key = stringValue(used.get("key"));
208
+ values.add(elementName(primarySkill, key) + "=" + displayValue(used.get("value")));
209
+ }
210
+ builder.append("\n- 已采用默认值:").append(String.join("、", values));
211
+ }
212
+
213
+ private void appendMissingElements(StringBuilder builder,
214
+ List<String> missing,
215
+ SkillDefinition primarySkill) {
216
+ if (missing == null || missing.isEmpty()) {
217
+ return;
218
+ }
219
+ List<String> names = new ArrayList<String>();
220
+ for (String key : missing) {
221
+ names.add(elementName(primarySkill, key));
222
+ }
223
+ builder.append("\n- 未识别项:").append(String.join("、", names));
224
+ builder.append("(将以原始用户需求作为分析目标继续)");
225
+ }
226
+
227
+ private String elementName(SkillDefinition primarySkill, String key) {
228
+ Map<String, Object> template = primarySkill == null ? null : primarySkill.getQuestionRequirements();
229
+ Object rawElements = template == null ? null : template.get("required_elements");
230
+ if (rawElements instanceof List) {
231
+ for (Object item : (List<?>) rawElements) {
232
+ if (!(item instanceof Map)) {
233
+ continue;
234
+ }
235
+ Map<?, ?> element = (Map<?, ?>) item;
236
+ if (key != null && key.equals(stringValue(element.get("key")))) {
237
+ String name = stringValue(element.get("name"));
238
+ return name == null ? key : name;
239
+ }
240
+ }
241
+ }
242
+ return key == null ? "未知要素" : key;
243
+ }
244
+
245
+ private String displayValue(Object value) {
246
+ String text = stringValue(value);
247
+ return text == null || text.trim().isEmpty() ? "未识别" : text;
248
+ }
249
+
180
250
  private void normalizeAnalysisTargets(Map<String, Object> extracted, String query) {
181
251
  List<String> targets = targetList(extracted.get("analysis_targets"));
182
252
  if (targets.isEmpty() && looksLikeMultiTargetRequest(query)) {
@@ -308,7 +378,7 @@ public class SkillQuestionRequirementService {
308
378
  builder.append("\n- 分析目标:").append(targets.get(0));
309
379
  return;
310
380
  }
311
- builder.append("\n- 分析目标:").append(stringValue(extracted.get("analysis_target")));
381
+ builder.append("\n- 分析目标:").append(displayValue(extracted.get("analysis_target")));
312
382
  }
313
383
 
314
384
  private List<String> targetList(Object raw) {
@@ -15,6 +15,7 @@ import com.example.agent.domain.model.SkillDefinition;
15
15
  import com.example.agent.domain.model.chat.ChatMessage;
16
16
  import com.example.agent.domain.model.chat.ChatRole;
17
17
  import lombok.RequiredArgsConstructor;
18
+ import org.springframework.beans.factory.annotation.Value;
18
19
  import org.springframework.stereotype.Service;
19
20
 
20
21
  import java.time.Instant;
@@ -40,6 +41,9 @@ public class AgentWaitingFlowService {
40
41
  private final HarnessWorkflowService harnessWorkflowService;
41
42
  private final HarnessMetricsService harnessMetricsService;
42
43
 
44
+ @Value("${agent.analysis.requirement-feedback.interaction-enabled:false}")
45
+ private boolean requirementFeedbackInteractionEnabled;
46
+
43
47
  public AgentSession askForZeroDataLoopConfirmation(AgentSession session, AgentTurn turn) {
44
48
  CompletionDecision decision = finalAnswerGuardService.zeroDataLoopDecision(session);
45
49
  if (decision == null) {
@@ -175,6 +179,11 @@ public class AgentWaitingFlowService {
175
179
  requirementSkill,
176
180
  session.getGlobalContext()
177
181
  );
182
+ // This kill switch bypasses only the up-front requirement prompt; later safety waits remain unchanged.
183
+ if (!requirementFeedbackInteractionEnabled) {
184
+ autoContinueQuestionRequirements(session, turn, requirementSkill, check);
185
+ return null;
186
+ }
178
187
  if (!check.needsUserInput()) {
179
188
  putAnalysisRequirements(session, check.requirements());
180
189
  harnessWorkflowService.finished(session.getSessionId(), turn.getTurnId(), HarnessPhase.QUESTION_REQUIREMENT_VALIDATION, "SUCCEEDED");
@@ -208,6 +217,39 @@ public class AgentWaitingFlowService {
208
217
  return session;
209
218
  }
210
219
 
220
+ private void autoContinueQuestionRequirements(AgentSession session,
221
+ AgentTurn turn,
222
+ SkillDefinition requirementSkill,
223
+ SkillQuestionRequirementService.RequirementCheckResult check) {
224
+ Map<String, Object> requirements = check.requirements() == null
225
+ ? new LinkedHashMap<String, Object>()
226
+ : new LinkedHashMap<String, Object>(check.requirements());
227
+ if (isBlank(requirements.get("analysis_target"))) {
228
+ requirements.put("analysis_target", session.getQuery());
229
+ }
230
+ requirements.put("confirmed", true);
231
+ putAnalysisRequirements(session, requirements);
232
+
233
+ String summary = skillQuestionRequirementService.analysisSummary(check, requirementSkill);
234
+ ChatMessage assistant = new ChatMessage();
235
+ assistant.setRole(ChatRole.ASSISTANT);
236
+ assistant.setContent(summary);
237
+ session.getTranscript().add(assistant);
238
+ session.setLatestAnswer(summary);
239
+ sessionStore.save(session);
240
+ runtimeEventBus.publish(session.getSessionId(), turn.getTurnId(), RuntimeEventType.ASSISTANT_MESSAGE, payload(
241
+ "content", summary,
242
+ "messageType", "REQUIREMENT_ANALYSIS",
243
+ "terminal", false
244
+ ));
245
+ harnessWorkflowService.finished(
246
+ session.getSessionId(),
247
+ turn.getTurnId(),
248
+ HarnessPhase.QUESTION_REQUIREMENT_VALIDATION,
249
+ "AUTO_CONTINUED"
250
+ );
251
+ }
252
+
211
253
  private SkillDefinition questionRequirementSkill(List<SkillDefinition> skills) {
212
254
  for (SkillDefinition skill : skills) {
213
255
  if (skill == null || skill.getQuestionRequirements() == null) {
@@ -317,6 +359,10 @@ public class AgentWaitingFlowService {
317
359
  session.setGlobalContext(globalContext);
318
360
  }
319
361
 
362
+ private boolean isBlank(Object value) {
363
+ return value == null || String.valueOf(value).trim().isEmpty();
364
+ }
365
+
320
366
  private Map<String, Object> payload(Object... items) {
321
367
  Map<String, Object> payload = new HashMap<String, Object>();
322
368
  for (int i = 0; i + 1 < items.length; i += 2) {