From 566b5e5bdf4f04cca2a3e6220755c7358d3f4f58 Mon Sep 17 00:00:00 2001 From: Minseo Kang Date: Tue, 12 Nov 2024 02:27:02 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=ED=99=8D=EB=B3=B4=20=EB=B9=84?= =?UTF-8?q?=EC=9A=A9=20=EA=B4=80=EB=A6=AC=20API=20=EC=9E=91=EC=84=B1=20(#8?= =?UTF-8?q?0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :recycle: Prompt 클래스명 변경 * :sparkles: 홍보 비용 관리 시스템 프롬프트 작성 * :zap: 홍보 비용 프롬프트에 더미데이터 추가 * :recycle: 홍보 타임라인 관련 메소드와 클래스명 변경 * :sparkles: 비용 관리 컨트롤러 작성 * :truck: 타임라인 관련 dto 패키지 이동 * :art: 컨트롤러에서 사용할 dto 정의 * :sparkles: 서비스 로직 완성 * :pencil2: 간단한 오타 수정 * :memo: Swagger 문서 업데이트 * :art: 코드 정렬 --- .../openai/controller/OpenAiController.java | 9 +- .../controller/OpenAiControllerApi.java | 6 +- .../openai/service/OpenAiService.java | 38 ++- .../controller/PromotionController.java | 20 +- .../controller/PromotionControllerApi.java | 134 +++----- .../promotion/dto/cost/PromotionCostReq.java | 21 ++ .../promotion/dto/cost/PromotionCostRes.java | 15 + .../PromotionTimelineReq.java} | 4 +- .../PromotionTimelineRes.java} | 4 +- .../promotion/dto/{ => timeline}/Task.java | 2 +- .../promotion/service/PromotionService.java | 12 +- .../service/PromotionServiceImpl.java | 38 ++- .../util/constant/CostPrompt.java | 133 ++++++++ .../util/constant/OpenAiPrompt.java | 314 ----------------- .../util/constant/TimelinePrompt.java | 317 ++++++++++++++++++ 15 files changed, 626 insertions(+), 441 deletions(-) create mode 100644 src/main/java/org/example/gather_back_end/promotion/dto/cost/PromotionCostReq.java create mode 100644 src/main/java/org/example/gather_back_end/promotion/dto/cost/PromotionCostRes.java rename src/main/java/org/example/gather_back_end/promotion/dto/{PromotionReq.java => timeline/PromotionTimelineReq.java} (91%) rename src/main/java/org/example/gather_back_end/promotion/dto/{PromotionRes.java => timeline/PromotionTimelineRes.java} (74%) rename src/main/java/org/example/gather_back_end/promotion/dto/{ => timeline}/Task.java (88%) create mode 100644 src/main/java/org/example/gather_back_end/util/constant/CostPrompt.java delete mode 100644 src/main/java/org/example/gather_back_end/util/constant/OpenAiPrompt.java create mode 100644 src/main/java/org/example/gather_back_end/util/constant/TimelinePrompt.java diff --git a/src/main/java/org/example/gather_back_end/openai/controller/OpenAiController.java b/src/main/java/org/example/gather_back_end/openai/controller/OpenAiController.java index e8e4a2a..a8b5680 100644 --- a/src/main/java/org/example/gather_back_end/openai/controller/OpenAiController.java +++ b/src/main/java/org/example/gather_back_end/openai/controller/OpenAiController.java @@ -1,12 +1,9 @@ package org.example.gather_back_end.openai.controller; import lombok.RequiredArgsConstructor; -import org.example.gather_back_end.openai.dto.CustomOpenAiClientRequest; import org.example.gather_back_end.openai.dto.CustomOpenAiClientResponse; import org.example.gather_back_end.openai.service.OpenAiService; -import org.example.gather_back_end.promotion.dto.PromotionReq; -import org.example.gather_back_end.test.exception.TestNotFoundException; -import org.example.gather_back_end.util.jwt.dto.CustomOAuth2User; +import org.example.gather_back_end.promotion.dto.timeline.PromotionTimelineReq; import org.example.gather_back_end.util.response.SuccessResponse; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.PostMapping; @@ -24,14 +21,14 @@ public class OpenAiController implements OpenAiControllerApi { @PostMapping("/test") public SuccessResponse requestToOpenAi( // Authentication authentication, - @RequestBody PromotionReq request + @RequestBody PromotionTimelineReq request ) { // CustomOAuth2User customOAuth2User = (CustomOAuth2User) authentication.getPrincipal(); // if (customOAuth2User == null) { // return null; // } - CustomOpenAiClientResponse response = openAiService.getOpenAiResponse(request); + CustomOpenAiClientResponse response = openAiService.getAboutTimelineOpenAiResponse(request); return SuccessResponse.of(response); } diff --git a/src/main/java/org/example/gather_back_end/openai/controller/OpenAiControllerApi.java b/src/main/java/org/example/gather_back_end/openai/controller/OpenAiControllerApi.java index 827e06d..3529a63 100644 --- a/src/main/java/org/example/gather_back_end/openai/controller/OpenAiControllerApi.java +++ b/src/main/java/org/example/gather_back_end/openai/controller/OpenAiControllerApi.java @@ -2,11 +2,9 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import org.example.gather_back_end.openai.dto.CustomOpenAiClientRequest; import org.example.gather_back_end.openai.dto.CustomOpenAiClientResponse; -import org.example.gather_back_end.promotion.dto.PromotionReq; +import org.example.gather_back_end.promotion.dto.timeline.PromotionTimelineReq; import org.example.gather_back_end.util.response.SuccessResponse; -import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -17,7 +15,7 @@ public interface OpenAiControllerApi { @PostMapping SuccessResponse requestToOpenAi( // Authentication authentication, - @RequestBody PromotionReq request + @RequestBody PromotionTimelineReq request ); diff --git a/src/main/java/org/example/gather_back_end/openai/service/OpenAiService.java b/src/main/java/org/example/gather_back_end/openai/service/OpenAiService.java index 4e090b9..33a0dc5 100644 --- a/src/main/java/org/example/gather_back_end/openai/service/OpenAiService.java +++ b/src/main/java/org/example/gather_back_end/openai/service/OpenAiService.java @@ -4,8 +4,10 @@ import org.example.gather_back_end.openai.client.OpenAiClient; import org.example.gather_back_end.openai.dto.CustomOpenAiClientRequest; import org.example.gather_back_end.openai.dto.CustomOpenAiClientResponse; -import org.example.gather_back_end.promotion.dto.PromotionReq; -import org.example.gather_back_end.util.constant.OpenAiPrompt; +import org.example.gather_back_end.promotion.dto.cost.PromotionCostReq; +import org.example.gather_back_end.promotion.dto.timeline.PromotionTimelineReq; +import org.example.gather_back_end.util.constant.CostPrompt; +import org.example.gather_back_end.util.constant.TimelinePrompt; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -18,19 +20,37 @@ public class OpenAiService { @Value("${spring.openai.api-key}") private String openAiApiKey; - public CustomOpenAiClientResponse getOpenAiResponse(PromotionReq req) { + public CustomOpenAiClientResponse getAboutTimelineOpenAiResponse(PromotionTimelineReq req) { String authHeader = "Bearer " + openAiApiKey; CustomOpenAiClientRequest realRequestDto = CustomOpenAiClientRequest.builder() .model("gpt-4o-mini") - .addMessage("system", OpenAiPrompt.SYSTEM_PROMPT) + .addMessage("system", TimelinePrompt.SYSTEM_PROMPT) // .addMessage("user", OpenAiPrompt.USER1) // ex1 - req // .addMessage("assistant", OpenAiPrompt.ASSISTANT1) // ex1 - res - .addMessage("user", OpenAiPrompt.USER2) // ex2 - req - .addMessage("assistant", OpenAiPrompt.ASSISTANT2) // ex2 - res - .addMessage("user", OpenAiPrompt.USER3) // ex3 - req - .addMessage("assistant", OpenAiPrompt.ASSISTANT3) // ex3 - res - .addMessage("user", req.toString() + " " + OpenAiPrompt.FINAL_REQUEST_PROMPT) // 최종 요청 문구 + .addMessage("user", TimelinePrompt.USER2) // ex2 - req + .addMessage("assistant", TimelinePrompt.ASSISTANT2) // ex2 - res + .addMessage("user", TimelinePrompt.USER3) // ex3 - req + .addMessage("assistant", TimelinePrompt.ASSISTANT3) // ex3 - res + .addMessage("user", req.toString() + " " + TimelinePrompt.FINAL_REQUEST_PROMPT) // 최종 요청 문구 + .build(); + + return openAiClient.postCustomOpenAiClientResponse(authHeader, realRequestDto); + } + + public CustomOpenAiClientResponse getAboutCostOpenAiResponse(PromotionCostReq req) { + String authHeader = "Bearer " + openAiApiKey; + + CustomOpenAiClientRequest realRequestDto = CustomOpenAiClientRequest.builder() + .model("gpt-4o-mini") + .addMessage("system", CostPrompt.SYSTEM_PROMPT) +// .addMessage("user", OpenAiPrompt.USER1) // ex1 - req +// .addMessage("assistant", OpenAiPrompt.ASSISTANT1) // ex1 - res + .addMessage("user", CostPrompt.USER2) // ex2 - req + .addMessage("assistant", CostPrompt.ASSISTANT2) // ex2 - res + .addMessage("user", CostPrompt.USER3) // ex3 - req + .addMessage("assistant", CostPrompt.ASSISTANT3) // ex3 - res + .addMessage("user", req.toString() + " " + CostPrompt.FINAL_REQUEST_PROMPT) // 최종 요청 문구 .build(); return openAiClient.postCustomOpenAiClientResponse(authHeader, realRequestDto); diff --git a/src/main/java/org/example/gather_back_end/promotion/controller/PromotionController.java b/src/main/java/org/example/gather_back_end/promotion/controller/PromotionController.java index c534fd7..ad36ee2 100644 --- a/src/main/java/org/example/gather_back_end/promotion/controller/PromotionController.java +++ b/src/main/java/org/example/gather_back_end/promotion/controller/PromotionController.java @@ -3,12 +3,12 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.example.gather_back_end.promotion.dto.PromotionReq; -import org.example.gather_back_end.promotion.dto.PromotionRes; +import org.example.gather_back_end.promotion.dto.cost.PromotionCostReq; +import org.example.gather_back_end.promotion.dto.cost.PromotionCostRes; +import org.example.gather_back_end.promotion.dto.timeline.PromotionTimelineReq; +import org.example.gather_back_end.promotion.dto.timeline.PromotionTimelineRes; import org.example.gather_back_end.promotion.service.PromotionService; -import org.example.gather_back_end.util.jwt.dto.CustomOAuth2User; import org.example.gather_back_end.util.response.SuccessResponse; -import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -23,15 +23,21 @@ public class PromotionController implements PromotionControllerApi { private final PromotionService promotionService; @PostMapping("/timeline") - public SuccessResponse> createPromotionStrategy( + public SuccessResponse> createPromotionTimeline( // TODO: 로그인 완성되면 Authentication 추가하기 // TODO: DB에 req 정보 저장하기 // Authentication authentication, - @RequestBody PromotionReq req) { + @RequestBody PromotionTimelineReq req) { // CustomOAuth2User user = (CustomOAuth2User) authentication.getPrincipal(); // String providerId = user.getUsername(); // List res = promotionService.createPromotionStrategy(req, providerId); - List res = promotionService.createPromotionStrategy(req); + List res = promotionService.createPromotionStrategy(req); + return SuccessResponse.of(res); + } + + @PostMapping("/cost-management") + public SuccessResponse> createPromotionCost(@RequestBody PromotionCostReq req) { + List res = promotionService.createPromotionCost(req); return SuccessResponse.of(res); } } diff --git a/src/main/java/org/example/gather_back_end/promotion/controller/PromotionControllerApi.java b/src/main/java/org/example/gather_back_end/promotion/controller/PromotionControllerApi.java index fa53b19..1457b8e 100644 --- a/src/main/java/org/example/gather_back_end/promotion/controller/PromotionControllerApi.java +++ b/src/main/java/org/example/gather_back_end/promotion/controller/PromotionControllerApi.java @@ -8,8 +8,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; -import org.example.gather_back_end.promotion.dto.PromotionReq; -import org.example.gather_back_end.promotion.dto.PromotionRes; +import org.example.gather_back_end.promotion.dto.cost.PromotionCostReq; +import org.example.gather_back_end.promotion.dto.cost.PromotionCostRes; +import org.example.gather_back_end.promotion.dto.timeline.PromotionTimelineReq; +import org.example.gather_back_end.promotion.dto.timeline.PromotionTimelineRes; import org.example.gather_back_end.util.response.SuccessResponse; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -19,87 +21,53 @@ public interface PromotionControllerApi { @Operation(summary = "타임라인 정보 생성") @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "호출 성공", - content = @Content( - mediaType = "application/json", - examples = @ExampleObject(value = "{\n" - + " \"timestamp\": \"2024-11-11T17:30:37.097401\",\n" - + " \"isSuccess\": true,\n" - + " \"code\": \"200\",\n" - + " \"message\": \"호출에 성공하였습니다.\",\n" - + " \"data\": [\n" - + " {\n" - + " \"period\": 43,\n" - + " \"category\": \"PRINTS\",\n" - + " \"tasks\": [\n" - + " {\n" - + " \"name\": \"기획\",\n" - + " \"start\": 0,\n" - + " \"end\": 2,\n" - + " \"tip\": \"단체의 매력을 정리해 소통을 준비하고, 크리에이터와 함께 아이디어를 실현하세요!\"\n" - + " },\n" - + " {\n" - + " \"name\": \"디자인\",\n" - + " \"start\": 2,\n" - + " \"end\": 10,\n" - + " \"tip\": \"단체의 색깔과 캐릭터를 정하고, 원하는 내용을 간결하게 정리해 디자이너와 원활히 소통하세요!\"\n" - + " },\n" - + " {\n" - + " \"name\": \"출력\",\n" - + " \"start\": 10,\n" - + " \"end\": 11,\n" - + " \"tip\": \"시간적 여유가 있다면 Bizhows로, 급하다면 학교나 인쇄소에서 사이즈를 미리 확인 후 인쇄하세요!\"\n" - + " },\n" - + " {\n" - + " \"name\": \"게시요청_캠퍼스픽\",\n" - + " \"start\": 11,\n" - + " \"end\": 12,\n" - + " \"tip\": \"접근성 높은 캠퍼스픽에 포스터를 준비해 효과적으로 홍보하세요!\"\n" - + " },\n" - + " {\n" - + " \"name\": \"승인기간_캠퍼스픽\",\n" - + " \"start\": 12,\n" - + " \"end\": 14,\n" - + " \"tip\": \"관리자의 승인을 받기까지 최대 48시간이 소요됩니다!\"\n" - + " },\n" - + " {\n" - + " \"name\": \"최종게시_캠퍼스픽\",\n" - + " \"start\": 14,\n" - + " \"end\": 43,\n" - + " \"tip\": \"캠퍼스픽에서 대학생들의 문의를 놓치지 않도록 매일 확인하세요!\"\n" - + " },\n" - + " {\n" - + " \"name\": \"게시_요즘것들\",\n" - + " \"start\": 11,\n" - + " \"end\": 43,\n" - + " \"tip\": \"대학생과 청년을 위한 다양한 활동 정보를 제공하는 요즘것들에 꼭 홍보하세요!\"\n" - + " },\n" - + " {\n" - + " \"name\": \"게시_에브리타임\",\n" - + " \"start\": 11,\n" - + " \"end\": 43,\n" - + " \"tip\": \"대학생 대상 홍보엔 에브리타임이 최적! 3일 주기로 원하는 학과 게시판에 꾸준히 올려보세요!\"\n" - + " },\n" - + " {\n" - + " \"name\": \"게시_인스타그램\",\n" - + " \"start\": 11,\n" - + " \"end\": 43,\n" - + " \"tip\": \"20대를 타겟으로 인스타그램 유행을 활용해 효율적으로 홍보하세요!\"\n" - + " }\n" - + " ]\n" - + " }\n" - + " ]\n" - + "}"), - schema = @Schema(implementation = SuccessResponse.class) - ) - ) - - }) + @ApiResponse(responseCode = "200", description = "호출 성공", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = + "{\n" + " \"timestamp\": \"2024-11-11T17:30:37.097401\",\n" + " \"isSuccess\": true,\n" + + " \"code\": \"200\",\n" + " \"message\": \"호출에 성공하였습니다.\",\n" + " \"data\": [\n" + + " {\n" + " \"period\": 43,\n" + " \"category\": \"PRINTS\",\n" + + " \"tasks\": [\n" + " {\n" + " \"name\": \"기획\",\n" + + " \"start\": 0,\n" + " \"end\": 2,\n" + + " \"tip\": \"단체의 매력을 정리해 소통을 준비하고, 크리에이터와 함께 아이디어를 실현하세요!\"\n" + + " },\n" + " {\n" + " \"name\": \"디자인\",\n" + + " \"start\": 2,\n" + " \"end\": 10,\n" + + " \"tip\": \"단체의 색깔과 캐릭터를 정하고, 원하는 내용을 간결하게 정리해 디자이너와 원활히 소통하세요!\"\n" + + " },\n" + " {\n" + " \"name\": \"출력\",\n" + + " \"start\": 10,\n" + " \"end\": 11,\n" + + " \"tip\": \"시간적 여유가 있다면 Bizhows로, 급하다면 학교나 인쇄소에서 사이즈를 미리 확인 후 인쇄하세요!\"\n" + + " },\n" + " {\n" + " \"name\": \"게시요청_캠퍼스픽\",\n" + + " \"start\": 11,\n" + " \"end\": 12,\n" + + " \"tip\": \"접근성 높은 캠퍼스픽에 포스터를 준비해 효과적으로 홍보하세요!\"\n" + " },\n" + + " {\n" + " \"name\": \"승인기간_캠퍼스픽\",\n" + + " \"start\": 12,\n" + " \"end\": 14,\n" + + " \"tip\": \"관리자의 승인을 받기까지 최대 48시간이 소요됩니다!\"\n" + " },\n" + + " {\n" + " \"name\": \"최종게시_캠퍼스픽\",\n" + + " \"start\": 14,\n" + " \"end\": 43,\n" + + " \"tip\": \"캠퍼스픽에서 대학생들의 문의를 놓치지 않도록 매일 확인하세요!\"\n" + " },\n" + + " {\n" + " \"name\": \"게시_요즘것들\",\n" + + " \"start\": 11,\n" + " \"end\": 43,\n" + + " \"tip\": \"대학생과 청년을 위한 다양한 활동 정보를 제공하는 요즘것들에 꼭 홍보하세요!\"\n" + + " },\n" + " {\n" + " \"name\": \"게시_에브리타임\",\n" + + " \"start\": 11,\n" + " \"end\": 43,\n" + + " \"tip\": \"대학생 대상 홍보엔 에브리타임이 최적! 3일 주기로 원하는 학과 게시판에 꾸준히 올려보세요!\"\n" + + " },\n" + " {\n" + " \"name\": \"게시_인스타그램\",\n" + + " \"start\": 11,\n" + " \"end\": 43,\n" + + " \"tip\": \"20대를 타겟으로 인스타그램 유행을 활용해 효율적으로 홍보하세요!\"\n" + " }\n" + + " ]\n" + " }\n" + " ]\n" + + "}"), schema = @Schema(implementation = SuccessResponse.class)))}) + @PostMapping + SuccessResponse> createPromotionTimeline(@RequestBody PromotionTimelineReq req); + @Operation(summary = "비용 관리") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "호출 성공", content = @Content(mediaType = "application/json", examples = @ExampleObject(value = + "{\n" + " \"timestamp\": \"2024-11-12T02:18:30.381308\",\n" + " \"isSuccess\": true,\n" + + " \"code\": \"200\",\n" + " \"message\": \"호출에 성공하였습니다.\",\n" + " \"data\": [\n" + + " {\n" + " \"means\": \"인쇄물\",\n" + " \"cost\": \"452,200\",\n" + + " \"rate\": 70\n" + " },\n" + " {\n" + " \"means\": \"영상\",\n" + + " \"cost\": \"129,200\",\n" + " \"rate\": 20\n" + " },\n" + + " {\n" + " \"means\": \"SNS 게시물\",\n" + " \"cost\": \"64,600\",\n" + + " \"rate\": 10\n" + " }\n" + " ]\n" + + "}"), schema = @Schema(implementation = SuccessResponse.class)))}) @PostMapping - SuccessResponse> createPromotionStrategy( - @RequestBody PromotionReq req - ); + SuccessResponse> createPromotionCost(@RequestBody PromotionCostReq req); } diff --git a/src/main/java/org/example/gather_back_end/promotion/dto/cost/PromotionCostReq.java b/src/main/java/org/example/gather_back_end/promotion/dto/cost/PromotionCostReq.java new file mode 100644 index 0000000..267a1fb --- /dev/null +++ b/src/main/java/org/example/gather_back_end/promotion/dto/cost/PromotionCostReq.java @@ -0,0 +1,21 @@ +package org.example.gather_back_end.promotion.dto.cost; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record PromotionCostReq( + @Schema(description = "사용자가 입력한 예산", example = "646000") + Integer budget, + + @Schema(description = "1순위 홍보 수단 (필수)", example = "PRINTS") + String firstMeans, + + @Schema(description = "2순위 홍보 수단 (null 가능)", example = "VIDEO") + String secondMeans, + + @Schema(description = "3순위 홍보 수단 (null 가능)", example = "SNS_POST") + String thirdMeans, + + @Schema(description = "인스타그램 홍보 기간", example = "20") + Integer instagramPromotionPeriod +) { +} diff --git a/src/main/java/org/example/gather_back_end/promotion/dto/cost/PromotionCostRes.java b/src/main/java/org/example/gather_back_end/promotion/dto/cost/PromotionCostRes.java new file mode 100644 index 0000000..ad689cd --- /dev/null +++ b/src/main/java/org/example/gather_back_end/promotion/dto/cost/PromotionCostRes.java @@ -0,0 +1,15 @@ +package org.example.gather_back_end.promotion.dto.cost; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record PromotionCostRes( + @Schema(description = "홍보 수단", example = "인쇄물") + String means, + + @Schema(description = "소모 비용", example = "452,200") + String cost, + + @Schema(description = "비용 비율", example = "70") + Integer rate +) { +} diff --git a/src/main/java/org/example/gather_back_end/promotion/dto/PromotionReq.java b/src/main/java/org/example/gather_back_end/promotion/dto/timeline/PromotionTimelineReq.java similarity index 91% rename from src/main/java/org/example/gather_back_end/promotion/dto/PromotionReq.java rename to src/main/java/org/example/gather_back_end/promotion/dto/timeline/PromotionTimelineReq.java index 5afe485..cc3ac69 100644 --- a/src/main/java/org/example/gather_back_end/promotion/dto/PromotionReq.java +++ b/src/main/java/org/example/gather_back_end/promotion/dto/timeline/PromotionTimelineReq.java @@ -1,11 +1,11 @@ -package org.example.gather_back_end.promotion.dto; +package org.example.gather_back_end.promotion.dto.timeline; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.annotation.Nullable; import jakarta.validation.constraints.NotNull; import org.example.gather_back_end.domain.WorkType; -public record PromotionReq( +public record PromotionTimelineReq( @Schema(description = "홍보 제목", example = "OOO 동아리 모집") String title, diff --git a/src/main/java/org/example/gather_back_end/promotion/dto/PromotionRes.java b/src/main/java/org/example/gather_back_end/promotion/dto/timeline/PromotionTimelineRes.java similarity index 74% rename from src/main/java/org/example/gather_back_end/promotion/dto/PromotionRes.java rename to src/main/java/org/example/gather_back_end/promotion/dto/timeline/PromotionTimelineRes.java index 792824f..f5db42d 100644 --- a/src/main/java/org/example/gather_back_end/promotion/dto/PromotionRes.java +++ b/src/main/java/org/example/gather_back_end/promotion/dto/timeline/PromotionTimelineRes.java @@ -1,9 +1,9 @@ -package org.example.gather_back_end.promotion.dto; +package org.example.gather_back_end.promotion.dto.timeline; import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; -public record PromotionRes( +public record PromotionTimelineRes( @Schema(description = "총 기간", example = "43") int period, @Schema(description = "홍보 수단", example = "PRINTS") diff --git a/src/main/java/org/example/gather_back_end/promotion/dto/Task.java b/src/main/java/org/example/gather_back_end/promotion/dto/timeline/Task.java similarity index 88% rename from src/main/java/org/example/gather_back_end/promotion/dto/Task.java rename to src/main/java/org/example/gather_back_end/promotion/dto/timeline/Task.java index 01c1b36..1a79c76 100644 --- a/src/main/java/org/example/gather_back_end/promotion/dto/Task.java +++ b/src/main/java/org/example/gather_back_end/promotion/dto/timeline/Task.java @@ -1,4 +1,4 @@ -package org.example.gather_back_end.promotion.dto; +package org.example.gather_back_end.promotion.dto.timeline; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/org/example/gather_back_end/promotion/service/PromotionService.java b/src/main/java/org/example/gather_back_end/promotion/service/PromotionService.java index 8ea5996..840ba69 100644 --- a/src/main/java/org/example/gather_back_end/promotion/service/PromotionService.java +++ b/src/main/java/org/example/gather_back_end/promotion/service/PromotionService.java @@ -1,11 +1,17 @@ package org.example.gather_back_end.promotion.service; import java.util.List; -import org.example.gather_back_end.promotion.dto.PromotionReq; -import org.example.gather_back_end.promotion.dto.PromotionRes; +import org.example.gather_back_end.promotion.dto.cost.PromotionCostReq; +import org.example.gather_back_end.promotion.dto.cost.PromotionCostRes; +import org.example.gather_back_end.promotion.dto.timeline.PromotionTimelineReq; +import org.example.gather_back_end.promotion.dto.timeline.PromotionTimelineRes; +import org.example.gather_back_end.util.response.SuccessResponse; +import org.springframework.web.bind.annotation.RequestBody; public interface PromotionService { - List createPromotionStrategy (PromotionReq req); + List createPromotionStrategy (PromotionTimelineReq req); // List createPromotionStrategy (PromotionReq req, String providerId); + + List createPromotionCost(PromotionCostReq req); } diff --git a/src/main/java/org/example/gather_back_end/promotion/service/PromotionServiceImpl.java b/src/main/java/org/example/gather_back_end/promotion/service/PromotionServiceImpl.java index 482e8e6..e4436ae 100644 --- a/src/main/java/org/example/gather_back_end/promotion/service/PromotionServiceImpl.java +++ b/src/main/java/org/example/gather_back_end/promotion/service/PromotionServiceImpl.java @@ -5,11 +5,11 @@ import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.example.gather_back_end.domain.User; -import org.example.gather_back_end.openai.dto.CustomOpenAiClientResponse; import org.example.gather_back_end.openai.service.OpenAiService; -import org.example.gather_back_end.promotion.dto.PromotionReq; -import org.example.gather_back_end.promotion.dto.PromotionRes; +import org.example.gather_back_end.promotion.dto.cost.PromotionCostReq; +import org.example.gather_back_end.promotion.dto.cost.PromotionCostRes; +import org.example.gather_back_end.promotion.dto.timeline.PromotionTimelineReq; +import org.example.gather_back_end.promotion.dto.timeline.PromotionTimelineRes; import org.example.gather_back_end.repository.UserRepository; import org.springframework.stereotype.Service; @@ -24,22 +24,40 @@ public class PromotionServiceImpl implements PromotionService { @Override // TODO: 데이터 저장 로직 추가 // public List createPromotionStrategy(PromotionReq req, String providerId) { - public List createPromotionStrategy(PromotionReq req) { + public List createPromotionStrategy(PromotionTimelineReq req) { // User user = userRepository.getByUsername(providerId); - String result = openAiService.getOpenAiResponse(req).getContent(); - return parseContentToPromotionRes(result); + String result = openAiService.getAboutTimelineOpenAiResponse(req).getContent(); + return parseContentToTimelineRes(result); + } + + @Override + public List createPromotionCost(PromotionCostReq req) { + String result = openAiService.getAboutCostOpenAiResponse(req).getContent(); + return parseContentToCostRes(result); } // Open AI로부터 받은 응답을 파싱 - private List parseContentToPromotionRes(String content) { + private List parseContentToTimelineRes(String content) { + ObjectMapper objectMapper = new ObjectMapper(); + + try { + // JSON 배열 문자열을 PromotionRes 리스트로 변환 + return objectMapper.readValue(content, objectMapper.getTypeFactory().constructCollectionType(List.class, PromotionTimelineRes.class)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + throw new RuntimeException("[타임라인 생성] Open Ai 응답 메시지 파싱 실패 : " + content, e); + } + } + + private List parseContentToCostRes(String content) { ObjectMapper objectMapper = new ObjectMapper(); try { // JSON 배열 문자열을 PromotionRes 리스트로 변환 - return objectMapper.readValue(content, objectMapper.getTypeFactory().constructCollectionType(List.class, PromotionRes.class)); + return objectMapper.readValue(content, objectMapper.getTypeFactory().constructCollectionType(List.class, PromotionCostRes.class)); } catch (JsonProcessingException e) { e.printStackTrace(); - throw new RuntimeException("Open Ai 응답 메시지 파싱 실패 : " + content, e); + throw new RuntimeException("[비용 관리] Open Ai 응답 메시지 파싱 실패 : " + content, e); } } } diff --git a/src/main/java/org/example/gather_back_end/util/constant/CostPrompt.java b/src/main/java/org/example/gather_back_end/util/constant/CostPrompt.java new file mode 100644 index 0000000..b79a419 --- /dev/null +++ b/src/main/java/org/example/gather_back_end/util/constant/CostPrompt.java @@ -0,0 +1,133 @@ +package org.example.gather_back_end.util.constant; + +/** + * 홍보 비용 관련 프롬프트 + */ +public class CostPrompt { + + public static final String SYSTEM_PROMPT = """ + 당신은 홍보 전략을 생성해주는 컨설턴트입니다. 최종 목적은 홍보 비용 관리를 위한 JSON 데이터를 만드는 것입니다. 아래와 같은 JSON 형식으로 홍보 전략을 생성해 달라고 요청이 올 것입니다. + + { + "budget": 646000, + "firstMeans": "PRINTS", + "secondMeans": "VIDEO", + "thirdMeans": "SNS_POST", + "instagramPromotionPeriod" : 20 + } + + 각 필드에 대한 설명을 해주겠습니다. + + budget: 보유한 총 예산 + firstMeans: 원하는 홍보 수단1 + secondMeans: 원하는 홍보 수단2 + thirdMeans: 원하는 홍보 수단3 + instagramPromotionPeriod: 인스타그램 홍보 기간 + + (홍보 수단 종류는 다음 세 가지로 고정되어 있습니다. 인쇄물, 영상, SNS 게시물) + + PRINTS // 인쇄물 (포스터, 배너 등) + VIDEO // 영상 (숏폼, 미드폼, 롱폼 등) + SNS_POST // SNS 게시물 (카드뉴스, 피드 등) + + 홍보 수단은 무조건 1개 이상 작성되어 있습니다. + + 그럼 아래와 같은 json 데이터 타입으로 응답을 해줘야 합니다. + + firstMeans에 대한 응답 오브젝트가 첫 번째, secondMeans에 대한 응답 오브젝트가 두 번째, thirdMeans에 대한 응답 오브젝트가 세 번째로 와야 합니다. + + - SNS_POST인 경우 means 필드는 "SNS 게시물" 이어야 합니다. + - VIDEO인 경우 means 필드는 "영상" 이어야 합니다. + - PRINTS인 경우 means 필드는 "인쇄물" 이어야 합니다. + + - 핵심적으로 봐줘야 할 것은 cost와 rate를 구하는 부분입니다. + \s + cost 구하는 방법 + 1. 사용 가능한 비용 = (Request로 받은 budget 필드) - (5000 * instagramPromotionPeriod) 입니다. 예시를 들면, 현재 Request에서 budget이 646000이므로 사용 가능한 금액 = (646000) - (5000 * 10) = 546000 이 됩니다. + 2-1. firstMeans에만 값이 있는 경우, 그 값을 100% 사용하도록 측정, 즉, cost 필드는 1.에서 구한 "사용 가능한 비용" 그대로 사용합니다. 예시를 들면, 첫 번째 오브젝트의 cost는 "646,000"이 됩니다. + 2-2. firstMeans와 secondMeans에만 값이 있는 경우, firstMeans는 1번에서 구한 "사용 가능한 비용"의 70%, secondMeans는 "사용 가능한 비용"의 30%를 사용하도록 측정합니다. 예시를 들면, 첫 번째 오브젝트의 cost는 "452,200", 두 번째 오브젝트의 cost는 "193,800" 입니다. + 2-3. firstMeans, secondMeans, thirdMeans 모두에 값이 있는 경우 firstMeans는 1번에서 구한 "사용 가능한 비용"의 70%, secondMeans는 "사용 가능한 비용"의 20%, thirdMeans는 "사용 가능한 비용"의 10%를 사용하도록 측정합니다. 예시를 들면, 첫 번째 오브젝트의 cost는 "452,200", 두 번째 오브젝트의 cost는 "129,200", 세 번째 오브젝트의 cost는 "64,600" 입니다.\s + + rate 구하는 방법 + 1. 각 오브젝트의 cost를 모두 더합니다. + 2. 100분율로 계산했을 때의 % 수치를 넣습니다. + + 응답 형식에서 주의사항은 두 가지 입니다. + 1. cost에서 천 원 단위로 쉼표를 붙여주세요. + 2. rate에서 %는 제외하고 숫자만 들어가게 해주세요. + + [ + { + "means": "인쇄물", + "cost": "452,200", + "rate": 70 + }, + { + "means": "영상", + "cost": "129,200", + "rate": 20 + }, + { + "means": "SNS 게시물", + "cost": "64,600", + "rate": 10 + } + ] + + """; + + public static final String USER1 = """ + """; + public static final String ASSISTANT1 = """ + """; + // 더미데이터 22번 + public static final String USER2 = """ + { + "budget": 167000, + "firstMeans": "VIDEO", + "secondMeans": "SNS_POST", + "thirdMeans": null, + "instagramPromotionPeriod" : 7 + } + """; + public static final String ASSISTANT2 = """ + [ + { + "means": "영상", + "cost": "92,400", + "rate": 70 + }, + { + "means": "SNS 게시물", + "cost": "39,600", + "rate": 30 + } + ] + """; + + // 더미데이터 23번 + public static final String USER3 = """ + { + "budget": 157000, + "firstMeans": "PRINTS", + "secondMeans": "SNS_POST", + "thirdMeans": null, + "instagramPromotionPeriod" : 10 + } + """; + public static final String ASSISTANT3 = """ + [ + { + "means": "인쇄물", + "cost": "74,900", + "rate": 70 + }, + { + "means": "SNS 게시물", + "cost": "32,100", + "rate": 30 + } + ] + """; + public static final String FINAL_REQUEST_PROMPT = "json 형태로 결과 데이터만 주세요."; +} diff --git a/src/main/java/org/example/gather_back_end/util/constant/OpenAiPrompt.java b/src/main/java/org/example/gather_back_end/util/constant/OpenAiPrompt.java deleted file mode 100644 index db35080..0000000 --- a/src/main/java/org/example/gather_back_end/util/constant/OpenAiPrompt.java +++ /dev/null @@ -1,314 +0,0 @@ -package org.example.gather_back_end.util.constant; - -public class OpenAiPrompt { - - public static final String SYSTEM_PROMPT = """ - 당신은 홍보 전략을 생성해주는 컨설턴트입니다. 최종 목적은 타임라인 구성을 위한 JSON 데이터를 만드는 것입니다. 아래와 같은 JSON 형식으로 홍보 전략을 생성해 달라고 요청이 올 것입니다. - - { - "title": "OOO 동아리 모집", - "period": 55, - "targetNumberOfPeople": 392, - "budget": 646000, - "firstMeans": "PRINTS", - "secondMeans": "VIDEO", - "thirdMeans": "SNS_POSTS" - } - - 각 필드에 대한 설명을 해주겠습니다. - - title: 홍보 제목 - period: 예상 모집 기간 - targetNumberOfPeople: 목표 인원(이 값은 결과 데이터에 영향을 미치지 않습니다.) - budget: 보유한 예산 - firstMeans: 원하는 홍보 수단1 - secondMeans: 원하는 홍보 수단2 - thirdMeans: 원하는 홍보 수단3 - - (홍보 수단 종류는 다음 세 가지로 고정되어 있습니다. 인쇄물, 영상, SNS 게시물) - - PRINTS // 인쇄물 (포스터, 배너 등) - VIDEO // 영상 (숏폼, 미드폼, 롱폼 등) - SNS_POST // SNS 게시물 (카드뉴스, 피드 등) - - 홍보 수단은 무조건 1개 이상 작성되어 있습니다. - - 그럼 아래와 같은 json 데이터 타입으로 응답을 해줘야 합니다. - - firstMeans에 대한 응답 오브젝트가 첫 번째, secondMeans에 대한 응답 오브젝트가 두 번째, thirdMeans에 대한 응답 오브젝트가 세 번째로 와야 합니다. - - - SNS_POST인 경우 tasks 배열에 들어가야 하는 name 필드는 "내용 정리 및 컨택", "제작", "게시_인스타그램" 입니다. - - VIDEO인 경우 tasks 배열에 들어가야 하는 name 필드는 "예산 확정/기획", "제작사 선정", "협의", "촬영", "게시_틱톡", "게시_유튜브", "게시_인스타그램" 입니다. - - PRINTS인 경우 tasks 배열에 들어가야 하는 name 필드는 "기획", "디자인", "출력", "게시요청_캠퍼스픽", "승인기간_캠퍼스픽", "최종게시_캠퍼스픽", "게시_요즘것들", "게시_인스타그램", "게시_에브리타임" 입니다. "게시요청_캠퍼스픽" 같은 경우 1일, "승인기간_캠퍼스픽" 같은 경우 2일이 소요됩니다. - - tip 필드는 category와 tasks의 name에 따라 다른 문구가 들어가야 합니다. 상세문구는 너무 길어서 아래에 첨부하겠습니다. - - tip 필드에 대한 상세 정보는 아래에 작성했습니다. - - - - 실제 "tip" 필드에는 "상세문구"에 대한 내용이 들어가야 합니다. - - 아래는 상세문구입니다. - - SNS_POST - 1. 내용 정리 및 컨택 (SNS_POST_TIP_1) : "단체의 키워드와 아이덴티티를 명확히 정리해, 디자이너와 최고의 결과물을 만들어보세요!" - 2. 제작 (SNS_POST_TIP_2) : "담당자를 지정해 크리에이터와 빠르고 예의 있게 소통하세요!" - 3. 게시 (SNS_POST_INSTAGRAM) : "일 5000원 투자로 짧은 기간에 최대 효과로 더 많은 사람들에게 정보를 알리세요!" - - PRINTS - 1. 기획 (PRINTS_TIP_1) : "단체의 매력을 정리해 소통을 준비하고, 크리에이터와 함께 아이디어를 실현하세요!" - 2. 디자인 (PRINTS_TIP_2) : "단체의 색깔과 캐릭터를 정하고, 원하는 내용을 간결하게 정리해 디자이너와 원활히 소통하세요!" - 3. 출력 (PRINTS_TIP_3) : "시간적 여유가 있다면 Bizhows로, 급하다면 학교나 인쇄소에서 사이즈를 미리 확인 후 인쇄하세요!" - 4. 게시 - - 캠퍼스픽 - - 게시요청 단계 (PRINTS_CAMPUSPICK_REQUEST) : "접근성 높은 캠퍼스픽에 포스터를 준비해 효과적으로 홍보하세요!" - - 승인기간 단계 (PRINTS_CAMPUSPICK_APPROVE_PERIOD) : "관리자의 승인을 받기까지 최대 48시간이 소요됩니다!" - - 최종게시 단계 (PRINTS_CAMPUSPICK_FINAL_POST) : "캠퍼스픽에서 대학생들의 문의를 놓치지 않도록 매일 확인하세요!" - - 요즘것들 (PRINTS_ALLFORYOUNG) : "대학생과 청년을 위한 다양한 활동 정보를 제공하는 요즘것들에 꼭 홍보하세요!" - - 에브리타임 (PRINTS_EVERYTIME) : "대학생 대상 홍보엔 에브리타임이 최적! 3일 주기로 원하는 학과 게시판에 꾸준히 올려보세요!" - - 인스타그램 (PRINTS_INSTAGRAM) : "20대를 타겟으로 인스타그램 유행을 활용해 효율적으로 홍보하세요!" - - VIDEO - 1. 예산 확정/기획 (VIDEO_TIP_1) : "단체의 매력을 정리해 소통을 준비하고, 크리에이터와 함께 아이디어를 실현하세요!" - 2. 제작사 선정 (VIDEO_TIP_2) : "꼼꼼히 포트폴리오를 읽고, 다양한 옵션에서 본인과 맞는 디자이너를 찾아보세요!" - 3. 협의 (VIDEO_TIP_3) : "정확한 요구와 예의를 갖춰 소통하고, '투게더에서 보고 연락 드렸어요!'로 시작해보세요!" - 4. 촬영 (VIDEO_TIP_4) : "상호 배려와 사전 확정된 기획으로 원활한 촬영을 준비하세요!" - 5. 게시 - - 틱톡 (VIDEO_TIKTOK) : "트렌디한 틱톡으로 바이럴 주인공이 되어, 젊은 층을 사로잡아보세요!" - - 유튜브 (VIDEO_YOUTUBE) : "700억 조회수의 유튜브 쇼츠로 짧고 강렬하게 단체를 홍보하세요!" - - 인스타그램 (VIDEO_INSTAGRAM) : "일 5000원으로 릴스로 관심을 끌고, 바이럴의 주인공이 되어보세요!" - - - [ - { - "period": 55, - "category": "PRINTS", - "tasks": [ - { "name": "기획", "start": 0, "end": 2, "tip" : "PRINTS_TIP_1" }, - { "name": "디자인", "start": 2, "end": 7, "tip" : "PRINTS_TIP_2" }, - { "name": "출력", "start": 7, "end": 8, "tip" : "PRINTS_TIP_3" }, - { "name": "게시요청_캠퍼스픽", "start": 8, "end": 9, "tip" : "PRINTS_CAMPUSPICK_REQUEST" }, - { "name": "승인기간_캠퍼스픽", "start": 9, "end": 11, "tip" : "PRINTS_CAMPUSPICK_APPROVE_PERIOD" }, - { "name": "최종게시_캠퍼스픽", "start": 11, "end": 55, "tip" : "PRINTS_CAMPUSPICK_FINAL_POST" }, - { "name": "게시_요즘것들", "start": 8, "end": 55, "tip" : "PRINTS_ALLFORYOUNG" }, - { "name": "게시_에브리타임", "start": 8, "end": 55, "tip" : "PRINTS_EVERYTIME" }, - { "name": "게시_인스타그램", "start": 8, "end": 55, "tip" : "PRINTS_INSTAGRAM" } - ] - }, - { - "period": 55, - "category": "VIDEO", - "tasks": [ - { "name": "예산 확정/기획", "start": 0, "end": 1, "tip" : "VIDEO_TIP_1" }, - { "name": "제작사 선정", "start": 1, "end": 3, "tip" : "VIDEO_TIP_2" }, - { "name": "협의", "start": 3, "end": 5, "tip" : "VIDEO_TIP_3" }, - { "name": "촬영", "start": 5, "end": 19, "tip" : "VIDEO_TIP_4" }, - { "name": "게시_틱톡", "start": 19, "end": 55, "tip" : "VIDEO_TIKTOK" }, - { "name": "게시_유튜브", "start": 19, "end": 55, "tip" : "VIDEO_YOUTUBE" }, - { "name": "게시_인스타그램", "start": 19, "end": 55, "tip" : "VIDEO_INSTAGRAM" } - ] - }, - { - "period": 55, - "category": "SNS_POST", - "tasks": [ - { "name": "내용 정리 및 컨택", "start": 0, "end": 2, "tip" : "SNS_POST_TIP_1" }, - { "name": "제작", "start": 2, "end": 8, "tip" : "SNS_POST_TIP_2" }, - { "name": "게시_인스타그램", "start": 8, "end": 55, "tip" : "SNS_POST_INSTAGRAM" } - ] - } - ] - """; - - public static final String USER1 = """ - """; - public static final String ASSISTANT1 = """ - """; - // 더미데이터 22번 - public static final String USER2 = """ - { - "title": "4호선톤 서포터즈 구함", - "period": 76, - "targetNumberOfPeople": 154, - "budget": 167000, - "firstMeans": "VIDEO", - "secondMeans": "SNS_POST", - "thirdMEans": null - } - """; - public static final String ASSISTANT2 = """ - [ - { - "period":76, - "category":"VIDEO", - "tasks":[ - { - "name":"예산 확정/기획", - "start":0, - "end":1, - "tip":"단체의 매력을 정리해 소통을 준비하고, 크리에이터와 함께 아이디어를 실현하세요!" - }, - { - "name":"제작사 선정", - "start":1, - "end":2, - "tip":"꼼꼼히 포트폴리오를 읽고, 다양한 옵션에서 본인과 맞는 디자이너를 찾아보세요!" - }, - { - "name":"협의", - "start":2, - "end":4, - "tip":"정확한 요구와 예의를 갖춰 소통하고, '투게더에서 보고 연락 드렸어요!'로 시작해보세요!" - }, - { - "name":"촬영", - "start":4, - "end":11, - "tip":"상호 배려와 사전 확정된 기획으로 원활한 촬영을 준비하세요!" - }, - { - "name":"게시_틱톡", - "start":11, - "end":76, - "tip":"트렌디한 틱톡으로 바이럴 주인공이 되어, 젊은 층을 사로잡아보세요!" - }, - { - "name":"게시_유튜브", - "start":11, - "end":76, - "tip":"700억 조회수의 유튜브 쇼츠로 짧고 강렬하게 단체를 홍보하세요!" - }, - { - "name":"게시_인스타", - "start":11, - "end":76, - "tip":"일 5000원으로 릴스로 관심을 끌고, 바이럴의 주인공이 되어보세요!" - } - ] - }, - { - "period":76, - "category":"SNS_POST", - "tasks":[ - { - "name":"내용 정리 및 컨택", - "start":0, - "end":3, - "tip":"단체의 키워드와 아이덴티티를 명확히 정리해, 디자이너와 최고의 결과물을 만들어보세요!" - }, - { - "name":"제작", - "start":3, - "end":10, - "tip":"담당자를 지정해 크리에이터와 빠르고 예의 있게 소통하세요!" - }, - { - "name":"게시_인스타그램", - "start":10, - "end":76, - "tip":"일 5000원 투자로 짧은 기간에 최대 효과로 더 많은 사람들에게 정보를 알리세요!" - } - ] - } - ] - """; - - // 더미데이터 23번 - public static final String USER3 = """ - { - "title": "4호선톤 서포터즈 구함", - "period": 48, - "targetNumberOfPeople": 432, - "budget": 157000, - "firstMeans": "PRINTS", - "secondMeans": "SNS_POST", - "thirdMEans": null - } - """; - public static final String ASSISTANT3 = """ - [ - { - "period":48, - "category":"PRINTS", - "tasks":[ - { - "name":"기획", - "start":0, - "end":2, - "tip":"단체의 매력을 정리해 소통을 준비하고, 크리에이터와 함께 아이디어를 실현하세요!" - }, - { - "name":"디자인", - "start":2, - "end":10, - "tip":"단체의 색깔과 캐릭터를 정하고, 원하는 내용을 간결하게 정리해 디자이너와 원활히 소통하세요!" - }, - { - "name":"출력", - "start":10, - "end":11, - "tip":"시간적 여유가 있다면 Bizhows로, 급하다면 학교나 인쇄소에서 사이즈를 미리 확인 후 인쇄하세요!" - }, - { - "name":"게시요청_캠퍼스픽", - "start":11, - "end":12, - "tip":"접근성 높은 캠퍼스픽에 포스터를 준비해 효과적으로 홍보하세요!" - }, - { - "name":"승인기간_캠퍼스픽", - "start":12, - "end":14, - "tip":"관리자의 승인을 받기까지 최대 48시간이 소요됩니다!" - }, - { - "name":"최종게시_캠퍼스픽", - "start":14, - "end":48, - "tip":"캠퍼스픽에서 대학생들의 문의를 놓치지 않도록 매일 확인하세요!" - }, - { - "name":"게시_요즘것들", - "start":11, - "end":48, - "tip":"대학생과 청년을 위한 다양한 활동 정보를 제공하는 요즘것들에 꼭 홍보하세요!" - }, - { - "name":"게시_에브리타임", - "start":11, - "end":48, - "tip":"대학생 대상 홍보엔 에브리타임이 최적! 3일 주기로 원하는 학과 게시판에 꾸준히 올려보세요!" - }, - { - "name":"게시_인스타그램", - "start":11, - "end":48, - "tip":"20대를 타겟으로 인스타그램 유행을 활용해 효율적으로 홍보하세요!" - } - ] - }, - { - "period":48, - "category":"SNS_POST", - "tasks":[ - { - "name":"내용 정리 및 컨택", - "start":0, - "end":2, - "tip":"단체의 키워드와 아이덴티티를 명확히 정리해, 디자이너와 최고의 결과물을 만들어보세요!" - }, - { - "name":"제작", - "start":2, - "end":9, - "tip":"담당자를 지정해 크리에이터와 빠르고 예의 있게 소통하세요!" - }, - { - "name":"게시_인스타그램", - "start":9, - "end":39, - "tip":"일 5000원 투자로 짧은 기간에 최대 효과로 더 많은 사람들에게 정보를 알리세요!" - } - ] - } - ] - """; - public static final String FINAL_REQUEST_PROMPT = "json 형태로 결과 데이터만 주세요."; -} diff --git a/src/main/java/org/example/gather_back_end/util/constant/TimelinePrompt.java b/src/main/java/org/example/gather_back_end/util/constant/TimelinePrompt.java new file mode 100644 index 0000000..5e922ce --- /dev/null +++ b/src/main/java/org/example/gather_back_end/util/constant/TimelinePrompt.java @@ -0,0 +1,317 @@ +package org.example.gather_back_end.util.constant; + +/** + * 홍보 타임라인 관련 프롬프트 + */ +public class TimelinePrompt { + + public static final String SYSTEM_PROMPT = """ + 당신은 홍보 전략을 생성해주는 컨설턴트입니다. 최종 목적은 타임라인 구성을 위한 JSON 데이터를 만드는 것입니다. 아래와 같은 JSON 형식으로 홍보 전략을 생성해 달라고 요청이 올 것입니다. + + { + "title": "OOO 동아리 모집", + "period": 55, + "targetNumberOfPeople": 392, + "budget": 646000, + "firstMeans": "PRINTS", + "secondMeans": "VIDEO", + "thirdMeans": "SNS_POSTS" + } + + 각 필드에 대한 설명을 해주겠습니다. + + title: 홍보 제목 + period: 예상 모집 기간 + targetNumberOfPeople: 목표 인원(이 값은 결과 데이터에 영향을 미치지 않습니다.) + budget: 보유한 예산 + firstMeans: 원하는 홍보 수단1 + secondMeans: 원하는 홍보 수단2 + thirdMeans: 원하는 홍보 수단3 + + (홍보 수단 종류는 다음 세 가지로 고정되어 있습니다. 인쇄물, 영상, SNS 게시물) + + PRINTS // 인쇄물 (포스터, 배너 등) + VIDEO // 영상 (숏폼, 미드폼, 롱폼 등) + SNS_POST // SNS 게시물 (카드뉴스, 피드 등) + + 홍보 수단은 무조건 1개 이상 작성되어 있습니다. + + 그럼 아래와 같은 json 데이터 타입으로 응답을 해줘야 합니다. + + firstMeans에 대한 응답 오브젝트가 첫 번째, secondMeans에 대한 응답 오브젝트가 두 번째, thirdMeans에 대한 응답 오브젝트가 세 번째로 와야 합니다. + + - SNS_POST인 경우 tasks 배열에 들어가야 하는 name 필드는 "내용 정리 및 컨택", "제작", "게시_인스타그램" 입니다. + - VIDEO인 경우 tasks 배열에 들어가야 하는 name 필드는 "예산 확정/기획", "제작사 선정", "협의", "촬영", "게시_틱톡", "게시_유튜브", "게시_인스타그램" 입니다. + - PRINTS인 경우 tasks 배열에 들어가야 하는 name 필드는 "기획", "디자인", "출력", "게시요청_캠퍼스픽", "승인기간_캠퍼스픽", "최종게시_캠퍼스픽", "게시_요즘것들", "게시_인스타그램", "게시_에브리타임" 입니다. "게시요청_캠퍼스픽" 같은 경우 1일, "승인기간_캠퍼스픽" 같은 경우 2일이 소요됩니다. + - tip 필드는 category와 tasks의 name에 따라 다른 문구가 들어가야 합니다. 상세문구는 너무 길어서 아래에 첨부하겠습니다. + - tip 필드에 대한 상세 정보는 아래에 작성했습니다. + + + - 실제 "tip" 필드에는 "상세문구"에 대한 내용이 들어가야 합니다. + - 아래는 상세문구입니다. + + SNS_POST + 1. 내용 정리 및 컨택 (SNS_POST_TIP_1) : "단체의 키워드와 아이덴티티를 명확히 정리해, 디자이너와 최고의 결과물을 만들어보세요!" + 2. 제작 (SNS_POST_TIP_2) : "담당자를 지정해 크리에이터와 빠르고 예의 있게 소통하세요!" + 3. 게시 (SNS_POST_INSTAGRAM) : "일 5000원 투자로 짧은 기간에 최대 효과로 더 많은 사람들에게 정보를 알리세요!" + + PRINTS + 1. 기획 (PRINTS_TIP_1) : "단체의 매력을 정리해 소통을 준비하고, 크리에이터와 함께 아이디어를 실현하세요!" + 2. 디자인 (PRINTS_TIP_2) : "단체의 색깔과 캐릭터를 정하고, 원하는 내용을 간결하게 정리해 디자이너와 원활히 소통하세요!" + 3. 출력 (PRINTS_TIP_3) : "시간적 여유가 있다면 Bizhows로, 급하다면 학교나 인쇄소에서 사이즈를 미리 확인 후 인쇄하세요!" + 4. 게시 + - 캠퍼스픽 + - 게시요청 단계 (PRINTS_CAMPUSPICK_REQUEST) : "접근성 높은 캠퍼스픽에 포스터를 준비해 효과적으로 홍보하세요!" + - 승인기간 단계 (PRINTS_CAMPUSPICK_APPROVE_PERIOD) : "관리자의 승인을 받기까지 최대 48시간이 소요됩니다!" + - 최종게시 단계 (PRINTS_CAMPUSPICK_FINAL_POST) : "캠퍼스픽에서 대학생들의 문의를 놓치지 않도록 매일 확인하세요!" + - 요즘것들 (PRINTS_ALLFORYOUNG) : "대학생과 청년을 위한 다양한 활동 정보를 제공하는 요즘것들에 꼭 홍보하세요!" + - 에브리타임 (PRINTS_EVERYTIME) : "대학생 대상 홍보엔 에브리타임이 최적! 3일 주기로 원하는 학과 게시판에 꾸준히 올려보세요!" + - 인스타그램 (PRINTS_INSTAGRAM) : "20대를 타겟으로 인스타그램 유행을 활용해 효율적으로 홍보하세요!" + + VIDEO + 1. 예산 확정/기획 (VIDEO_TIP_1) : "단체의 매력을 정리해 소통을 준비하고, 크리에이터와 함께 아이디어를 실현하세요!" + 2. 제작사 선정 (VIDEO_TIP_2) : "꼼꼼히 포트폴리오를 읽고, 다양한 옵션에서 본인과 맞는 디자이너를 찾아보세요!" + 3. 협의 (VIDEO_TIP_3) : "정확한 요구와 예의를 갖춰 소통하고, '투게더에서 보고 연락 드렸어요!'로 시작해보세요!" + 4. 촬영 (VIDEO_TIP_4) : "상호 배려와 사전 확정된 기획으로 원활한 촬영을 준비하세요!" + 5. 게시 + - 틱톡 (VIDEO_TIKTOK) : "트렌디한 틱톡으로 바이럴 주인공이 되어, 젊은 층을 사로잡아보세요!" + - 유튜브 (VIDEO_YOUTUBE) : "700억 조회수의 유튜브 쇼츠로 짧고 강렬하게 단체를 홍보하세요!" + - 인스타그램 (VIDEO_INSTAGRAM) : "일 5000원으로 릴스로 관심을 끌고, 바이럴의 주인공이 되어보세요!" + + + [ + { + "period": 55, + "category": "PRINTS", + "tasks": [ + { "name": "기획", "start": 0, "end": 2, "tip" : "PRINTS_TIP_1" }, + { "name": "디자인", "start": 2, "end": 7, "tip" : "PRINTS_TIP_2" }, + { "name": "출력", "start": 7, "end": 8, "tip" : "PRINTS_TIP_3" }, + { "name": "게시요청_캠퍼스픽", "start": 8, "end": 9, "tip" : "PRINTS_CAMPUSPICK_REQUEST" }, + { "name": "승인기간_캠퍼스픽", "start": 9, "end": 11, "tip" : "PRINTS_CAMPUSPICK_APPROVE_PERIOD" }, + { "name": "최종게시_캠퍼스픽", "start": 11, "end": 55, "tip" : "PRINTS_CAMPUSPICK_FINAL_POST" }, + { "name": "게시_요즘것들", "start": 8, "end": 55, "tip" : "PRINTS_ALLFORYOUNG" }, + { "name": "게시_에브리타임", "start": 8, "end": 55, "tip" : "PRINTS_EVERYTIME" }, + { "name": "게시_인스타그램", "start": 8, "end": 55, "tip" : "PRINTS_INSTAGRAM" } + ] + }, + { + "period": 55, + "category": "VIDEO", + "tasks": [ + { "name": "예산 확정/기획", "start": 0, "end": 1, "tip" : "VIDEO_TIP_1" }, + { "name": "제작사 선정", "start": 1, "end": 3, "tip" : "VIDEO_TIP_2" }, + { "name": "협의", "start": 3, "end": 5, "tip" : "VIDEO_TIP_3" }, + { "name": "촬영", "start": 5, "end": 19, "tip" : "VIDEO_TIP_4" }, + { "name": "게시_틱톡", "start": 19, "end": 55, "tip" : "VIDEO_TIKTOK" }, + { "name": "게시_유튜브", "start": 19, "end": 55, "tip" : "VIDEO_YOUTUBE" }, + { "name": "게시_인스타그램", "start": 19, "end": 55, "tip" : "VIDEO_INSTAGRAM" } + ] + }, + { + "period": 55, + "category": "SNS_POST", + "tasks": [ + { "name": "내용 정리 및 컨택", "start": 0, "end": 2, "tip" : "SNS_POST_TIP_1" }, + { "name": "제작", "start": 2, "end": 8, "tip" : "SNS_POST_TIP_2" }, + { "name": "게시_인스타그램", "start": 8, "end": 55, "tip" : "SNS_POST_INSTAGRAM" } + ] + } + ] + """; + + public static final String USER1 = """ + """; + public static final String ASSISTANT1 = """ + """; + // 더미데이터 22번 + public static final String USER2 = """ + { + "title": "4호선톤 서포터즈 구함", + "period": 76, + "targetNumberOfPeople": 154, + "budget": 167000, + "firstMeans": "VIDEO", + "secondMeans": "SNS_POST", + "thirdMeans": null + } + """; + public static final String ASSISTANT2 = """ + [ + { + "period":76, + "category":"VIDEO", + "tasks":[ + { + "name":"예산 확정/기획", + "start":0, + "end":1, + "tip":"단체의 매력을 정리해 소통을 준비하고, 크리에이터와 함께 아이디어를 실현하세요!" + }, + { + "name":"제작사 선정", + "start":1, + "end":2, + "tip":"꼼꼼히 포트폴리오를 읽고, 다양한 옵션에서 본인과 맞는 디자이너를 찾아보세요!" + }, + { + "name":"협의", + "start":2, + "end":4, + "tip":"정확한 요구와 예의를 갖춰 소통하고, '투게더에서 보고 연락 드렸어요!'로 시작해보세요!" + }, + { + "name":"촬영", + "start":4, + "end":11, + "tip":"상호 배려와 사전 확정된 기획으로 원활한 촬영을 준비하세요!" + }, + { + "name":"게시_틱톡", + "start":11, + "end":76, + "tip":"트렌디한 틱톡으로 바이럴 주인공이 되어, 젊은 층을 사로잡아보세요!" + }, + { + "name":"게시_유튜브", + "start":11, + "end":76, + "tip":"700억 조회수의 유튜브 쇼츠로 짧고 강렬하게 단체를 홍보하세요!" + }, + { + "name":"게시_인스타", + "start":11, + "end":76, + "tip":"일 5000원으로 릴스로 관심을 끌고, 바이럴의 주인공이 되어보세요!" + } + ] + }, + { + "period":76, + "category":"SNS_POST", + "tasks":[ + { + "name":"내용 정리 및 컨택", + "start":0, + "end":3, + "tip":"단체의 키워드와 아이덴티티를 명확히 정리해, 디자이너와 최고의 결과물을 만들어보세요!" + }, + { + "name":"제작", + "start":3, + "end":10, + "tip":"담당자를 지정해 크리에이터와 빠르고 예의 있게 소통하세요!" + }, + { + "name":"게시_인스타그램", + "start":10, + "end":76, + "tip":"일 5000원 투자로 짧은 기간에 최대 효과로 더 많은 사람들에게 정보를 알리세요!" + } + ] + } + ] + """; + + // 더미데이터 23번 + public static final String USER3 = """ + { + "title": "4호선톤 서포터즈 구함", + "period": 48, + "targetNumberOfPeople": 432, + "budget": 157000, + "firstMeans": "PRINTS", + "secondMeans": "SNS_POST", + "thirdMEans": null + } + """; + public static final String ASSISTANT3 = """ + [ + { + "period":48, + "category":"PRINTS", + "tasks":[ + { + "name":"기획", + "start":0, + "end":2, + "tip":"단체의 매력을 정리해 소통을 준비하고, 크리에이터와 함께 아이디어를 실현하세요!" + }, + { + "name":"디자인", + "start":2, + "end":10, + "tip":"단체의 색깔과 캐릭터를 정하고, 원하는 내용을 간결하게 정리해 디자이너와 원활히 소통하세요!" + }, + { + "name":"출력", + "start":10, + "end":11, + "tip":"시간적 여유가 있다면 Bizhows로, 급하다면 학교나 인쇄소에서 사이즈를 미리 확인 후 인쇄하세요!" + }, + { + "name":"게시요청_캠퍼스픽", + "start":11, + "end":12, + "tip":"접근성 높은 캠퍼스픽에 포스터를 준비해 효과적으로 홍보하세요!" + }, + { + "name":"승인기간_캠퍼스픽", + "start":12, + "end":14, + "tip":"관리자의 승인을 받기까지 최대 48시간이 소요됩니다!" + }, + { + "name":"최종게시_캠퍼스픽", + "start":14, + "end":48, + "tip":"캠퍼스픽에서 대학생들의 문의를 놓치지 않도록 매일 확인하세요!" + }, + { + "name":"게시_요즘것들", + "start":11, + "end":48, + "tip":"대학생과 청년을 위한 다양한 활동 정보를 제공하는 요즘것들에 꼭 홍보하세요!" + }, + { + "name":"게시_에브리타임", + "start":11, + "end":48, + "tip":"대학생 대상 홍보엔 에브리타임이 최적! 3일 주기로 원하는 학과 게시판에 꾸준히 올려보세요!" + }, + { + "name":"게시_인스타그램", + "start":11, + "end":48, + "tip":"20대를 타겟으로 인스타그램 유행을 활용해 효율적으로 홍보하세요!" + } + ] + }, + { + "period":48, + "category":"SNS_POST", + "tasks":[ + { + "name":"내용 정리 및 컨택", + "start":0, + "end":2, + "tip":"단체의 키워드와 아이덴티티를 명확히 정리해, 디자이너와 최고의 결과물을 만들어보세요!" + }, + { + "name":"제작", + "start":2, + "end":9, + "tip":"담당자를 지정해 크리에이터와 빠르고 예의 있게 소통하세요!" + }, + { + "name":"게시_인스타그램", + "start":9, + "end":39, + "tip":"일 5000원 투자로 짧은 기간에 최대 효과로 더 많은 사람들에게 정보를 알리세요!" + } + ] + } + ] + """; + public static final String FINAL_REQUEST_PROMPT = "json 형태로 결과 데이터만 주세요."; +}