feat: AI测试输出渲染Markdown/流式响应、Coze/Dify适配器优化

- 新增 MarkdownPreview 组件,支持 AI 测试输出 Markdown 渲染
- Coze 适配器优化:支持流式响应、工作流接口调用、SSE事件处理
- Dify 适配器优化:支持停止接口、流式聊天、SSE事件解析
- web-admin 添加 markdown-it 和 highlight.js 依赖
- AI 配置列表页面优化测试对话框输出显示

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 16:24:07 +08:00
parent d3746fa6c7
commit bdb4fd8c8e
9 changed files with 479 additions and 17 deletions
@@ -115,33 +115,85 @@ public class CozeProviderAdapter implements AiProviderAdapter {
String type = json.getString("type");
String content = json.getString("content");
if (StringUtils.hasText(content) && (type == null || type.contains("answer") || type.contains("delta"))) {
return content;
return normalizeOutputText(content);
}
String output = json.getString("output");
if (StringUtils.hasText(output)) {
return output;
return normalizeOutputText(output);
}
String answer = json.getString("answer");
if (StringUtils.hasText(answer)) {
return answer;
return normalizeOutputText(answer);
}
JSONObject message = json.getJSONObject("message");
if (message != null && StringUtils.hasText(message.getString("content"))) {
return message.getString("content");
return normalizeOutputText(message.getString("content"));
}
JSONObject data = json.getJSONObject("data");
if (data != null) {
String dataOutput = data.getString("output");
if (StringUtils.hasText(dataOutput)) {
return dataOutput;
return normalizeOutputText(dataOutput);
}
String dataAnswer = data.getString("answer");
if (StringUtils.hasText(dataAnswer)) {
return dataAnswer;
return normalizeOutputText(dataAnswer);
}
String dataContent = data.getString("content");
if (StringUtils.hasText(dataContent)) {
return dataContent;
return normalizeOutputText(dataContent);
}
}
return null;
}
private String normalizeOutputText(String value) {
if (!StringUtils.hasText(value)) {
return value;
}
String trimmed = value.trim();
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
return value;
}
try {
JSONObject json = JSON.parseObject(trimmed);
String extracted = firstText(json);
return StringUtils.hasText(extracted) ? normalizeOutputText(extracted) : value;
} catch (Exception ignored) {
return value;
}
}
private String firstText(JSONObject json) {
String direct = firstTextValue(json, "output", "answer", "content", "text", "result");
if (StringUtils.hasText(direct)) {
return direct;
}
JSONObject data = json.getJSONObject("data");
if (data != null) {
String dataText = firstText(data);
if (StringUtils.hasText(dataText)) {
return dataText;
}
}
JSONObject outputs = json.getJSONObject("outputs");
if (outputs != null) {
return firstText(outputs);
}
return null;
}
private String firstTextValue(JSONObject json, String... keys) {
for (String key : keys) {
Object value = json.get(key);
if (value instanceof String text && StringUtils.hasText(text)) {
return text;
}
if (value instanceof JSONObject object) {
String nested = firstText(object);
if (StringUtils.hasText(nested)) {
return nested;
}
}
}
return null;
@@ -103,11 +103,11 @@ public class DifyProviderAdapter implements AiProviderAdapter {
private String extractDifyDelta(JSONObject json) {
String event = json.getString("event");
if ("message".equals(event) || "agent_message".equals(event)) {
return json.getString("answer");
return normalizeOutputText(json.getString("answer"));
}
if ("text_chunk".equals(event)) {
JSONObject data = json.getJSONObject("data");
return data == null ? null : data.getString("text");
return data == null ? null : normalizeOutputText(data.getString("text"));
}
if ("workflow_finished".equals(event)) {
JSONObject data = json.getJSONObject("data");
@@ -117,7 +117,59 @@ public class DifyProviderAdapter implements AiProviderAdapter {
if (text == null) {
text = outputs.get("answer");
}
return text == null ? null : String.valueOf(text);
return text == null ? null : normalizeOutputText(String.valueOf(text));
}
}
return null;
}
private String normalizeOutputText(String value) {
if (!StringUtils.hasText(value)) {
return value;
}
String trimmed = value.trim();
if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
return value;
}
try {
JSONObject json = JSON.parseObject(trimmed);
String extracted = firstText(json);
return StringUtils.hasText(extracted) ? normalizeOutputText(extracted) : value;
} catch (Exception ignored) {
return value;
}
}
private String firstText(JSONObject json) {
String direct = firstTextValue(json, "output", "answer", "content", "text", "result");
if (StringUtils.hasText(direct)) {
return direct;
}
JSONObject data = json.getJSONObject("data");
if (data != null) {
String dataText = firstText(data);
if (StringUtils.hasText(dataText)) {
return dataText;
}
}
JSONObject outputs = json.getJSONObject("outputs");
if (outputs != null) {
return firstText(outputs);
}
return null;
}
private String firstTextValue(JSONObject json, String... keys) {
for (String key : keys) {
Object value = json.get(key);
if (value instanceof String text && StringUtils.hasText(text)) {
return text;
}
if (value instanceof JSONObject object) {
String nested = firstText(object);
if (StringUtils.hasText(nested)) {
return nested;
}
}
}
return null;