이제 본격적으로 Open api를 통해 AI 이미지를 생성하는 웹사이트를 만들어보자.
GPT도 이미지 생성 api를 제공하므로, 꼭 Karlo를 사용할 필요는 없다.
하지만 Karlo는 공짜임
우선 Karlo 부터 연동해보자.

https://devtalk.kakao.com/t/kogpt-karlo-api-notice-end-of-support-for-kogpt-karlo-apis/139024
Karlo API가 9월 30일자부로 서비스가 종료되었다.... 😯 ㅋㅋㅋ
따라서 기존의 Karlo api를 연동하는 부분을 GPT api를 사용하는 것으로 대체하기로 한다.
Chat GPT API로 AI 이미지 생성 웹사이트 만들기가 됐네...
이미지를 받을 DTO를 추가한다.
// [24/09/30 - deprecated] karlo 이미지 생성 api
// import lombok.AllArgsConstructor;
// import lombok.Getter;
// import lombok.NoArgsConstructor;
// import lombok.Setter;
//
// @Getter
// @Setter
// @NoArgsConstructor
// @AllArgsConstructor
// public class ImageDto {
// private String id;
// private String image;
// }

위의 예제 응답처럼 id와 image를 추출할 생각이다.
Karlo api를 연동한다.
import com.communitcation.rest.client.CommunicationInfo;
import com.communitcation.rest.model.ImageDto;
import com.communitcation.rest.model.UrlDto;
import com.google.gson.reflect.TypeToken;
import lombok.RequiredArgsConstructor;
import org.apache.hc.core5.http.Method;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.List;
import java.util.Map;
import static com.communitcation.rest.util.JsonUtil.fromJson;
import static com.communitcation.rest.util.JsonUtil.toJson;
@Service
@RequiredArgsConstructor
public class RestApiService {
private final CommunicationService communicationService;
@Value("${api.naver.client-id}")
private String naverClientId;
@Value("${api.naver.client-key}")
private String naverClientKey;
// @Value("${api.kakao.key}")
// private String kakaoRestApiKey; // 카카오 api key 추가
... 생략 ...
// [24/09/30 - deprecated] karlo 이미지 생성 api
// public String makeAiImageByKakao(String prompt) throws IOException {
// Map<String,String> karloReqHeader = Map.of(
// "Authorization", "KakaoAK " + kakaoRestApiKey
// );
//
// String host = "api.kakaobrain.com";
// String genImgPath = "/v2/inference/karlo/t2i";
// prompt = prompt.replaceAll("prompt=","");
// String koToEng = translateKoToEng(prompt);
//
// Map<String, Object> requestMap = Map.of(
// "version" , "v2.1",
// "prompt",koToEng,
// "negative_prompt","low quality, low contrast, draft, amateur, cut off, cropped, frame",
// "width" , 768,
// "height", 768,
// "image_format","png",
// "image_quality",100
// );
//
// CommunicationInfo karloGenApiInfo = CommunicationInfo
// .builder()
// .scheme("https")
// .port("443")
// .host(host)
// .path(genImgPath)
// .method(Method.POST)
// .headers(karloReqHeader)
// .requestData(requestMap)
// .responseClazz(Map.class)
// .build();
// Map<String,Object> imgResMap = communicationService.communicate(karloGenApiInfo);
// System.out.println(imgResMap);
// String imagesToStr = toJson(imgResMap.get("images"));
// TypeToken<List<ImageDto>> imagesTypeToken = new TypeToken<List<ImageDto>>() {};
// List<ImageDto> images = fromJson(imagesToStr, imagesTypeToken);
// ImageDto imageDto = images.get(0);
// String imageUrl = imageDto.getImage();
// return imageUrl;
// }
}
최종적으로 이미지의 url을 반환하게끔 했다.
request는 다음처럼 설정했다.
// Map<String, Object> requestMap = Map.of(
// "version" , "v2.1",
// "prompt",prompt,
// "negative_prompt","low quality, low contrast, draft, amateur, cut off, cropped, frame",
// "width" , 768,
// "height", 768,
// "image_format","png",
// "image_quality",100
// );
버전은 2.1, negative_prompt는 예시로 적어둔거 그대로 복사해서 씀ㅋㅋㅋ

width랑 height는 최소로 설정해놨다.

이외에도 노이즈 조정 등 다양한 파라미터가 있다. 난 기본적인거만 세팅함.
참고로, Karlo는 영어 프롬프트만 지원한다.

지난글에서도 언급했다시피, 무려 5$를 투자한 GPT api를 번역기로 쓰면된다.

그럼 이놈이 내가 쓴 한글 프롬프트를 영어로 바꿔서 Karlo에게 넘겨줄 것이다.
// import com.communitcation.rest.service.RestApiService;
import com.communitcation.rest.service.GenerateImageService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
// import java.io.IOException;
@Controller
@RequiredArgsConstructor
public class HomeController {
// [24/09/30 - deprecated] karlo 이미지 생성 api
// private final RestApiService restApiService;
// @PostMapping("/karlo-img")
// public ResponseEntity<String> kakaoGenAiImageApi(
// @RequestBody String prompt,
// HttpServletRequest request
// ) throws IOException {
// String genImage = restApiService.makeAiImageByKakao(prompt);
// return ResponseEntity.ok(genImage);
// }
private final GenerateImageService generateImageService;
@PostMapping("/ai-img")
public String openAiImageApi(
@RequestBody String prompt,
HttpServletRequest request
) {
String genImage = generateImageService.generateImageUrl(prompt);
return ResponseEntity.ok(genImage);
}
}
kakaoGenAiImageApi 메서드를 위와 같이 수정한다. GPT api 호출을 위해, GenerateImageService 라는 별도의 서비스 로직을 작성할 것이다.
Java는 Azure에서 지원하는 라이브러리랑 Theo Kanning 이라는 개발자가 만든 다음 라이브러리가 주로 사용되는 듯 하다. 둘 다 공식문서에 있음
난 👇 이거 쓸거다.
해당 라이브러리도 6월 이후로 업데이트를 지원하지 않는다고 한다. ㅋㅋㅋㅋ

그래도 /v1/chat/completions, /v1/images/generations api를 호출하는데는 무리가 없어보이므로 그대로 사용하겠다.

빠르게 시작하고 싶으면 OpenAiService를 쓰라고 한다.
사실 지난글에서 이미 이 라이브러리를 build.gradle에 추가해놨기 때문에 별도로 gradle 추가하는 부분은 생략하였다.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.theokanning.openai.service.OpenAiService;
@Configuration
public class GptService {
@Value("${api.gpt.key}")
private String gptKey;
@Bean
public OpenAiService openAiService(){
OpenAiService service = new OpenAiService(gptKey, Duration.ofSeconds(120));
return service;
}
}
Bean으로 OpenAiService를 등록해준다.
지금 생각해보니 Service 폴더보단 Config 폴더가 어울리는 거 같기도..
귀찮으니 그냥 이대로 냅두겠다.
import com.theokanning.openai.completion.chat.*;
import com.theokanning.openai.image.CreateImageRequest;
import com.theokanning.openai.image.ImageResult;
import com.theokanning.openai.service.OpenAiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.net.URLDecoder;
import java.util.List;
@Service
public class GenerateImageService {
@Autowired
private GptService gptService;
private OpenAiService openAiService;
public GenerateImageService(GptService gptService){
this.gptService = gptService;
this.openAiService = gptService.openAiService();
}
// 한 -> 영 번역
private String translateKoToEng(String prompt){
String decodePrompt = URLDecoder.decode(prompt);
String message = """
Translate the following Korean text to English and Return only the translated result in English.: %s """.formatted(decodePrompt);
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(),message);
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.messages(List.of(chatMessage))
.model("gpt-4o-mini")
.build();
ChatCompletionResult response = openAiService.createChatCompletion(chatCompletionRequest);
ChatCompletionChoice choice = response.getChoices().get(0);
String translatePrompt = choice.getMessage().getContent();
String log = """
[%s]\ntranslate: %s""".formatted(decodePrompt,translatePrompt);
System.out.println(log);
return translatePrompt;
}
// ai 이미지 생성
public String generateImageUrl(String prompt){
String message = translateKoToEng(prompt);
CreateImageRequest createImageRequest = CreateImageRequest.builder()
.prompt(message)
.build();
ImageResult images = openAiService.createImage(createImageRequest);
String imageUrl = images.getData().get(0).getUrl();
return imageUrl;
}
}
공식 문서에서는 Completions는 레거시라면서 ChatCompletions을 사용할 것을 권장함
Completions API Legacy
The completions API endpoint received its final update in July 2023 and has a different interface than the new chat completions endpoint. Instead of the input being a list of messages, the input is a freeform text string called a prompt.
다음처럼 프롬프트를 입력하면 번역을 해준다고 한다.
그대로 복사해서 message에 설정해주었다.

사실 "<- translate to eng" 이렇게 썼었는데 등신같이 번역되길래 공식 문서를 봤다...😲
OpenAiHttpException: The model
gpt-4-vision-previewhas been deprecated, learn more here: https://platform.openai.com/docs/deprecations] with root cause
gpt-4-vision-preview 가 deprecated 되었다.
대신, 보다 더 저렴하고 성능이 좋다고 하는 gpt-3.5-turbogpt-4o-mini 를 사용하면 된다.
GPT 이미지 생성 api도 한글 프롬프트를 전달하면 엉뚱한 이미지가 생성되길래, translateKoToEng 메서드를 GenerateImageService 내부로 옮겨서 재사용했다.


뭔데 이거... 이렇게 페퍼로니 피자의 흔적이라곤 전혀 찾아볼 수 없는 결과가 나온다.
한영번역을 적용하면 피자가 잘 만들어진다.


맛있겠다.
백엔드 작업은 끝났다.
Thymeleaf로 웹사이트의 구색을 갖춰주자.
spring:
devtools:
livereload:
enabled: true
thymeleaf:
prefix: classpath:/templates/
suffix: .html
cache: false
check-template-location: true
... 생략 ...
yml 파일 상단에 타임리프 설정을 추가해준다.
main 하위 폴더에 있는 resource > templates 폴더에 index.html을 만들어주자.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>HELLO AI</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
height: 100vh;
margin: 0;
padding-top: 1.5vh;
}
form {
margin-bottom: 10px;
padding: 20px;
width: 600px;
display: flex;
align-items: center;
}
input[type="text"] {
flex: 1;
padding: 10px;
margin-right: 10px;
}
button {
padding: 10px 20px;
}
</style>
</head>
<body>
<h1>AI 이미지 생성기</h1>
<!-- <form th:action="@{/karlo-img}" method="post"> -->
<form th:action="@{/ai-img}" method="post">
<input type="text" id="prompt" name="prompt" placeholder="예: 산책하는 귀여운 웰시코기, 사진같은, 8k 이미지"
required>
<button type="submit">생성</button>
</form>
<div th:if="${session.genImage != null}">
<img th:src="@{${session.genImage}}" alt="AI 이미지">
</div>
</body>
</html>
참고로 이 html은 Chat GPT를 훈육하면서 얻어냈다. css 하기 시렁
index.html 원툴이므로 redirect 시 사진이 날아가는 것을 방지하기 위해 HttpSession을 사용했다.
form 태그의 /karlo-img를 /ai-img 로 수정한다.
HomeController에 index.html을 연결해주자.
import com.communitcation.rest.service.GenerateImageService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequiredArgsConstructor
public class HomeController {
private final GenerateImageService generateImageService;
@RequestMapping("/")
public String index() {
return "index";
}
// [24/09/30 - deprecated] karlo 이미지 생성 api
// @PostMapping("/karlo-img")
// public String kakaoGenAiImageApi(
// @RequestBody String prompt,
// HttpServletRequest request
// ) throws IOException {
// String genImage = restApiService.makeAiImageByKakao(prompt);
// HttpSession session = request.getSession();
// session.setAttribute("genImage",genImage); // 이미지 url 설정
// session.setAttribute("prompt", prompt);
// return "redirect:/";
// }
@PostMapping("/ai-img")
public String openAiImageApi(
@RequestBody String prompt,
HttpServletRequest request
) {
String genImage = generateImageService.generateImageUrl(prompt);
HttpSession session = request.getSession();
session.setAttribute("genImage",genImage);
session.setAttribute("prompt", prompt);
return "redirect:/";
}
}
서버 재부팅 후, localhost:8080 으로 접속한다.

예제를 그대로 입력해서 어떤 이미지가 생성되는지 확인해보겠다.


끝. 재밌었다ㅎㅎ
좋은 글이네요 깃소스있나요