Refactor tutorial management and add AI image model functionality

- Updated application.properties for local database configuration.
- Introduced DateTimeHelper for timestamp parsing.
- Added MyAIImageModel and MyImageOptions for AI image processing.
- Enhanced Tutorial model to include created and modified timestamps.
- Implemented tutorial editing component with improved UI and functionality.
- Updated user API service to handle tutorial retrieval and updates.
- Refactored tutorials list component to support selection and batch deletion.
This commit is contained in:
liosha84
2025-07-12 18:30:09 +03:00
parent dbdc189c12
commit f9306f7af8
19 changed files with 930 additions and 101 deletions
@@ -0,0 +1,109 @@
package com.jambotronGroup.jambotron.ZhiPuAi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.image.*;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.ai.zhipuai.ZhiPuAiImageModel;
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
import org.springframework.http.ResponseEntity;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert;
import java.util.List;
public class MyAIImageModel implements ImageModel {
private static final Logger logger = LoggerFactory.getLogger(ZhiPuAiImageModel.class);
public final RetryTemplate retryTemplate;
private final MyImageOptions defaultOptions;
private final ZhiPuAiImageApi zhiPuAiImageApi;
public MyAIImageModel(ZhiPuAiImageApi zhiPuAiImageApi) {
this(zhiPuAiImageApi, MyImageOptions.builder().build(), RetryUtils.DEFAULT_RETRY_TEMPLATE);
}
public MyAIImageModel(ZhiPuAiImageApi zhiPuAiImageApi, MyImageOptions defaultOptions,
RetryTemplate retryTemplate) {
Assert.notNull(zhiPuAiImageApi, "ZhiPuAiImageApi must not be null");
Assert.notNull(defaultOptions, "defaultOptions must not be null");
Assert.notNull(retryTemplate, "retryTemplate must not be null");
this.zhiPuAiImageApi = zhiPuAiImageApi;
this.defaultOptions = defaultOptions;
this.retryTemplate = retryTemplate;
}
public MyImageOptions getDefaultOptions() {
return this.defaultOptions;
}
@Override
public ImageResponse call(ImagePrompt imagePrompt) {
return this.retryTemplate.execute(ctx -> {
String instructions = imagePrompt.getInstructions().get(0).getText();
ZhiPuAiImageApi.ZhiPuAiImageRequest imageRequest = new ZhiPuAiImageApi.ZhiPuAiImageRequest(instructions,
ZhiPuAiImageApi.DEFAULT_IMAGE_MODEL);
if (this.defaultOptions != null) {
imageRequest = ModelOptionsUtils.merge(this.defaultOptions, imageRequest,
ZhiPuAiImageApi.ZhiPuAiImageRequest.class);
}
if (imagePrompt.getOptions() != null) {
imageRequest = ModelOptionsUtils.merge(toZhiPuAiImageOptions(imagePrompt.getOptions()), imageRequest,
ZhiPuAiImageApi.ZhiPuAiImageRequest.class);
}
// Make the request
ResponseEntity<ZhiPuAiImageApi.ZhiPuAiImageResponse> imageResponseEntity = this.zhiPuAiImageApi
.createImage(imageRequest);
// Convert to org.springframework.ai.model derived ImageResponse data type
return convertResponse(imageResponseEntity, imageRequest);
});
}
private ImageResponse convertResponse(ResponseEntity<ZhiPuAiImageApi.ZhiPuAiImageResponse> imageResponseEntity,
ZhiPuAiImageApi.ZhiPuAiImageRequest zhiPuAiImageRequest) {
ZhiPuAiImageApi.ZhiPuAiImageResponse imageApiResponse = imageResponseEntity.getBody();
if (imageApiResponse == null) {
logger.warn("No image response returned for request: {}", zhiPuAiImageRequest);
return new ImageResponse(List.of());
}
List<ImageGeneration> imageGenerationList = imageApiResponse.data()
.stream()
.map(entry -> new ImageGeneration(new Image(entry.url(), null)))
.toList();
return new ImageResponse(imageGenerationList);
}
/**
* Convert the {@link ImageOptions} into {@link ZhiPuAiImageOptions}.
* @param runtimeImageOptions the image options to use.
* @return the converted {@link ZhiPuAiImageOptions}.
*/
private MyImageOptions toZhiPuAiImageOptions(ImageOptions runtimeImageOptions) {
MyImageOptions.Builder myImageOptionsBuilder = MyImageOptions.builder();
if (runtimeImageOptions != null) {
if (runtimeImageOptions.getModel() != null) {
myImageOptionsBuilder.model(runtimeImageOptions.getModel());
}
if (runtimeImageOptions instanceof MyImageOptions myImageOptions) {
if (myImageOptions.getUser() != null) {
myImageOptionsBuilder.user(myImageOptions.getUser());
}
}
}
return myImageOptionsBuilder.build();
}
}
@@ -0,0 +1,128 @@
package com.jambotronGroup.jambotron.ZhiPuAi;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.ai.image.ImageOptions;
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
import java.util.Objects;
public class MyImageOptions implements ImageOptions {
/**
* The model to use for image generation.
*/
@JsonProperty("model")
private String model = ZhiPuAiImageApi.DEFAULT_IMAGE_MODEL;
/**
* A unique identifier representing your end-user, which can help ZhiPuAI to monitor
* and detect abuse. User ID length requirement: minimum of 6 characters, maximum of
* 128 characters
*/
@JsonProperty("user_id")
private String user;
public static MyImageOptions.Builder builder() {
return new MyImageOptions.Builder();
}
@Override
@JsonIgnore
public Integer getN() {
return null;
}
@Override
public String getModel() {
return this.model;
}
public void setModel(String model) {
this.model = model;
}
@Override
@JsonIgnore
public Integer getWidth() {
return 300;
}
@Override
@JsonIgnore
public Integer getHeight() {
return 200;
}
@Override
@JsonIgnore
public String getResponseFormat() {
return null;
}
@Override
@JsonIgnore
public String getStyle() {
return null;
}
public String getUser() {
return this.user;
}
public void setUser(String user) {
this.user = user;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
else {
return false;
}
// if (!(o instanceof ZhiPuAiImageOptions that)) {
// return false;
// }
// return Objects.equals(this.model, that.model) && Objects.equals(this.user, that.user);
}
@Override
public int hashCode() {
return Objects.hash(this.model, this.user);
}
@Override
public String toString() {
return "ZhiPuAiImageOptions{model='" + this.model + '\'' + ", user='" + this.user + '\'' + '}';
}
public static final class Builder {
private final MyImageOptions options;
Builder() {
this.options = new MyImageOptions();
}
public MyImageOptions.Builder model(String model) {
this.options.setModel(model);
return this;
}
public MyImageOptions.Builder user(String user) {
this.options.setUser(user);
return this;
}
public MyImageOptions build() {
return this.options;
}
}
}
@@ -1,9 +1,13 @@
package com.jambotronGroup.jambotron.ZhiPuAi;
import org.springframework.ai.image.ImageOptions;
import org.springframework.ai.image.ImageOptionsBuilder;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.ai.zhipuai.ZhiPuAiImageModel;
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@@ -21,18 +25,48 @@ public class ZhiPuAiImageService {
private ZhiPuAiImageApi _zhiPuAiImageApi;
private MyAIImageModel _myAIImageModel;
private ZhiPuAiImageModel _zhiPuAiImageModel;
private MyImageOptions _myImageOptions;
public ZhiPuAiImageService() {
_zhiPuAiImageApi = new ZhiPuAiImageApi("628447c8c65845a48a7226391464a2ea.Dw8ci6TiW0BRF5LI");
ImageOptionsBuilder imageOptionsBuilder = ImageOptionsBuilder.builder();
ImageOptions imageOptions = imageOptionsBuilder.model("Cogview-3-Flash")//ZhiPuAiImageApi.DEFAULT_IMAGE_MODEL)
.height(100)
.width(200)
.build();
// _myImageOptions = new MyImageOptions.Builder()
// .model(ZhiPuAiImageApi.DEFAULT_IMAGE_MODEL)
// .user("jambotron")
//
// .build();
_zhiPuAiImageModel = new ZhiPuAiImageModel(_zhiPuAiImageApi);
_myAIImageModel = new MyAIImageModel(_zhiPuAiImageApi);
}
public ImageResponse generateImage(String prompt) {
ImageOptionsBuilder imageOptionsBuilder = ImageOptionsBuilder.builder();
ImageOptions imageOptions = imageOptionsBuilder.model("Cogview-3-Flash")//ZhiPuAiImageApi.DEFAULT_IMAGE_MODEL)
.height(1440)
.width(720)
.build();
// Create an ImagePrompt object with the desired prompt
ImagePrompt imagePrompt = new ImagePrompt(prompt);
ImagePrompt imagePrompt = new ImagePrompt(prompt,imageOptions);
/*// Call the generate method to get the image response
ImageResponse imageResponse = _myAIImageModel.call(imagePrompt);
*/
// Call the generate method to get the image response
ImageResponse imageResponse = _zhiPuAiImageModel.call(imagePrompt);
@@ -7,21 +7,17 @@ import com.jambotronGroup.jambotron.repository.TutorialRepository;
import com.jambotronGroup.jambotron.repository.UserRepository;
import com.jambotronGroup.jambotron.security.AuthenticationFacade;
import com.jambotronGroup.jambotron.security.services.UserDetailsImpl;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.*;
//@CrossOrigin(origins = "http://localhost:4200", maxAge = 3600, allowCredentials="true")
@@ -106,18 +102,7 @@ public class TutorialController {
}
}
@GetMapping("/tutorials/{id}")
public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") long id) {
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
if (tutorialData.isPresent()) {
return new ResponseEntity<>(tutorialData.get(), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@PostMapping("user/tutorial-add")
@PostMapping("user/tutorial-add")
public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
@@ -127,29 +112,49 @@ public class TutorialController {
try {
Tutorial _tutorial = tutorialRepository
.save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false, user));
.save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false, user, Timestamp.valueOf(LocalDateTime.now()), Timestamp.valueOf(LocalDateTime.now())));
return new ResponseEntity<>(_tutorial, HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@PutMapping("/tutorials/{id}")
public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
@GetMapping("user/tutorial-get/{id}")
public ResponseEntity<Tutorial> getTutorial(@PathVariable("id") long id) {
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
if (tutorialData.isPresent()) {
Tutorial _tutorial = tutorialData.get();
_tutorial.setTitle(tutorial.getTitle());
_tutorial.setDescription(tutorial.getDescription());
_tutorial.setPublished(tutorial.isPublished());
return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
return new ResponseEntity<Tutorial>(tutorialData.get(), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@DeleteMapping("/tutorials/{id}")
@PutMapping("user/tutorial-update/{id}")
public ResponseEntity<?> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
Map<String, Object> map = new LinkedHashMap<String, Object>();
if (tutorialData.isPresent()) {
Tutorial servTutorial = tutorialData.get();
servTutorial.setTitle(tutorial.getTitle());
servTutorial.setDescription(tutorial.getDescription());
servTutorial.setPublished(tutorial.isPublished());
try {
servTutorial = tutorialRepository.save(servTutorial);
} catch (Exception e) {
map.put("status", 0);
map.put("message", e.getMessage());
return new ResponseEntity<>(map,HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<>(servTutorial, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@DeleteMapping("user/tutorials/{id}")
public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") long id) {
try {
tutorialRepository.deleteById(id);
@@ -25,15 +25,25 @@ public class Tutorial {
@JoinColumn(name = "userID", nullable = false)
private User user;
@Column(name = "created")
private java.sql.Timestamp created;
@Column(name = "modified")
private java.sql.Timestamp modified;
public Tutorial() {
}
public Tutorial(String title, String description, boolean published, User user) {
public Tutorial(String title, String description, boolean published, User user, java.sql.Timestamp created, java.sql.Timestamp modified) {
this.title = title;
this.description = description;
this.published = published;
this.user = user;
this.created = created;
this.modified = modified;
}
public long getId() {
@@ -64,6 +74,22 @@ public class Tutorial {
this.published = isPublished;
}
public void setCreated(java.sql.Timestamp created) {
this.created = created;
}
public java.sql.Timestamp getCreated() {
return created;
}
public void setModified(java.sql.Timestamp modified) {
this.modified = modified;
}
public java.sql.Timestamp getModified() {
return modified;
}
@Override
public String toString() {
return "Tutorial [id=" + id + ", title=" + title + ", desc=" + description + ", published=" + published + "]";
@@ -0,0 +1,18 @@
package com.jambotronGroup.jambotron.utils;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class DateTimeHelper {
public static SimpleDateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static java.sql.Timestamp parseTimestamp(String timestamp) {
try {
return new Timestamp(DATE_TIME_FORMAT.parse(timestamp).getTime());
} catch (ParseException e) {
throw new IllegalArgumentException(e);
}
}
}
+20 -20
View File
@@ -2,32 +2,32 @@ spring.application.name=jambotron
#============Localhost Configurations========================
#spring.datasource.url= jdbc:postgresql://localhost:5432/jambotronDB
#spring.datasource.username= admin
#spring.datasource.password= postgrespw
#spring.flyway.baseline-on-migrate=true
#spring.flyway.validate-on-migrate=true
#
#spring.flyway.url=jdbc:postgresql://localhost:5432/jambotronDB
#spring.flyway.user=admin
#spring.flyway.password=postgrespw
#============Koyeb Configurations========================
spring.datasource.url= jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app/jambotronDB
spring.datasource.username= koyeb-adm
spring.datasource.password= npg_HfFEUA7bay1i
spring.datasource.url= jdbc:postgresql://localhost:5432/jambotronDB
spring.datasource.username= admin
spring.datasource.password= postgrespw
spring.flyway.baseline-on-migrate=true
spring.flyway.validate-on-migrate=true
spring.flyway.url=jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app/jambotronDB
spring.flyway.user=koyeb-adm
spring.flyway.password=npg_HfFEUA7bay1i
spring.flyway.url=jdbc:postgresql://localhost:5432/jambotronDB
spring.flyway.user=admin
spring.flyway.password=postgrespw
#============Koyeb Configurations========================
#spring.datasource.url= jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app/jambotronDB
#spring.datasource.username= koyeb-adm
#spring.datasource.password= npg_HfFEUA7bay1i
#
#spring.flyway.baseline-on-migrate=true
#spring.flyway.validate-on-migrate=true
#
#spring.flyway.url=jdbc:postgresql://ep-red-breeze-a2cxhq5f.eu-central-1.pg.koyeb.app/jambotronDB
#spring.flyway.user=koyeb-adm
#spring.flyway.password=npg_HfFEUA7bay1i
#
#spring.datasource.url=${SPRING_DATASOURCE_URL}
#spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
#spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}