From ea1d9f0075f9dc5f00d7e9b26180140856f4ce09 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Sun, 9 Mar 2025 21:43:45 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91AI=EF=BC=9A=E6=96=87=E5=A4=9A=E5=A4=9A?= =?UTF-8?q?=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/core/model/wenduoduo/api/WddApi.java | 449 ++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java new file mode 100644 index 0000000000..b5467002f4 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java @@ -0,0 +1,449 @@ +package cn.iocoder.yudao.framework.ai.core.model.wenduoduo.api; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * 文多多 API + *

+ *

+ * * 对接文多多:PPT 生成 API + * + * @author xiaoxin + */ +@Slf4j +public class WddApi { + + public static final String BASE_URL = "https://docmee.cn"; + + private final WebClient webClient; + + private final Predicate STATUS_PREDICATE = status -> !status.is2xxSuccessful(); + + private final Function>> EXCEPTION_FUNCTION = + reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> { + HttpRequest request = response.request(); + log.error("[wdd-api] 调用失败!请求方式:[{}],请求地址:[{}],请求参数:[{}],响应数据: [{}]", + request.getMethod(), request.getURI(), reqParam, responseBody); + sink.error(new IllegalStateException("[wdd-api] 调用失败!")); + }); + + public WddApi(String baseUrl) { + this.webClient = WebClient.builder() + .baseUrl(baseUrl) + .defaultHeaders((headers) -> headers.setContentType(MediaType.APPLICATION_JSON)) + .build(); + } + + /** + * 创建API令牌 + * + * @param apiKey API密钥 + * @param uid 用户ID + * @param limit 限制 + * @return API令牌 + */ + public String createApiToken(String apiKey, String uid, Integer limit) { + CreateApiTokenRequest request = new CreateApiTokenRequest(uid, limit); + return this.webClient.post() + .uri("/api/user/createApiToken") + .header("Api-Key", apiKey) + .body(Mono.just(request), CreateApiTokenRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("创建apiToken异常," + response.message)); + return; + } + sink.next(response.data.get("token").toString()); + }) + .block(); + } + + /** + * 解析文件数据 + * + * @param apiToken API令牌 + * @param content 内容 + * @param fileUrl 文件URL + * @return 数据URL + */ + public String parseFileData(String apiToken, String content, String fileUrl) { + ParseFileDataRequest request = new ParseFileDataRequest(content, fileUrl); + return this.webClient.post() + .uri("/api/ppt/parseFileData") + .header("token", apiToken) + .body(Mono.just(request), ParseFileDataRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("解析文件或内容异常," + response.message)); + return; + } + sink.next(response.data.get("dataUrl").toString()); + }) + .block(); + } + + /** + * 生成大纲 + * + * @param apiToken API令牌 + * @param subject 主题 + * @param dataUrl 数据URL + * @param prompt 提示词 + * @return 大纲内容 + */ + public String generateOutline(String apiToken, String subject, String dataUrl, String prompt) { + GenerateOutlineRequest request = new GenerateOutlineRequest(subject, dataUrl, prompt); + return this.webClient.post() + .uri("/api/ppt/generateOutline") + .header("token", apiToken) + .body(Mono.just(request), GenerateOutlineRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(String.class) + .block(); + } + + /** + * 生成大纲内容 + * + * @param apiToken API令牌 + * @param outlineMarkdown 大纲Markdown + * @param dataUrl 数据URL + * @param prompt 提示词 + * @return 大纲内容 + */ + public String generateContent(String apiToken, String outlineMarkdown, String dataUrl, String prompt) { + GenerateContentRequest request = new GenerateContentRequest(outlineMarkdown, dataUrl, prompt); + return this.webClient.post() + .uri("/api/ppt/generateContent") + .header("token", apiToken) + .body(Mono.just(request), GenerateContentRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(String.class) + .block(); + } + + /** + * 异步生成大纲内容 + * + * @param apiToken API令牌 + * @param outlineMarkdown 大纲Markdown + * @param dataUrl 数据URL + * @param templateId 模板ID + * @param prompt 提示词 + * @return 大纲内容和PPT ID + */ + public Map asyncGenerateContent(String apiToken, String outlineMarkdown, String dataUrl, String templateId, String prompt) { + AsyncGenerateContentRequest request = new AsyncGenerateContentRequest(outlineMarkdown, dataUrl, templateId, prompt); + return this.webClient.post() + .uri("/api/ppt/generateContent") + .header("token", apiToken) + .body(Mono.just(request), AsyncGenerateContentRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(new ParameterizedTypeReference>() { + }) + .block(); + } + + /** + * 随机获取一个模板ID + * + * @param apiToken API令牌 + * @return 模板ID + */ + public String randomOneTemplateId(String apiToken) { + RandomTemplateRequest request = new RandomTemplateRequest(1, new TemplateFilter(1)); + return this.webClient.post() + .uri("/api/ppt/randomTemplates") + .header("token", apiToken) + .body(Mono.just(request), RandomTemplateRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("获取模板异常," + response.message)); + return; + } + sink.next(((Map) ((Object[]) response.data.get("data"))[0]).get("id").toString()); + }) + .block(); + } + + /** + * 生成PPT + * + * @param apiToken API令牌 + * @param templateId 模板ID + * @param markdown Markdown内容 + * @param pptxProperty PPT属性 + * @return PPT信息 + */ + public Map generatePptx(String apiToken, String templateId, String markdown, boolean pptxProperty) { + GeneratePptxRequest request = new GeneratePptxRequest(templateId, markdown, pptxProperty); + return this.webClient.post() + .uri("/api/ppt/generatePptx") + .header("token", apiToken) + .body(Mono.just(request), GeneratePptxRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .>handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("生成PPT异常," + response.message)); + return; + } + sink.next((Map) response.data.get("pptInfo")); + }) + .block(); + } + + /** + * 下载PPT + * + * @param apiToken API令牌 + * @param id PPT ID + * @return 下载信息 + */ + public Map downloadPptx(String apiToken, String id) { + DownloadPptxRequest request = new DownloadPptxRequest(id); + return this.webClient.post() + .uri("/api/ppt/downloadPptx") + .header("token", apiToken) + .body(Mono.just(request), DownloadPptxRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .>handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("下载PPT异常," + response.message)); + return; + } + sink.next(response.data); + }) + .block(); + } + + /** + * 直接生成PPT + * + * @param apiToken API令牌 + * @param templateId 模板ID + * @param subject 主题 + * @param dataUrl 数据URL + * @param prompt 提示词 + * @param pptxProperty PPT属性 + * @return PPT信息 + */ + public Map directGeneratePptx(String apiToken, String templateId, String subject, String dataUrl, String prompt, boolean pptxProperty) { + DirectGeneratePptxRequest request = new DirectGeneratePptxRequest(false, templateId, subject, dataUrl, prompt, pptxProperty); + return this.webClient.post() + .uri("/api/ppt/directGeneratePptx") + .header("token", apiToken) + .body(Mono.just(request), DirectGeneratePptxRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .>handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("生成PPT异常," + response.message)); + return; + } + sink.next((Map) response.data.get("pptInfo")); + }) + .block(); + } + + /** + * 查询所有PPT列表 + * + * @param apiToken API令牌 + * @param body 请求体 + * @return PPT列表 + */ + public Map listAllPptx(String apiToken, String body) { + return this.webClient.post() + .uri("/api/ppt/listAllPptx") + .header("token", apiToken) + .bodyValue(body) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(body)) + .bodyToMono(ApiResponse.class) + .>handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("查询所有PPT列表异常," + response.message)); + return; + } + sink.next(response.data); + }) + .block(); + } + + /** + * 分页查询PPT模板 + * + * @param apiToken API令牌 + * @param body 请求体 + * @return 模板列表 + */ + public Map getPptTemplates(String apiToken, String body) { + return this.webClient.post() + .uri("/api/ppt/templates") + .header("token", apiToken) + .bodyValue(body) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(body)) + .bodyToMono(ApiResponse.class) + .>handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("分页查询PPT模板异常," + response.message)); + return; + } + sink.next(response.data); + }) + .block(); + } + + /** + * API响应 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record ApiResponse( + Integer code, + String message, + Map data + ) { + } + + /** + * 创建API令牌请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record CreateApiTokenRequest( + String uid, + Integer limit + ) { + } + + /** + * 解析文件数据请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record ParseFileDataRequest( + String content, + String fileUrl + ) { + } + + /** + * 生成大纲请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record GenerateOutlineRequest( + String subject, + String dataUrl, + String prompt + ) { + } + + /** + * 生成大纲内容请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record GenerateContentRequest( + String outlineMarkdown, + String dataUrl, + String prompt + ) { + } + + /** + * 异步生成大纲内容请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record AsyncGenerateContentRequest( + String outlineMarkdown, + String dataUrl, + String templateId, + String prompt, + @JsonProperty("asyncGenPptx") boolean asyncGenPptx + ) { + public AsyncGenerateContentRequest(String outlineMarkdown, String dataUrl, String templateId, String prompt) { + this(outlineMarkdown, dataUrl, templateId, prompt, true); + } + } + + /** + * 随机模板请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record RandomTemplateRequest( + Integer size, + TemplateFilter filters + ) { + } + + /** + * 模板过滤器 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record TemplateFilter( + Integer type + ) { + } + + /** + * 生成PPT请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record GeneratePptxRequest( + String templateId, + @JsonProperty("outlineContentMarkdown") String outlineContentMarkdown, + boolean pptxProperty + ) { + } + + /** + * 下载PPT请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record DownloadPptxRequest( + String id + ) { + } + + /** + * 直接生成PPT请求 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record DirectGeneratePptxRequest( + boolean stream, + String templateId, + String subject, + String dataUrl, + String prompt, + boolean pptxProperty + ) { + } +} \ No newline at end of file From a82abed2b56f4c1bc07e8905b582bb4f3c7634b6 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Sat, 15 Mar 2025 22:38:09 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91AI=EF=BC=9A=E5=AF=B9=E6=8E=A5=E6=96=87?= =?UTF-8?q?=E5=A4=9A=E5=A4=9A=20v2=20=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/core/model/wenduoduo/api/WddApi.java | 510 ++++++++---------- 1 file changed, 217 insertions(+), 293 deletions(-) diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java index b5467002f4..66583f50af 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java @@ -1,17 +1,26 @@ package cn.iocoder.yudao.framework.ai.core.model.wenduoduo.api; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.extern.slf4j.Slf4j; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.time.LocalDateTime; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; @@ -47,26 +56,24 @@ public class WddApi { .build(); } + /** - * 创建API令牌 + * 创建 token * - * @param apiKey API密钥 - * @param uid 用户ID - * @param limit 限制 - * @return API令牌 + * @param request 请求信息 + * @return token */ - public String createApiToken(String apiKey, String uid, Integer limit) { - CreateApiTokenRequest request = new CreateApiTokenRequest(uid, limit); + public String createApiToken(CreateTokenRequest request) { return this.webClient.post() .uri("/api/user/createApiToken") - .header("Api-Key", apiKey) - .body(Mono.just(request), CreateApiTokenRequest.class) + .header("Api-Key", request.apiKey) + .body(Mono.just(request), CreateTokenRequest.class) .retrieve() .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) .bodyToMono(ApiResponse.class) .handle((response, sink) -> { if (response.code != 0) { - sink.error(new IllegalStateException("创建apiToken异常," + response.message)); + sink.error(new IllegalStateException("创建 token 异常," + response.message)); return; } sink.next(response.data.get("token").toString()); @@ -74,259 +81,159 @@ public class WddApi { .block(); } + /** - * 解析文件数据 + * 创建任务 * - * @param apiToken API令牌 - * @param content 内容 - * @param fileUrl 文件URL - * @return 数据URL + * @param type 类型 + * @param content 内容 + * @param files 文件列表 + * @return 任务ID */ - public String parseFileData(String apiToken, String content, String fileUrl) { - ParseFileDataRequest request = new ParseFileDataRequest(content, fileUrl); + public ApiResponse createTask(String token, Integer type, String content, List files) { + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("type", type); + if (content != null) { + formData.add("content", content); + } + if (files != null) { + for (MultipartFile file : files) { + formData.add("file", file.getResource()); + } + } + return this.webClient.post() - .uri("/api/ppt/parseFileData") - .header("token", apiToken) - .body(Mono.just(request), ParseFileDataRequest.class) + .uri("/api/ppt/v2/createTask") + .header("token", token) + .contentType(MediaType.MULTIPART_FORM_DATA) + .body(BodyInserters.fromMultipartData(formData)) .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(formData)) .bodyToMono(ApiResponse.class) - .handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("解析文件或内容异常," + response.message)); - return; - } - sink.next(response.data.get("dataUrl").toString()); - }) .block(); } /** - * 生成大纲 + * 获取生成选项 * - * @param apiToken API令牌 - * @param subject 主题 - * @param dataUrl 数据URL - * @param prompt 提示词 - * @return 大纲内容 + * @param lang 语种 + * @return 生成选项 */ - public String generateOutline(String apiToken, String subject, String dataUrl, String prompt) { - GenerateOutlineRequest request = new GenerateOutlineRequest(subject, dataUrl, prompt); - return this.webClient.post() - .uri("/api/ppt/generateOutline") - .header("token", apiToken) - .body(Mono.just(request), GenerateOutlineRequest.class) + public Map getOptions(String lang) { + String uri = "/api/ppt/v2/options"; + if (lang != null) { + uri += "?lang=" + lang; + } + return this.webClient.get() + .uri(uri) .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(String.class) + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(null)) + .bodyToMono(new ParameterizedTypeReference() { + }) + .>handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("获取生成选项异常," + response.message)); + return; + } + sink.next(response.data); + }) .block(); } /** * 生成大纲内容 * - * @param apiToken API令牌 - * @param outlineMarkdown 大纲Markdown - * @param dataUrl 数据URL - * @param prompt 提示词 - * @return 大纲内容 + * @return 大纲内容流 */ - public String generateContent(String apiToken, String outlineMarkdown, String dataUrl, String prompt) { - GenerateContentRequest request = new GenerateContentRequest(outlineMarkdown, dataUrl, prompt); + public Flux> generateOutlineContent(String token, GenerateOutlineRequest request) { return this.webClient.post() - .uri("/api/ppt/generateContent") - .header("token", apiToken) - .body(Mono.just(request), GenerateContentRequest.class) + .uri("/api/ppt/v2/generateContent") + .header("token", token) + .body(Mono.just(request), GenerateOutlineRequest.class) .retrieve() .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(String.class) - .block(); + .bodyToFlux(new ParameterizedTypeReference<>() { + }); } /** - * 异步生成大纲内容 + * 修改大纲内容 * - * @param apiToken API令牌 - * @param outlineMarkdown 大纲Markdown - * @param dataUrl 数据URL - * @param templateId 模板ID - * @param prompt 提示词 - * @return 大纲内容和PPT ID + * @param id 任务ID + * @param markdown 大纲内容markdown + * @param question 用户修改建议 + * @return 大纲内容流 */ - public Map asyncGenerateContent(String apiToken, String outlineMarkdown, String dataUrl, String templateId, String prompt) { - AsyncGenerateContentRequest request = new AsyncGenerateContentRequest(outlineMarkdown, dataUrl, templateId, prompt); + public Flux> updateOutlineContent(String token, String id, String markdown, String question) { + UpdateOutlineRequest request = new UpdateOutlineRequest(id, markdown, question); return this.webClient.post() - .uri("/api/ppt/generateContent") - .header("token", apiToken) - .body(Mono.just(request), AsyncGenerateContentRequest.class) + .uri("/api/ppt/v2/updateContent") + .header("token", token) + .body(Mono.just(request), UpdateOutlineRequest.class) .retrieve() .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(new ParameterizedTypeReference>() { - }) - .block(); + .bodyToFlux(new ParameterizedTypeReference>() { + }); } - /** - * 随机获取一个模板ID - * - * @param apiToken API令牌 - * @return 模板ID - */ - public String randomOneTemplateId(String apiToken) { - RandomTemplateRequest request = new RandomTemplateRequest(1, new TemplateFilter(1)); - return this.webClient.post() - .uri("/api/ppt/randomTemplates") - .header("token", apiToken) - .body(Mono.just(request), RandomTemplateRequest.class) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(ApiResponse.class) - .handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("获取模板异常," + response.message)); - return; - } - sink.next(((Map) ((Object[]) response.data.get("data"))[0]).get("id").toString()); - }) - .block(); - } - - /** - * 生成PPT - * - * @param apiToken API令牌 - * @param templateId 模板ID - * @param markdown Markdown内容 - * @param pptxProperty PPT属性 - * @return PPT信息 - */ - public Map generatePptx(String apiToken, String templateId, String markdown, boolean pptxProperty) { - GeneratePptxRequest request = new GeneratePptxRequest(templateId, markdown, pptxProperty); - return this.webClient.post() - .uri("/api/ppt/generatePptx") - .header("token", apiToken) - .body(Mono.just(request), GeneratePptxRequest.class) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(ApiResponse.class) - .>handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("生成PPT异常," + response.message)); - return; - } - sink.next((Map) response.data.get("pptInfo")); - }) - .block(); - } - - /** - * 下载PPT - * - * @param apiToken API令牌 - * @param id PPT ID - * @return 下载信息 - */ - public Map downloadPptx(String apiToken, String id) { - DownloadPptxRequest request = new DownloadPptxRequest(id); - return this.webClient.post() - .uri("/api/ppt/downloadPptx") - .header("token", apiToken) - .body(Mono.just(request), DownloadPptxRequest.class) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(ApiResponse.class) - .>handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("下载PPT异常," + response.message)); - return; - } - sink.next(response.data); - }) - .block(); - } - - /** - * 直接生成PPT - * - * @param apiToken API令牌 - * @param templateId 模板ID - * @param subject 主题 - * @param dataUrl 数据URL - * @param prompt 提示词 - * @param pptxProperty PPT属性 - * @return PPT信息 - */ - public Map directGeneratePptx(String apiToken, String templateId, String subject, String dataUrl, String prompt, boolean pptxProperty) { - DirectGeneratePptxRequest request = new DirectGeneratePptxRequest(false, templateId, subject, dataUrl, prompt, pptxProperty); - return this.webClient.post() - .uri("/api/ppt/directGeneratePptx") - .header("token", apiToken) - .body(Mono.just(request), DirectGeneratePptxRequest.class) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToMono(ApiResponse.class) - .>handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("生成PPT异常," + response.message)); - return; - } - sink.next((Map) response.data.get("pptInfo")); - }) - .block(); - } - - /** - * 查询所有PPT列表 - * - * @param apiToken API令牌 - * @param body 请求体 - * @return PPT列表 - */ - public Map listAllPptx(String apiToken, String body) { - return this.webClient.post() - .uri("/api/ppt/listAllPptx") - .header("token", apiToken) - .bodyValue(body) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(body)) - .bodyToMono(ApiResponse.class) - .>handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("查询所有PPT列表异常," + response.message)); - return; - } - sink.next(response.data); - }) - .block(); - } /** * 分页查询PPT模板 * - * @param apiToken API令牌 - * @param body 请求体 + * @param token 令牌 + * @param request 请求体 * @return 模板列表 */ - public Map getPptTemplates(String apiToken, String body) { + public PagePptTemplateInfo getPptTemplatePage(String token, TemplateQueryRequest request) { return this.webClient.post() .uri("/api/ppt/templates") - .header("token", apiToken) - .bodyValue(body) + .header("token", token) + .bodyValue(request) .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(body)) - .bodyToMono(ApiResponse.class) - .>handle((response, sink) -> { - if (response.code != 0) { - sink.error(new IllegalStateException("分页查询PPT模板异常," + response.message)); - return; - } - sink.next(response.data); + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(new ParameterizedTypeReference() { }) .block(); } + /** - * API响应 + * 生成PPT + * + * @return PPT信息 + */ + public PptInfo generatePptx(String token, GeneratePptxRequest request) { + return this.webClient.post() + .uri("/api/ppt/v2/generatePptx") + .header("token", token) + .body(Mono.just(request), GeneratePptxRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(ApiResponse.class) + .handle((response, sink) -> { + if (response.code != 0) { + sink.error(new IllegalStateException("生成 PPT 异常," + response.message)); + return; + } + sink.next(Objects.requireNonNull(JsonUtils.parseObject(JsonUtils.toJsonString(response.data.get("pptInfo")), PptInfo.class))); + }) + .block(); + } + + + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record CreateTokenRequest( + String apiKey, + String uid, + Integer limit + ) { + public CreateTokenRequest(String apiKey) { + this(apiKey, null, null); + } + } + + /** + * API 通用响应 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) public record ApiResponse( @@ -337,33 +244,13 @@ public class WddApi { } /** - * 创建API令牌请求 + * 创建任务 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record CreateApiTokenRequest( - String uid, - Integer limit - ) { - } - - /** - * 解析文件数据请求 - */ - @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record ParseFileDataRequest( + public record CreateTaskRequest( + Integer type, String content, - String fileUrl - ) { - } - - /** - * 生成大纲请求 - */ - @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record GenerateOutlineRequest( - String subject, - String dataUrl, - String prompt + List files ) { } @@ -371,45 +258,24 @@ public class WddApi { * 生成大纲内容请求 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record GenerateContentRequest( - String outlineMarkdown, - String dataUrl, + public record GenerateOutlineRequest( + String id, + String length, + String scene, + String audience, + String lang, String prompt ) { } /** - * 异步生成大纲内容请求 + * 修改大纲内容请求 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record AsyncGenerateContentRequest( - String outlineMarkdown, - String dataUrl, - String templateId, - String prompt, - @JsonProperty("asyncGenPptx") boolean asyncGenPptx - ) { - public AsyncGenerateContentRequest(String outlineMarkdown, String dataUrl, String templateId, String prompt) { - this(outlineMarkdown, dataUrl, templateId, prompt, true); - } - } - - /** - * 随机模板请求 - */ - @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record RandomTemplateRequest( - Integer size, - TemplateFilter filters - ) { - } - - /** - * 模板过滤器 - */ - @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record TemplateFilter( - Integer type + public record UpdateOutlineRequest( + String id, + String markdown, + String question ) { } @@ -418,32 +284,90 @@ public class WddApi { */ @JsonInclude(value = JsonInclude.Include.NON_NULL) public record GeneratePptxRequest( + String id, String templateId, - @JsonProperty("outlineContentMarkdown") String outlineContentMarkdown, - boolean pptxProperty + String markdown ) { } - /** - * 下载PPT请求 - */ - @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record DownloadPptxRequest( - String id - ) { - } - /** - * 直接生成PPT请求 - */ @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record DirectGeneratePptxRequest( - boolean stream, - String templateId, + public record PptInfo( + String id, + String name, String subject, - String dataUrl, - String prompt, - boolean pptxProperty + String coverUrl, + String fileUrl, + String templateId, + String pptxProperty, + String userId, + String userName, + int companyId, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime updateTime, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime createTime, + String createUser, + String updateUser ) { } -} \ No newline at end of file + + + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record TemplateQueryRequest( + int page, + int size, + Filter filters + ) { + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record Filter( + int type, + String category, + String style, + String themeColor + ) { + } + } + + + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record PagePptTemplateInfo( + List data, + String total + ) { + + } + + + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record PptTemplateInfo( + String id, + int type, + Integer subType, + String layout, + String category, + String style, + String themeColor, + String lang, + boolean animation, + String subject, + String coverUrl, + String fileUrl, + List pageCoverUrls, + String pptxProperty, + int sort, + int num, + Integer imgNum, + int isDeleted, + String userId, + int companyId, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime updateTime, + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + LocalDateTime createTime, + String createUser, + String updateUser + ) { + } + +} \ No newline at end of file From c79273c5d6c151e5a9ed516bd1f4db15d8714643 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Mon, 17 Mar 2025 09:46:47 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E3=80=90=E6=B5=8B=E8=AF=95=E7=94=A8?= =?UTF-8?q?=E4=BE=8B=E6=96=B0=E5=A2=9E=E3=80=91AI=EF=BC=9A=E6=96=87?= =?UTF-8?q?=E5=A4=9A=E5=A4=9A=20API=20=E6=B5=8B=E8=AF=95=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/core/model/wenduoduo/api/WddApi.java | 28 +- .../framework/ai/ppt/wdd/WddApiTests.java | 306 ++++++++++++++++++ 2 files changed, 330 insertions(+), 4 deletions(-) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java index 66583f50af..4a827b04a6 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java @@ -86,8 +86,29 @@ public class WddApi { * 创建任务 * * @param type 类型 + * 1.智能生成(主题、要求) + * 2.上传文件生成 + * 3.上传思维导图生成 + * 4.通过word精准转ppt + * 5.通过网页链接生成 + * 6.粘贴文本内容生成 + * 7.Markdown大纲生成 * @param content 内容 + * type=1 用户输入主题或要求(不超过1000字符) + * type=2、4 不传 + * type=3 幕布等分享链接 + * type=5 网页链接地址(http/https) + * type=6 粘贴文本内容(不超过20000字符) + * type=7 大纲内容(markdown) * @param files 文件列表 + * 文件列表(文件数不超过5个,总大小不超过50M): + * type=1 上传参考文件(非必传,支持多个) + * type=2 上传文件(支持多个) + * type=3 上传思维导图(xmind/mm/md)(仅支持一个) + * type=4 上传word文件(仅支持一个) + * type=5、6、7 不传 + *

+ * 支持格式:doc/docx/pdf/ppt/pptx/txt/md/xls/xlsx/csv/html/epub/mobi/xmind/mm * @return 任务ID */ public ApiResponse createTask(String token, Integer type, String content, List files) { @@ -127,7 +148,7 @@ public class WddApi { return this.webClient.get() .uri(uri) .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(null)) + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(lang)) .bodyToMono(new ParameterizedTypeReference() { }) .>handle((response, sink) -> { @@ -164,15 +185,14 @@ public class WddApi { * @param question 用户修改建议 * @return 大纲内容流 */ - public Flux> updateOutlineContent(String token, String id, String markdown, String question) { - UpdateOutlineRequest request = new UpdateOutlineRequest(id, markdown, question); + public Flux> updateOutlineContent(String token, UpdateOutlineRequest request) { return this.webClient.post() .uri("/api/ppt/v2/updateContent") .header("token", token) .body(Mono.just(request), UpdateOutlineRequest.class) .retrieve() .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToFlux(new ParameterizedTypeReference>() { + .bodyToFlux(new ParameterizedTypeReference<>() { }); } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java new file mode 100644 index 0000000000..b64a4e3fe1 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java @@ -0,0 +1,306 @@ +package cn.iocoder.yudao.framework.ai.ppt.wdd; + +import cn.iocoder.yudao.framework.ai.core.model.wenduoduo.api.WddApi; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; + +import java.util.Map; +import java.util.Objects; + + +/** + * {@link WddApi} 集成测试 + * + * @author xiaoxin + */ +public class WddApiTests { + + private final WddApi wddApi = new WddApi("https://docmee.cn"); + + + private final String token = "sk_FJo7sKErrrEs5CIZz1"; + + + @Test //获取token + @Disabled + public void testCreateApiToken() { + // 准备参数 + String apiKey = "ak_RK1rm7TrEv3E3JSWIK"; + WddApi.CreateTokenRequest request = new WddApi.CreateTokenRequest(apiKey); + // 调用方法 + String token = wddApi.createApiToken(request); + // 打印结果 + System.out.println(token); + } + + + @Test // 创建任务 + @Disabled + public void testCreateTask() { + WddApi.ApiResponse apiResponse = wddApi.createTask(token, 1, "dify 介绍", null); + System.out.println(apiResponse); + } + + + @Test // 创建大纲 + @Disabled + public void testGenerateOutlineRequest() { + WddApi.GenerateOutlineRequest request = new WddApi.GenerateOutlineRequest("1901449466163105792", "medium", null, null, null, null); + //调用 + Flux> flux = wddApi.generateOutlineContent(token, request); + StringBuffer contentBuffer = new StringBuffer(); + flux.doOnNext(chunk -> { + contentBuffer.append(chunk.get("text")); + if (Objects.equals(Integer.parseInt(String.valueOf(chunk.get("status"))), 4)) { + // status 为 4,最终 markdown 结构树 + System.out.println(JsonUtils.toJsonString(chunk.get("result"))); + System.out.println(" ########################################################################"); + } + }).then().block(); + // 打印结果 + System.out.println(contentBuffer); + + } + + @Test // 修改大纲 + @Disabled + public void testUpdateOutlineContentRequest() { + WddApi.UpdateOutlineRequest request = new WddApi.UpdateOutlineRequest("1901449466163105792", TEST_OUT_LINE_CONTENT, "精简一点,三个章节即可"); + //调用 + Flux> flux = wddApi.updateOutlineContent(token, request); + StringBuffer contentBuffer = new StringBuffer(); + flux.doOnNext(chunk -> { + contentBuffer.append(chunk.get("text")); + if (Objects.equals(Integer.parseInt(String.valueOf(chunk.get("status"))), 4)) { + // status 为 4,最终 markdown 结构树 + System.out.println(JsonUtils.toJsonString(chunk.get("result"))); + System.out.println(" ########################################################################"); + } + }).then().block(); + // 打印结果 + System.out.println(contentBuffer); + + } + + @Test // 获取 PPT 模版分页 + @Disabled + public void testGetPptTemplatePage() { + // 准备参数 + WddApi.TemplateQueryRequest.Filter filter = new WddApi.TemplateQueryRequest.Filter(1, null, null, null); + WddApi.TemplateQueryRequest request = new WddApi.TemplateQueryRequest(1, 10, filter); + //调用 + WddApi.PagePptTemplateInfo pptTemplatePage = wddApi.getPptTemplatePage(token, request); + // 打印结果 + System.out.println(pptTemplatePage); + } + + + @Test // 生成 PPT + @Disabled + public void testGeneratePptx() { + // 准备参数 + WddApi.GeneratePptxRequest request = new WddApi.GeneratePptxRequest("1900913633555255296", "1804885538940116992", TEST_OUT_LINE_CONTENT); + //调用 + WddApi.PptInfo pptInfo = wddApi.generatePptx("", request); + // 打印结果 + System.out.println(pptInfo); + } + + + private final String TEST_OUT_LINE_CONTENT = """ + # Dify:新一代AI应用开发平台 + + ## 1 什么是Dify + ### 1.1 Dify定义:AI应用开发平台 + #### 1.1.1 低代码开发 + Dify是一个低代码AI应用开发平台,旨在简化AI应用的构建过程,让开发者无需编写大量代码即可快速创建各种智能应用。 + #### 1.1.2 核心功能 + Dify的核心功能包括数据集成、模型选择、流程编排和应用部署,提供一站式解决方案,加速AI应用的落地和迭代。 + #### 1.1.3 开源与商业 + Dify提供开源版本和商业版本,满足不同用户的需求,开源版本适合个人开发者和小型团队,商业版本则提供更强大的功能和技术支持。 + + ### 1.2 Dify解决的问题:AI开发痛点 + #### 1.2.1 开发周期长 + 传统AI应用开发周期长,需要大量的人力和时间投入,Dify通过可视化界面和预置组件,大幅缩短开发周期。 + #### 1.2.2 技术门槛高 + AI技术门槛高,需要专业的知识和技能,Dify降低技术门槛,让更多开发者能够参与到AI应用的开发中来。 + #### 1.2.3 部署和维护复杂 + AI应用的部署和维护复杂,需要专业的运维团队,Dify提供自动化的部署和维护工具,简化流程,降低成本。 + + ### 1.3 Dify发展历程 + #### 1.3.1 早期探索 + Dify的早期版本主要关注于自然语言处理领域的应用,通过集成各种NLP模型,提供文本分类、情感分析等功能。 + #### 1.3.2 功能扩展 + 随着用户需求的不断增长,Dify的功能逐渐扩展到图像识别、语音识别等领域,支持更多类型的AI应用。 + #### 1.3.3 生态建设 + Dify积极建设开发者生态,提供丰富的文档、教程和案例,帮助开发者更好地使用Dify平台,共同推动AI技术的发展。 + + ## 2 Dify的核心功能 + ### 2.1 数据集成:连接各种数据源 + #### 2.1.1 支持多种数据源 + Dify支持连接各种数据源,包括关系型数据库、NoSQL数据库、文件系统、云存储等,满足不同场景的数据需求。 + #### 2.1.2 数据转换和清洗 + Dify提供数据转换和清洗功能,可以将不同格式的数据转换为统一的格式,并去除无效数据,提高数据质量。 + #### 2.1.3 数据安全 + Dify注重数据安全,采用各种安全措施保护用户的数据,包括数据加密、访问控制、权限管理等。 + + ### 2.2 模型选择:丰富的AI模型库 + #### 2.2.1 预置模型 + Dify预置了丰富的AI模型,包括自然语言处理、图像识别、语音识别等领域的模型,开发者可以直接使用这些模型,无需自行训练,极大的简化了开发流程。 + #### 2.2.2 自定义模型 + Dify支持开发者上传自定义模型,满足个性化的需求。开发者可以将自己训练的模型部署到Dify平台上,与其他开发者共享。 + #### 2.2.3 模型评估 + Dify提供模型评估功能,可以对不同模型进行评估,选择最优的模型,提高应用性能。 + + ### 2.3 流程编排:可视化流程设计器 + #### 2.3.1 可视化界面 + Dify提供可视化的流程设计器,开发者可以通过拖拽组件的方式,设计AI应用的流程,无需编写代码,简单高效。 + #### 2.3.2 灵活的流程控制 + Dify支持灵活的流程控制,可以根据不同的条件执行不同的分支,实现复杂的业务逻辑。 + #### 2.3.3 实时调试 + Dify提供实时调试功能,可以在设计流程的过程中,实时查看流程的执行结果,及时发现和解决问题。 + + ### 2.4 应用部署:一键部署和管理 + #### 2.4.1 快速部署 + Dify提供一键部署功能,可以将AI应用快速部署到各种环境,包括本地环境、云环境、容器环境等。 + #### 2.4.2 自动伸缩 + Dify支持自动伸缩,可以根据应用的负载自动调整资源,保证应用的稳定性和性能。 + #### 2.4.3 监控和告警 + Dify提供监控和告警功能,可以实时监控应用的状态,并在出现问题时及时告警,方便运维人员进行处理。 + + ## 3 Dify的特点和优势 + ### 3.1 低代码:降低开发门槛 + #### 3.1.1 可视化开发 + Dify采用可视化开发模式,开发者无需编写大量代码,只需通过拖拽组件即可完成AI应用的开发,降低了开发门槛。 + #### 3.1.2 预置组件 + Dify预置了丰富的组件,包括数据源组件、模型组件、流程控制组件等,开发者可以直接使用这些组件,提高开发效率。 + #### 3.1.3 减少代码量 + Dify可以显著减少代码量,降低开发难度,让更多开发者能够参与到AI应用的开发中来。 + + ### 3.2 灵活:满足不同场景需求 + #### 3.2.1 支持多种数据源 + Dify支持多种数据源,可以连接各种数据源,满足不同场景的数据需求。 + #### 3.2.2 支持自定义模型 + Dify支持自定义模型,开发者可以将自己训练的模型部署到Dify平台上,满足个性化的需求。 + #### 3.2.3 灵活的流程控制 + Dify支持灵活的流程控制,可以根据不同的条件执行不同的分支,实现复杂的业务逻辑。 + + ### 3.3 高效:加速应用落地 + #### 3.3.1 快速开发 + Dify通过可视化界面和预置组件,大幅缩短开发周期,加速AI应用的落地。 + #### 3.3.2 快速部署 + Dify提供一键部署功能,可以将AI应用快速部署到各种环境,提高部署效率。 + #### 3.3.3 自动化运维 + Dify提供自动化的运维工具,简化运维流程,降低运维成本。 + + ### 3.4 开放:构建繁荣生态 + #### 3.4.1 开源社区 + Dify拥有活跃的开源社区,开发者可以在社区中交流经验、分享资源、共同推动Dify的发展。 + #### 3.4.2 丰富的文档 + Dify提供丰富的文档、教程和案例,帮助开发者更好地使用Dify平台。 + #### 3.4.3 API支持 + Dify提供API支持,开发者可以通过API将Dify集成到自己的系统中,扩展Dify的功能。 + + ## 4 Dify的使用场景 + ### 4.1 智能客服:提升客户服务质量 + #### 4.1.1 自动回复 + Dify可以用于构建智能客服系统,实现自动回复客户的常见问题,提高客户服务效率。 + #### 4.1.2 情感分析 + Dify可以对客户的语音或文本进行情感分析,判断客户的情绪,并根据情绪提供个性化的服务。 + #### 4.1.3 知识库问答 + Dify可以构建知识库问答系统,让客户通过提问的方式获取所需的信息,提高客户满意度。 + + ### 4.2 金融风控:提高风险识别能力 + #### 4.2.1 欺诈检测 + Dify可以用于构建金融风控系统,实现欺诈检测,识别可疑交易,降低风险。 + #### 4.2.2 信用评估 + Dify可以对用户的信用进行评估,并根据评估结果提供不同的金融服务。 + #### 4.2.3 反洗钱 + Dify可以用于反洗钱,识别可疑资金流动,防止犯罪行为。 + + ### 4.3 智慧医疗:提升医疗服务水平 + #### 4.3.1 疾病诊断 + Dify可以用于辅助疾病诊断,提高诊断准确率,缩短诊断时间。 + #### 4.3.2 药物研发 + Dify可以用于药物研发,加速新药的发现和开发。 + #### 4.3.3 智能健康管理 + Dify可以构建智能健康管理系统,为用户提供个性化的健康建议和服务。 + + ### 4.4 智慧城市:提升城市管理效率 + #### 4.4.1 交通优化 + Dify可以用于交通优化,提高交通效率,缓解交通拥堵。 + #### 4.4.2 环境监测 + Dify可以用于环境监测,实时监测空气质量、水质等环境指标,及时发现和解决环境问题。 + #### 4.4.3 智能安防 + Dify可以用于智能安防,提高城市安全水平,预防犯罪行为。 + + ## 5 Dify的成功案例 + ### 5.1 Case 1:某电商平台的智能客服 + #### 5.1.1 项目背景 + 该电商平台客户服务压力大,人工客服成本高,需要一种智能化的解决方案。 + #### 5.1.2 解决方案 + 使用Dify构建智能客服系统,实现自动回复客户的常见问题,并根据客户的情绪提供个性化的服务。 + #### 5.1.3 效果 + 客户服务效率提高50%,客户满意度提高20%,人工客服成本降低30%。 + + ### 5.2 Case 2:某银行的金融风控系统 + #### 5.2.1 项目背景 + 该银行面临日益增长的金融风险,需要一种更有效的风险识别和控制手段。 + #### 5.2.2 解决方案 + 使用Dify构建金融风控系统,实现欺诈检测、信用评估和反洗钱等功能,提高风险识别能力。 + #### 5.2.3 效果 + 欺诈交易识别率提高40%,信用评估准确率提高30%,洗钱风险降低25%。 + + ### 5.3 Case 3:某医院的辅助疾病诊断系统 + #### 5.3.1 项目背景 + 该医院医生工作压力大,疾病诊断准确率有待提高,需要一种辅助诊断工具。 + #### 5.3.2 解决方案 + 使用Dify构建辅助疾病诊断系统,根据患者的病历和症状,提供诊断建议,提高诊断准确率。 + #### 5.3.3 效果 + 疾病诊断准确率提高20%,诊断时间缩短15%,医生工作效率提高10%。 + + ## 6 Dify的未来展望 + ### 6.1 技术升级 + #### 6.1.1 模型优化 + Dify将不断优化预置模型,提高模型性能,并支持更多类型的AI模型。 + #### 6.1.2 流程引擎升级 + Dify将升级流程引擎,提高流程的灵活性和可扩展性,支持更复杂的业务逻辑。 + #### 6.1.3 平台性能优化 + Dify将不断优化平台性能,提高平台的稳定性和可靠性,满足大规模应用的需求。 + + ### 6.2 生态建设 + #### 6.2.1 社区建设 + Dify将继续加强开源社区建设,吸引更多开发者参与,共同推动Dify的发展。 + #### 6.2.2 合作伙伴拓展 + Dify将拓展合作伙伴,与更多的企业和机构合作,共同推动AI技术的应用。 + #### 6.2.3 应用商店 + Dify将构建应用商店,让开发者可以分享自己的应用,用户可以购买和使用这些应用,构建繁荣的生态系统。 + + ### 6.3 应用领域拓展 + #### 6.3.1 智能制造 + Dify将拓展到智能制造领域,为企业提供智能化的生产管理和质量控制解决方案。 + #### 6.3.2 智慧农业 + Dify将拓展到智慧农业领域,为农民提供智能化的种植和养殖管理解决方案。 + #### 6.3.3 更多领域 + Dify将拓展到更多领域,为各行各业提供智能化的解决方案,推动社会发展。 + + ## 7 总结 + ### 7.1 Dify的价值 + #### 7.1.1 降低AI开发门槛 + Dify通过低代码的方式,让更多开发者能够参与到AI应用的开发中来。 + #### 7.1.2 加速AI应用落地 + Dify提供一站式解决方案,加速AI应用的落地和迭代。 + #### 7.1.3 构建繁荣的AI生态 + Dify通过开源社区和应用商店,构建繁荣的AI生态系统。 + + ### 7.2 共同发展 + #### 7.2.1 欢迎加入Dify社区 + 欢迎更多开发者加入Dify社区,共同推动Dify的发展。 + #### 7.2.2 合作共赢 + 期待与更多的企业和机构合作,共同推动AI技术的应用。 + #### 7.2.3 共创未来 + 让我们一起用AI技术改变世界,共创美好未来。"""; + +} From ecc3bd281c3680865546b83772d8bc8d2b3f2177 Mon Sep 17 00:00:00 2001 From: xiaoxin <718949661@qq.com> Date: Mon, 17 Mar 2025 15:44:17 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E3=80=91AI=EF=BC=9A=E8=AE=AF=E9=A3=9E=20PPT=20API=20?= =?UTF-8?q?=E5=AF=B9=E6=8E=A5=EF=BC=8C=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/{WddApi.java => WddPptApi.java} | 82 +- .../core/model/xunfei/api/XunfeiPptApi.java | 766 ++++++++++++++++++ .../{WddApiTests.java => WddPptApiTests.java} | 38 +- .../ai/ppt/xunfei/XunfeiPptApiTests.java | 300 +++++++ 4 files changed, 1124 insertions(+), 62 deletions(-) rename yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/{WddApi.java => WddPptApi.java} (93%) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java rename yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/{WddApiTests.java => WddPptApiTests.java} (91%) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunfeiPptApiTests.java diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddPptApi.java similarity index 93% rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java rename to yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddPptApi.java index 4a827b04a6..b752f73352 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddApi.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/wenduoduo/api/WddPptApi.java @@ -33,7 +33,7 @@ import java.util.function.Predicate; * @author xiaoxin */ @Slf4j -public class WddApi { +public class WddPptApi { public static final String BASE_URL = "https://docmee.cn"; @@ -49,7 +49,7 @@ public class WddApi { sink.error(new IllegalStateException("[wdd-api] 调用失败!")); }); - public WddApi(String baseUrl) { + public WddPptApi(String baseUrl) { this.webClient = WebClient.builder() .baseUrl(baseUrl) .defaultHeaders((headers) -> headers.setContentType(MediaType.APPLICATION_JSON)) @@ -161,42 +161,6 @@ public class WddApi { .block(); } - /** - * 生成大纲内容 - * - * @return 大纲内容流 - */ - public Flux> generateOutlineContent(String token, GenerateOutlineRequest request) { - return this.webClient.post() - .uri("/api/ppt/v2/generateContent") - .header("token", token) - .body(Mono.just(request), GenerateOutlineRequest.class) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToFlux(new ParameterizedTypeReference<>() { - }); - } - - /** - * 修改大纲内容 - * - * @param id 任务ID - * @param markdown 大纲内容markdown - * @param question 用户修改建议 - * @return 大纲内容流 - */ - public Flux> updateOutlineContent(String token, UpdateOutlineRequest request) { - return this.webClient.post() - .uri("/api/ppt/v2/updateContent") - .header("token", token) - .body(Mono.just(request), UpdateOutlineRequest.class) - .retrieve() - .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) - .bodyToFlux(new ParameterizedTypeReference<>() { - }); - } - - /** * 分页查询PPT模板 * @@ -204,7 +168,7 @@ public class WddApi { * @param request 请求体 * @return 模板列表 */ - public PagePptTemplateInfo getPptTemplatePage(String token, TemplateQueryRequest request) { + public PagePptTemplateInfo getTemplatePage(String token, TemplateQueryRequest request) { return this.webClient.post() .uri("/api/ppt/templates") .header("token", token) @@ -216,17 +180,49 @@ public class WddApi { .block(); } + /** + * 生成大纲内容 + * + * @return 大纲内容流 + */ + public Flux> createOutline(String token, CreateOutlineRequest request) { + return this.webClient.post() + .uri("/api/ppt/v2/generateContent") + .header("token", token) + .body(Mono.just(request), CreateOutlineRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToFlux(new ParameterizedTypeReference<>() { + }); + } + + /** + * 修改大纲内容 + * + * @param request 请求体 + * @return 大纲内容流 + */ + public Flux> updateOutline(String token, UpdateOutlineRequest request) { + return this.webClient.post() + .uri("/api/ppt/v2/updateContent") + .header("token", token) + .body(Mono.just(request), UpdateOutlineRequest.class) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToFlux(new ParameterizedTypeReference<>() { + }); + } /** * 生成PPT * * @return PPT信息 */ - public PptInfo generatePptx(String token, GeneratePptxRequest request) { + public PptInfo create(String token, CreatePptRequest request) { return this.webClient.post() .uri("/api/ppt/v2/generatePptx") .header("token", token) - .body(Mono.just(request), GeneratePptxRequest.class) + .body(Mono.just(request), CreatePptRequest.class) .retrieve() .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) .bodyToMono(ApiResponse.class) @@ -278,7 +274,7 @@ public class WddApi { * 生成大纲内容请求 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record GenerateOutlineRequest( + public record CreateOutlineRequest( String id, String length, String scene, @@ -303,7 +299,7 @@ public class WddApi { * 生成PPT请求 */ @JsonInclude(value = JsonInclude.Include.NON_NULL) - public record GeneratePptxRequest( + public record CreatePptRequest( String id, String templateId, String markdown diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java new file mode 100644 index 0000000000..fe8ba4c046 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xunfei/api/XunfeiPptApi.java @@ -0,0 +1,766 @@ +package cn.iocoder.yudao.framework.ai.core.model.xunfei.api; + +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * 讯飞智能PPT生成 API + *

+ * 对接讯飞:智能 PPT 生成 API + * + * @author xiaoxin + */ +@Slf4j +public class XunfeiPptApi { + + public static final String BASE_URL = "https://zwapi.xfyun.cn/api/ppt/v2"; + + private final WebClient webClient; + private final String appId; + private final String apiSecret; + + private final Predicate STATUS_PREDICATE = status -> !status.is2xxSuccessful(); + + private final Function>> EXCEPTION_FUNCTION = + reqParam -> response -> response.bodyToMono(String.class).handle((responseBody, sink) -> { + log.error("[xunfei-ppt-api] 调用失败!请求参数:[{}],响应数据: [{}]", reqParam, responseBody); + sink.error(new IllegalStateException("[xunfei-ppt-api] 调用失败!")); + }); + + public XunfeiPptApi(String baseUrl, String appId, String apiSecret) { + this.webClient = WebClient.builder() + .baseUrl(baseUrl) + .build(); + this.appId = appId; + this.apiSecret = apiSecret; + } + + /** + * 获取签名 + * + * @return 签名信息 + */ + private SignatureInfo getSignature() { + long timestamp = System.currentTimeMillis() / 1000; + String ts = String.valueOf(timestamp); + String signature = generateSignature(appId, apiSecret, timestamp); + return new SignatureInfo(appId, ts, signature); + } + + /** + * 生成签名 + * + * @param appId 应用ID + * @param apiSecret 应用密钥 + * @param timestamp 时间戳(秒) + * @return 签名 + */ + private String generateSignature(String appId, String apiSecret, long timestamp) { + try { + String auth = md5(appId + timestamp); + return hmacSHA1Encrypt(auth, apiSecret); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + log.error("[xunfei-ppt-api] 生成签名失败", e); + throw new IllegalStateException("[xunfei-ppt-api] 生成签名失败"); + } + } + + /** + * HMAC SHA1 加密 + */ + private String hmacSHA1Encrypt(String encryptText, String encryptKey) + throws NoSuchAlgorithmException, InvalidKeyException { + SecretKeySpec keySpec = new SecretKeySpec( + encryptKey.getBytes(StandardCharsets.UTF_8), "HmacSHA1"); + + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(keySpec); + byte[] result = mac.doFinal(encryptText.getBytes(StandardCharsets.UTF_8)); + + return Base64.getEncoder().encodeToString(result); + } + + /** + * MD5 哈希 + */ + private String md5(String text) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] digest = md.digest(text.getBytes(StandardCharsets.UTF_8)); + + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + /** + * 获取PPT模板列表 + * + * @param style 风格,如"商务" + * @param pageSize 每页数量 + * @return 模板列表 + */ + public TemplatePageResponse getTemplatePage(String style, Integer pageSize) { + SignatureInfo signInfo = getSignature(); + Map requestBody = new HashMap<>(); + requestBody.put("style", style); + requestBody.put("pageSize", pageSize != null ? pageSize.toString() : "10"); + + return this.webClient.post() + .uri("/template/list") + .header("appId", signInfo.appId) + .header("timestamp", signInfo.timestamp) + .header("signature", signInfo.signature) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(requestBody) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(requestBody)) + .bodyToMono(TemplatePageResponse.class) + .block(); + } + + /** + * 创建大纲(通过文本) + * + * @param query 查询文本 + * @return 大纲创建响应 + */ + public CreateResponse createOutline(String query) { + SignatureInfo signInfo = getSignature(); + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("query", query); + + return this.webClient.post() + .uri("/createOutline") + .header("appId", signInfo.appId) + .header("timestamp", signInfo.timestamp) + .header("signature", signInfo.signature) + .contentType(MediaType.MULTIPART_FORM_DATA) + .body(BodyInserters.fromMultipartData(formData)) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(formData)) + .bodyToMono(CreateResponse.class) + .block(); + } + + + /** + * 直接创建PPT(简化版 - 通过文本) + * + * @param query 查询文本 + * @return 创建响应 + */ + public CreateResponse create(String query) { + CreatePptRequest request = CreatePptRequest.builder() + .query(query) + .build(); + return create(request); + } + + /** + * 直接创建PPT(简化版 - 通过文件) + * + * @param file 文件 + * @param fileName 文件名 + * @return 创建响应 + */ + public CreateResponse create(MultipartFile file, String fileName) { + CreatePptRequest request = CreatePptRequest.builder() + .file(file) + .fileName(fileName) + .build(); + return create(request); + } + + /** + * 直接创建PPT(完整版) + * + * @param request 请求参数 + * @return 创建响应 + */ + public CreateResponse create(CreatePptRequest request) { + SignatureInfo signInfo = getSignature(); + MultiValueMap formData = buildCreateFormData(request); + + return this.webClient.post() + .uri("/create") + .header("appId", signInfo.appId) + .header("timestamp", signInfo.timestamp) + .header("signature", signInfo.signature) + .contentType(MediaType.MULTIPART_FORM_DATA) + .body(BodyInserters.fromMultipartData(formData)) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(formData)) + .bodyToMono(CreateResponse.class) + .block(); + } + + + /** + * 通过大纲创建PPT(简化版) + * + * @param outline 大纲内容 + * @param query 查询文本 + * @return 创建响应 + */ + public CreateResponse createPptByOutline(OutlineData outline, String query) { + CreatePptByOutlineRequest request = CreatePptByOutlineRequest.builder() + .outline(outline) + .query(query) + .build(); + return createPptByOutline(request); + } + + /** + * 通过大纲创建PPT(完整版) + * + * @param request 请求参数 + * @return 创建响应 + */ + public CreateResponse createPptByOutline(CreatePptByOutlineRequest request) { + SignatureInfo signInfo = getSignature(); + + return this.webClient.post() + .uri("/createPptByOutline") + .header("appId", signInfo.appId) + .header("timestamp", signInfo.timestamp) + .header("signature", signInfo.signature) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(request) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(request)) + .bodyToMono(CreateResponse.class) + .block(); + } + + /** + * 检查PPT生成进度 + * + * @param sid 任务ID + * @return 进度响应 + */ + public ProgressResponse checkProgress(String sid) { + SignatureInfo signInfo = getSignature(); + + return this.webClient.get() + .uri(uriBuilder -> uriBuilder + .path("/progress") + .queryParam("sid", sid) + .build()) + .header("appId", signInfo.appId) + .header("timestamp", signInfo.timestamp) + .header("signature", signInfo.signature) + .retrieve() + .onStatus(STATUS_PREDICATE, EXCEPTION_FUNCTION.apply(sid)) + .bodyToMono(ProgressResponse.class) + .block(); + } + + /** + * 签名信息 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + private record SignatureInfo( + String appId, + String timestamp, + String signature + ) { + } + + /** + * 模板列表响应 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record TemplatePageResponse( + boolean flag, + int code, + String desc, + Integer count, + TemplatePageData data + ) { + } + + /** + * 模板列表数据 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record TemplatePageData( + String total, + List records, + Integer pageNum + ) { + } + + /** + * 模板信息 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record TemplateInfo( + String templateIndexId, + Integer pageCount, + String type, + String color, + String industry, + String style, + String detailImage + ) { + } + + /** + * 创建响应 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record CreateResponse( + boolean flag, + int code, + String desc, + Integer count, + CreateResponseData data + ) { + } + + /** + * 创建响应数据 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record CreateResponseData( + String sid, + String coverImgSrc, + String title, + String subTitle, + OutlineData outline + ) { + } + + /** + * 大纲数据结构 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record OutlineData( + String title, + String subTitle, + List chapters + ) { + /** + * 章节结构 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record Chapter( + String chapterTitle, + List chapterContents + ) { + /** + * 章节内容 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record ChapterContent( + String chapterTitle + ) { + } + } + + /** + * 将大纲对象转换为JSON字符串 + * + * @return 大纲JSON字符串 + */ + public String toJsonString() { + return JsonUtils.toJsonString(this); + } + } + + /** + * 进度响应 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record ProgressResponse( + int code, + String desc, + ProgressResponseData data + ) { + } + + /** + * 进度响应数据 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record ProgressResponseData( + int process, + String pptId, + String pptUrl, + String pptStatus, // PPT构建状态:building(构建中),done(已完成),build_failed(生成失败) + String aiImageStatus, // ai配图状态:building(构建中),done(已完成) + String cardNoteStatus, // 演讲备注状态:building(构建中),done(已完成) + String errMsg, // 生成PPT的失败信息 + Integer totalPages, // 生成PPT的总页数 + Integer donePages // 生成PPT的完成页数 + ) { + /** + * 是否全部完成 + * + * @return 是否全部完成 + */ + public boolean isAllDone() { + return "done".equals(pptStatus) + && ("done".equals(aiImageStatus) || aiImageStatus == null) + && ("done".equals(cardNoteStatus) || cardNoteStatus == null); + } + + /** + * 是否失败 + * + * @return 是否失败 + */ + public boolean isFailed() { + return "build_failed".equals(pptStatus); + } + + /** + * 获取进度百分比 + * + * @return 进度百分比 + */ + public int getProgressPercent() { + if (totalPages == null || totalPages == 0 || donePages == null) { + return process; // 兼容旧版返回 + } + return (int) (donePages * 100.0 / totalPages); + } + } + + /** + * 通过大纲创建PPT请求参数 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record CreatePptByOutlineRequest( + String query, // 用户生成PPT要求(最多8000字) + String outlineSid, // 已生成大纲后,响应返回的请求大纲唯一id + OutlineData outline, // 大纲内容 + String templateId, // 模板ID + String businessId, // 业务ID(非必传) + String author, // PPT作者名 + Boolean isCardNote, // 是否生成PPT演讲备注 + Boolean search, // 是否联网搜索 + String language, // 语种 + String fileUrl, // 文件地址 + String fileName, // 文件名(带文件名后缀) + Boolean isFigure, // 是否自动配图 + String aiImage // ai配图类型:normal、advanced + ) { + /** + * 创建构建器 + * + * @return 构建器 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * 构建器类 + */ + public static class Builder { + private String query; + private String outlineSid; + private OutlineData outline; + private String templateId; + private String businessId; + private String author; + private Boolean isCardNote; + private Boolean search; + private String language; + private String fileUrl; + private String fileName; + private Boolean isFigure; + private String aiImage; + + public Builder query(String query) { + this.query = query; + return this; + } + + public Builder outlineSid(String outlineSid) { + this.outlineSid = outlineSid; + return this; + } + + public Builder outline(OutlineData outline) { + this.outline = outline; + return this; + } + + public Builder templateId(String templateId) { + this.templateId = templateId; + return this; + } + + public Builder businessId(String businessId) { + this.businessId = businessId; + return this; + } + + public Builder author(String author) { + this.author = author; + return this; + } + + public Builder isCardNote(Boolean isCardNote) { + this.isCardNote = isCardNote; + return this; + } + + public Builder search(Boolean search) { + this.search = search; + return this; + } + + public Builder language(String language) { + this.language = language; + return this; + } + + public Builder fileUrl(String fileUrl) { + this.fileUrl = fileUrl; + return this; + } + + public Builder fileName(String fileName) { + this.fileName = fileName; + return this; + } + + public Builder isFigure(Boolean isFigure) { + this.isFigure = isFigure; + return this; + } + + public Builder aiImage(String aiImage) { + this.aiImage = aiImage; + return this; + } + + public CreatePptByOutlineRequest build() { + return new CreatePptByOutlineRequest( + query, outlineSid, outline, templateId, businessId, author, + isCardNote, search, language, fileUrl, fileName, isFigure, aiImage + ); + } + } + } + + /** + * 构建创建PPT的表单数据 + * + * @param request 请求参数 + * @return 表单数据 + */ + private MultiValueMap buildCreateFormData(CreatePptRequest request) { + MultiValueMap formData = new LinkedMultiValueMap<>(); + // 添加请求参数 + if (request.query() != null) { + formData.add("query", request.query()); + } + + if (request.file() != null) { + try { + formData.add("file", new ByteArrayResource(request.file().getBytes()) { + @Override + public String getFilename() { + return request.file().getOriginalFilename(); + } + }); + } catch (IOException e) { + log.error("[xunfei-ppt-api] 文件处理失败", e); + throw new IllegalStateException("[xunfei-ppt-api] 文件处理失败", e); + } + } + + if (request.fileUrl() != null) { + formData.add("fileUrl", request.fileUrl()); + } + + if (request.fileName() != null) { + formData.add("fileName", request.fileName()); + } + + if (request.templateId() != null) { + formData.add("templateId", request.templateId()); + } + + if (request.businessId() != null) { + formData.add("businessId", request.businessId()); + } + + if (request.author() != null) { + formData.add("author", request.author()); + } + + if (request.isCardNote() != null) { + formData.add("isCardNote", request.isCardNote().toString()); + } + + if (request.search() != null) { + formData.add("search", request.search().toString()); + } + + if (request.language() != null) { + formData.add("language", request.language()); + } + + if (request.isFigure() != null) { + formData.add("isFigure", request.isFigure().toString()); + } + + if (request.aiImage() != null) { + formData.add("aiImage", request.aiImage()); + } + + return formData; + } + + /** + * 直接生成PPT请求参数 + */ + @JsonInclude(value = JsonInclude.Include.NON_NULL) + public record CreatePptRequest( + String query, // 用户生成PPT要求(最多8000字) + MultipartFile file, // 上传文件 + String fileUrl, // 文件地址 + String fileName, // 文件名(带文件名后缀) + String templateId, // 模板ID + String businessId, // 业务ID(非必传) + String author, // PPT作者名 + Boolean isCardNote, // 是否生成PPT演讲备注 + Boolean search, // 是否联网搜索 + String language, // 语种 + Boolean isFigure, // 是否自动配图 + String aiImage // ai配图类型:normal、advanced + ) { + /** + * 创建构建器 + * + * @return 构建器 + */ + public static Builder builder() { + return new Builder(); + } + + /** + * 构建器类 + */ + public static class Builder { + private String query; + private MultipartFile file; + private String fileUrl; + private String fileName; + private String templateId; + private String businessId; + private String author; + private Boolean isCardNote; + private Boolean search; + private String language; + private Boolean isFigure; + private String aiImage; + + public Builder query(String query) { + this.query = query; + return this; + } + + public Builder file(MultipartFile file) { + this.file = file; + return this; + } + + public Builder fileUrl(String fileUrl) { + this.fileUrl = fileUrl; + return this; + } + + public Builder fileName(String fileName) { + this.fileName = fileName; + return this; + } + + public Builder templateId(String templateId) { + this.templateId = templateId; + return this; + } + + public Builder businessId(String businessId) { + this.businessId = businessId; + return this; + } + + public Builder author(String author) { + this.author = author; + return this; + } + + public Builder isCardNote(Boolean isCardNote) { + this.isCardNote = isCardNote; + return this; + } + + public Builder search(Boolean search) { + this.search = search; + return this; + } + + public Builder language(String language) { + this.language = language; + return this; + } + + public Builder isFigure(Boolean isFigure) { + this.isFigure = isFigure; + return this; + } + + public Builder aiImage(String aiImage) { + this.aiImage = aiImage; + return this; + } + + public CreatePptRequest build() { + // 验证参数 + if (query == null && file == null && fileUrl == null) { + throw new IllegalArgumentException("query、file、fileUrl必填其一"); + } + if ((file != null || fileUrl != null) && fileName == null) { + throw new IllegalArgumentException("如果传file或者fileUrl,fileName必填"); + } + return new CreatePptRequest( + query, file, fileUrl, fileName, templateId, businessId, author, + isCardNote, search, language, isFigure, aiImage + ); + } + } + } +} \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java similarity index 91% rename from yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java rename to yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java index b64a4e3fe1..e01311c8bd 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddApiTests.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/wdd/WddPptApiTests.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.framework.ai.ppt.wdd; -import cn.iocoder.yudao.framework.ai.core.model.wenduoduo.api.WddApi; +import cn.iocoder.yudao.framework.ai.core.model.wenduoduo.api.WddPptApi; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -11,26 +11,26 @@ import java.util.Objects; /** - * {@link WddApi} 集成测试 + * {@link WddPptApi} 集成测试 * * @author xiaoxin */ -public class WddApiTests { +public class WddPptApiTests { - private final WddApi wddApi = new WddApi("https://docmee.cn"); + private final WddPptApi wddPptApi = new WddPptApi("https://docmee.cn"); - private final String token = "sk_FJo7sKErrrEs5CIZz1"; + private final String token = ""; @Test //获取token @Disabled public void testCreateApiToken() { // 准备参数 - String apiKey = "ak_RK1rm7TrEv3E3JSWIK"; - WddApi.CreateTokenRequest request = new WddApi.CreateTokenRequest(apiKey); + String apiKey = ""; + WddPptApi.CreateTokenRequest request = new WddPptApi.CreateTokenRequest(apiKey); // 调用方法 - String token = wddApi.createApiToken(request); + String token = wddPptApi.createApiToken(request); // 打印结果 System.out.println(token); } @@ -39,7 +39,7 @@ public class WddApiTests { @Test // 创建任务 @Disabled public void testCreateTask() { - WddApi.ApiResponse apiResponse = wddApi.createTask(token, 1, "dify 介绍", null); + WddPptApi.ApiResponse apiResponse = wddPptApi.createTask(token, 1, "dify 介绍", null); System.out.println(apiResponse); } @@ -47,9 +47,9 @@ public class WddApiTests { @Test // 创建大纲 @Disabled public void testGenerateOutlineRequest() { - WddApi.GenerateOutlineRequest request = new WddApi.GenerateOutlineRequest("1901449466163105792", "medium", null, null, null, null); + WddPptApi.CreateOutlineRequest request = new WddPptApi.CreateOutlineRequest("1901539019628613632", "medium", null, null, null, null); //调用 - Flux> flux = wddApi.generateOutlineContent(token, request); + Flux> flux = wddPptApi.createOutline(token, request); StringBuffer contentBuffer = new StringBuffer(); flux.doOnNext(chunk -> { contentBuffer.append(chunk.get("text")); @@ -66,10 +66,10 @@ public class WddApiTests { @Test // 修改大纲 @Disabled - public void testUpdateOutlineContentRequest() { - WddApi.UpdateOutlineRequest request = new WddApi.UpdateOutlineRequest("1901449466163105792", TEST_OUT_LINE_CONTENT, "精简一点,三个章节即可"); + public void testUpdateOutlineRequest() { + WddPptApi.UpdateOutlineRequest request = new WddPptApi.UpdateOutlineRequest("1901539019628613632", TEST_OUT_LINE_CONTENT, "精简一点,三个章节即可"); //调用 - Flux> flux = wddApi.updateOutlineContent(token, request); + Flux> flux = wddPptApi.updateOutline(token, request); StringBuffer contentBuffer = new StringBuffer(); flux.doOnNext(chunk -> { contentBuffer.append(chunk.get("text")); @@ -88,10 +88,10 @@ public class WddApiTests { @Disabled public void testGetPptTemplatePage() { // 准备参数 - WddApi.TemplateQueryRequest.Filter filter = new WddApi.TemplateQueryRequest.Filter(1, null, null, null); - WddApi.TemplateQueryRequest request = new WddApi.TemplateQueryRequest(1, 10, filter); + WddPptApi.TemplateQueryRequest.Filter filter = new WddPptApi.TemplateQueryRequest.Filter(1, null, null, null); + WddPptApi.TemplateQueryRequest request = new WddPptApi.TemplateQueryRequest(1, 10, filter); //调用 - WddApi.PagePptTemplateInfo pptTemplatePage = wddApi.getPptTemplatePage(token, request); + WddPptApi.PagePptTemplateInfo pptTemplatePage = wddPptApi.getTemplatePage(token, request); // 打印结果 System.out.println(pptTemplatePage); } @@ -101,9 +101,9 @@ public class WddApiTests { @Disabled public void testGeneratePptx() { // 准备参数 - WddApi.GeneratePptxRequest request = new WddApi.GeneratePptxRequest("1900913633555255296", "1804885538940116992", TEST_OUT_LINE_CONTENT); + WddPptApi.CreatePptRequest request = new WddPptApi.CreatePptRequest("1901539019628613632", "1805081814809960448", TEST_OUT_LINE_CONTENT); //调用 - WddApi.PptInfo pptInfo = wddApi.generatePptx("", request); + WddPptApi.PptInfo pptInfo = wddPptApi.create(token, request); // 打印结果 System.out.println(pptInfo); } diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunfeiPptApiTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunfeiPptApiTests.java new file mode 100644 index 0000000000..b710f59942 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/ppt/xunfei/XunfeiPptApiTests.java @@ -0,0 +1,300 @@ +package cn.iocoder.yudao.framework.ai.ppt.xunfei; + +import cn.iocoder.yudao.framework.ai.core.model.xunfei.api.XunfeiPptApi; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * {@link XunfeiPptApi} 集成测试 + * + * @author xiaoxin + */ +public class XunfeiPptApiTests { + + // 讯飞API配置信息,实际使用时请替换为您的应用信息 + private static final String APP_ID = ""; + private static final String API_SECRET = ""; + + private final XunfeiPptApi xunfeiPptApi = new XunfeiPptApi(XunfeiPptApi.BASE_URL, APP_ID, API_SECRET); + + @Test // 获取PPT模板列表 + @Disabled + public void testGetTemplatePage() { + // 调用方法 + XunfeiPptApi.TemplatePageResponse response = xunfeiPptApi.getTemplatePage("商务", 10); + // 打印结果 + System.out.println("模板列表响应:" + JsonUtils.toJsonString(response)); + + if (response != null && response.data() != null && response.data().records() != null) { + System.out.println("模板总数:" + response.data().total()); + System.out.println("当前页码:" + response.data().pageNum()); + System.out.println("模板数量:" + response.data().records().size()); + + // 打印第一个模板的信息(如果存在) + if (!response.data().records().isEmpty()) { + XunfeiPptApi.TemplateInfo firstTemplate = response.data().records().get(0); + System.out.println("模板ID:" + firstTemplate.templateIndexId()); + System.out.println("模板风格:" + firstTemplate.style()); + System.out.println("模板颜色:" + firstTemplate.color()); + System.out.println("模板行业:" + firstTemplate.industry()); + } + } + } + + @Test // 创建大纲(通过文本) + @Disabled + public void testCreateOutline() { + XunfeiPptApi.CreateResponse response = getCreateResponse(); + // 打印结果 + System.out.println("创建大纲响应:" + JsonUtils.toJsonString(response)); + + // 保存sid和outline用于后续测试 + if (response != null && response.data() != null) { + System.out.println("sid: " + response.data().sid()); + if (response.data().outline() != null) { + // 使用OutlineData的toJsonString方法 + System.out.println("outline: " + response.data().outline().toJsonString()); + // 将outline对象转换为JSON字符串,用于后续createPptByOutline测试 + String outlineJson = response.data().outline().toJsonString(); + System.out.println("可用于createPptByOutline的outline字符串: " + outlineJson); + } + } + } + + // 创建大纲(通过文本) + private XunfeiPptApi.CreateResponse getCreateResponse() { + // 准备参数 + String param = "智能体平台 Dify 介绍"; + // 调用方法 + return xunfeiPptApi.createOutline(param); + } + + @Test // 通过大纲创建PPT(完整参数) + @Disabled + public void testCreatePptByOutlineWithFullParams() { + // 创建大纲对象 + XunfeiPptApi.CreateResponse createResponse = getCreateResponse(); + // 调用方法 + XunfeiPptApi.CreateResponse response = xunfeiPptApi.createPptByOutline(createResponse.data().outline(), "精简一些,不要超过6个章节"); + // 打印结果 + System.out.println("通过大纲创建PPT响应:" + JsonUtils.toJsonString(response)); + + // 保存sid用于后续进度查询 + if (response != null && response.data() != null) { + System.out.println("sid: " + response.data().sid()); + if (response.data().coverImgSrc() != null) { + System.out.println("封面图片: " + response.data().coverImgSrc()); + } + } + } + + @Test // 检查PPT生成进度 + @Disabled + public void testCheckProgress() { + // 准备参数 - 使用之前创建PPT时返回的sid + String sid = "e96dac09f2ec4ee289f029a5fb874ecd"; // 替换为实际的sid + + // 调用方法 + XunfeiPptApi.ProgressResponse response = xunfeiPptApi.checkProgress(sid); + // 打印结果 + System.out.println("检查进度响应:" + JsonUtils.toJsonString(response)); + + // 安全地访问响应数据 + if (response != null && response.data() != null) { + XunfeiPptApi.ProgressResponseData data = response.data(); + + // 打印PPT生成状态 + System.out.println("PPT构建状态: " + data.pptStatus()); + System.out.println("AI配图状态: " + data.aiImageStatus()); + System.out.println("演讲备注状态: " + data.cardNoteStatus()); + + // 打印进度信息 + if (data.totalPages() != null && data.donePages() != null) { + System.out.println("总页数: " + data.totalPages()); + System.out.println("已完成页数: " + data.donePages()); + System.out.println("完成进度: " + data.getProgressPercent() + "%"); + } else { + System.out.println("进度: " + data.process() + "%"); + } + + // 检查是否完成 + if (data.isAllDone()) { + System.out.println("PPT生成已完成!"); + System.out.println("PPT下载链接: " + data.pptUrl()); + } + // 检查是否失败 + else if (data.isFailed()) { + System.out.println("PPT生成失败!"); + System.out.println("错误信息: " + data.errMsg()); + } + // 正在进行中 + else { + System.out.println("PPT生成中,请稍后再查询..."); + } + } + } + + @Test // 轮询检查PPT生成进度直到完成 + @Disabled + public void testPollCheckProgress() throws InterruptedException { + // 准备参数 - 使用之前创建PPT时返回的sid + String sid = "fa36e926f2ed434987fcb4c1f0776ffb"; // 替换为实际的sid + + // 最大轮询次数 + int maxPolls = 20; + // 轮询间隔(毫秒)- 讯飞API限流为3秒一次 + long pollInterval = 3500; + + for (int i = 0; i < maxPolls; i++) { + System.out.println("第" + (i + 1) + "次查询进度..."); + + // 调用方法 + XunfeiPptApi.ProgressResponse response = xunfeiPptApi.checkProgress(sid); + + // 安全地访问响应数据 + if (response != null && response.data() != null) { + XunfeiPptApi.ProgressResponseData data = response.data(); + + // 打印进度信息 + System.out.println("PPT构建状态: " + data.pptStatus()); + if (data.totalPages() != null && data.donePages() != null) { + System.out.println("完成进度: " + data.donePages() + "/" + data.totalPages() + + " (" + data.getProgressPercent() + "%)"); + } + + // 检查是否完成 + if (data.isAllDone()) { + System.out.println("PPT生成已完成!"); + System.out.println("PPT下载链接: " + data.pptUrl()); + break; + } + // 检查是否失败 + else if (data.isFailed()) { + System.out.println("PPT生成失败!"); + System.out.println("错误信息: " + data.errMsg()); + break; + } + // 正在进行中,继续轮询 + else { + System.out.println("PPT生成中,等待" + (pollInterval / 1000) + "秒后继续查询..."); + Thread.sleep(pollInterval); + } + } else { + System.out.println("查询失败,等待" + (pollInterval / 1000) + "秒后重试..."); + Thread.sleep(pollInterval); + } + } + } + + @Test // 直接创建PPT(通过文本) + @Disabled + public void testCreatePptByText() { + // 准备参数 + String query = "合肥天气趋势分析,包括近5年的气温变化、降水量变化、极端天气事件,以及对城市生活的影响"; + + // 调用方法 + XunfeiPptApi.CreateResponse response = xunfeiPptApi.create(query); + // 打印结果 + System.out.println("直接创建PPT响应:" + JsonUtils.toJsonString(response)); + + // 保存sid用于后续进度查询 + if (response != null && response.data() != null) { + System.out.println("sid: " + response.data().sid()); + if (response.data().coverImgSrc() != null) { + System.out.println("封面图片: " + response.data().coverImgSrc()); + } + System.out.println("标题: " + response.data().title()); + System.out.println("副标题: " + response.data().subTitle()); + } + } + + @Test // 直接创建PPT(通过文件) + @Disabled + public void testCreatePptByFile() throws IOException { + // 准备参数 + File file = new File("src/test/resources/test.txt"); // 请确保此文件存在 + MultipartFile multipartFile = convertFileToMultipartFile(file); + + // 调用方法 + XunfeiPptApi.CreateResponse response = xunfeiPptApi.create(multipartFile, file.getName()); + // 打印结果 + System.out.println("通过文件创建PPT响应:" + JsonUtils.toJsonString(response)); + + // 保存sid用于后续进度查询 + if (response != null && response.data() != null) { + System.out.println("sid: " + response.data().sid()); + if (response.data().coverImgSrc() != null) { + System.out.println("封面图片: " + response.data().coverImgSrc()); + } + System.out.println("标题: " + response.data().title()); + System.out.println("副标题: " + response.data().subTitle()); + } + } + + @Test // 直接创建PPT(完整参数) + @Disabled + public void testCreatePptWithFullParams() throws IOException { + // 准备参数 + String query = "合肥天气趋势分析,包括近5年的气温变化、降水量变化、极端天气事件,以及对城市生活的影响"; + + // 创建请求对象 + XunfeiPptApi.CreatePptRequest request = XunfeiPptApi.CreatePptRequest.builder() + .query(query) + .language("cn") + .isCardNote(true) + .search(true) + .isFigure(true) + .aiImage("advanced") + .author("测试用户") + .build(); + + // 调用方法 + XunfeiPptApi.CreateResponse response = xunfeiPptApi.create(request); + // 打印结果 + System.out.println("使用完整参数创建PPT响应:" + JsonUtils.toJsonString(response)); + + // 保存sid用于后续进度查询 + if (response != null && response.data() != null) { + String sid = response.data().sid(); + System.out.println("sid: " + sid); + if (response.data().coverImgSrc() != null) { + System.out.println("封面图片: " + response.data().coverImgSrc()); + } + System.out.println("标题: " + response.data().title()); + System.out.println("副标题: " + response.data().subTitle()); + + // 立即查询一次进度 + System.out.println("立即查询进度..."); + XunfeiPptApi.ProgressResponse progressResponse = xunfeiPptApi.checkProgress(sid); + if (progressResponse != null && progressResponse.data() != null) { + XunfeiPptApi.ProgressResponseData progressData = progressResponse.data(); + System.out.println("PPT构建状态: " + progressData.pptStatus()); + if (progressData.totalPages() != null && progressData.donePages() != null) { + System.out.println("完成进度: " + progressData.donePages() + "/" + progressData.totalPages() + + " (" + progressData.getProgressPercent() + "%)"); + } + } + } + } + + /** + * 将File转换为MultipartFile + */ + private MultipartFile convertFileToMultipartFile(File file) throws IOException { + FileInputStream input = new FileInputStream(file); + return new MockMultipartFile( + "file", + file.getName(), + "text/plain", + input.readAllBytes() + ); + } + +} \ No newline at end of file