From f9306f7af8d5ddbd2e664aa163079a880cdfa79d Mon Sep 17 00:00:00 2001 From: liosha84 <138026690+liosha84@users.noreply.github.com> Date: Sat, 12 Jul 2025 18:30:09 +0300 Subject: [PATCH 1/2] 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. --- build/libs/jambotron-0.0.1-SNAPSHOT.jar | Bin 83836153 -> 83836147 bytes jambotron-ui/src/app/models/tutorial.model.ts | 41 +++++ .../tutorial-edit.component.html | 41 +++++ .../tutorial-edit.component.scss | 52 ++++++ .../tutorial-edit.component.spec.ts | 23 +++ .../tutorial-edit.component.ts | 91 ++++++++++ .../tutorials-list.component.html | 167 +++++++++++++++--- .../tutorials-list.component.scss | 11 ++ .../tutorials-list.component.ts | 156 +++++++++++++--- .../src/app/user-module/user-api.service.ts | 23 ++- .../user.component/user.component.scss | 2 + .../src/app/user-module/user.routing.ts | 4 + .../jambotron/ZhiPuAi/MyAIImageModel.java | 109 ++++++++++++ .../jambotron/ZhiPuAi/MyImageOptions.java | 128 ++++++++++++++ .../ZhiPuAi/ZhiPuAiImageService.java | 36 +++- .../controllers/TutorialController.java | 61 ++++--- .../jambotron/model/Tutorial.java | 28 ++- .../jambotron/utils/DateTimeHelper.java | 18 ++ src/main/resources/application.properties | 40 ++--- 19 files changed, 930 insertions(+), 101 deletions(-) create mode 100644 jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.html create mode 100644 jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.scss create mode 100644 jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.spec.ts create mode 100644 jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.ts create mode 100644 src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/MyAIImageModel.java create mode 100644 src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/MyImageOptions.java create mode 100644 src/main/java/com/jambotronGroup/jambotron/utils/DateTimeHelper.java diff --git a/build/libs/jambotron-0.0.1-SNAPSHOT.jar b/build/libs/jambotron-0.0.1-SNAPSHOT.jar index 82b88c59e29a369d8b8674ae377b0eb91659bcff..e52fd4e4b6d8905b225b24cf66f9abe4813494b6 100644 GIT binary patch delta 14443 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%0AAYx_UojP$i4jE) zN(w>rcAAS@Jo8El2~_ZrKVmsF4HENj7L_#Ax>ckw*R6kqGd}A5EQ+Qf;_S5Qf^HxE zM};8#B&AHkT#QPoR$Z{S;|s?Q3j+FX8^**DOLL&Z(Wz5+W>xHESze3<8{Y$M;ya%nn1 z==E27-Qd2i8nK1YqWVCVPc0T~AAkL^#VFnDVtVZ(cV}&B1Nte7R4jpJ!o*c;!Ubj< zI7YBy7Vg;Cc`THsJ%nyQce7%etHok}?iQ*s=cYI|%a&^DvE;N_>4AM|$oA8;n|vgb)5#PnPMdPBWEUQwS?zIGG9-q&SkXzjg{Gb5AK81F zhKawPo)?&1&UwL=pO(&I{;gC&ZJqtQ7E7Viw3GZ2Gj602JIXJ!o-_>->#}r#btLMr z>r`--4`QEb8X`6g93-;7gacDH#+Lp?+KBEWhlu=WHfMXO;3E%VZ)n<+rtO;GaQ9j^ zTI3%@P3C=Vv}npMP}xg#zE@fv)n^RtA$$6iyl69e(X@+LbKF>wwIb@VOe#2wP3|qD z_0T@6$tKdQ8Q-tY@*EaQyDe1Sm2IVI7qM{WCQ*0TY0sg?vo?wJtxG445p!>bL!gia z(X_jmAHG?;-lCWNCz(_9BXZ+^o`yuIr)+{j(>3U*@bm&04cTR5n8UIJC=s*kYPGp>T*7 RyjTWJ>*^QMzm3Xd{|CO!1YiIF delta 14788 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 D+U@;3 diff --git a/jambotron-ui/src/app/models/tutorial.model.ts b/jambotron-ui/src/app/models/tutorial.model.ts index 66c6ce3..6e356d8 100644 --- a/jambotron-ui/src/app/models/tutorial.model.ts +++ b/jambotron-ui/src/app/models/tutorial.model.ts @@ -1,6 +1,47 @@ export class Tutorial { + isSelected?: boolean; id?: any; title?: string; description?: string; published?: boolean; + created?: Date; + modified?: Date; + isEdit?: boolean; } + +export const TutorialColumns = [ + { + key: 'isSelected', + type: 'isSelected', + label: '', + }, + { + key: 'title', + type: 'text', + label: 'Title', + required: true, + }, + { + key: 'published', + type: 'boolean', + label: 'Is Published', + }, + { + key: 'created', + type: 'datetime', + label: 'Created Date', + required: true, + + }, + { + key: 'modified', + type: 'datetime', + label: 'Modified Date', + required: true + }, + { + key: 'isEdit', + type: 'isEdit', + label: '', + } +]; diff --git a/jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.html b/jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.html new file mode 100644 index 0000000..9a3eefd --- /dev/null +++ b/jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.html @@ -0,0 +1,41 @@ + + + Edit tutorial + + + + Title + + + + + + + + + + + + + + + + + + + + @if (hasError){ + + {{errorMessage}} + + } + @if(submitted) { +

Tutorial was submitted successfully!

+ + + } + +
+
+ diff --git a/jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.scss b/jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.scss new file mode 100644 index 0000000..bd7c2c2 --- /dev/null +++ b/jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.scss @@ -0,0 +1,52 @@ +.submit-form { + max-width: 400px; + margin: auto; +} + +mat-card{ + margin: 20px; +} +mat-card-title{ + color: cyan; +} + +.example-form { + min-width: 150px; + max-width: 500px; + width: 100%; +} + + +.example-full-width { + width: 100%; +} + + +.variable-binding, +.variable-textarea { + width: 49%; +} + +.variable-textarea { + border-radius: 5px; + box-shadow: 0 1px 2px rgba(0,0,0,.07); + min-height: 420px; + padding: 8px; + transition: all 300ms ease-out; +} + +.variable-textarea:hover { + box-shadow: 0 6px 12px 3px rgba(0,0,0,.09), + 0 2px 3px 1px rgba(0,0,0,.06); +} + +.variable-binding { + display: block; + float: right; +} + +.preview { + /* display: block; + float: right;*/ +} + diff --git a/jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.spec.ts b/jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.spec.ts new file mode 100644 index 0000000..e480036 --- /dev/null +++ b/jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TutorialEditComponent } from './tutorial-edit.component'; + +describe('TutorialEditComponent', () => { + let component: TutorialEditComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TutorialEditComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TutorialEditComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.ts b/jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.ts new file mode 100644 index 0000000..301c9db --- /dev/null +++ b/jambotron-ui/src/app/user-module/tutorial-edit.component/tutorial-edit.component.ts @@ -0,0 +1,91 @@ +import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; +import {MarkdownComponent} from 'ngx-markdown'; +import {MatButton} from '@angular/material/button'; +import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card'; +import {MatError, MatFormField, MatInput, MatLabel} from '@angular/material/input'; +import {MatTab, MatTabGroup} from '@angular/material/tabs'; +import {NgIf} from '@angular/common'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {Tutorial} from '../../models/tutorial.model'; +import {UserApiService} from '../user-api.service'; +import {ActivatedRoute, Router, RouterLink} from '@angular/router'; + +@Component({ + selector: 'app-tutorial-edit.component', + imports: [ + MarkdownComponent, + MatButton, + MatCard, + MatCardActions, + MatCardContent, + MatCardHeader, + MatFormField, + MatInput, + MatLabel, + MatTab, + MatTabGroup, + ReactiveFormsModule, + FormsModule, + MatFormField, + RouterLink, + MatError + ], + templateUrl: './tutorial-edit.component.html', + styleUrl: './tutorial-edit.component.scss', + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class TutorialEditComponent { + tutorial: Tutorial = new Tutorial(); + submitted = false; + hasError = false; + errorMessage = ''; + markdown = `## Markdown __rulez__! +--- + +### Syntax highlight +\`\`\`typescript +const language = 'typescript'; +\`\`\` + +### Lists +1. Ordered list +2. Another bullet point + - Unordered list + - Another unordered bullet + +### Blockquote +> Blockquote to the max`; + private id: string | null | undefined; + + constructor(private userApiService: UserApiService, private route: ActivatedRoute) { + + this.route.queryParams + .subscribe(params => { + console.log(params); + this.id = params['id']; + console.log(this.id); + }); + + this.userApiService.getTutorial(this.id).subscribe( + data=>{ + this.tutorial = data; + } + ); + } + updateTutorial(): void { + this.userApiService.update(this.id, this.tutorial) + .subscribe( + response => { + console.log(response); + this.submitted = true; + this.hasError = false; + }, + error => { + console.log(error); + + this.errorMessage = error.error.message; + + this.hasError = true; + }); + } +} diff --git a/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.html b/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.html index 4e5588a..dd683bf 100644 --- a/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.html +++ b/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.html @@ -1,29 +1,148 @@ -

tutorials-list.component works!

+
+ + + - +
+ + @for (column of columnsSchema; track column){ + + + + - - - - - --> - -
- {{tutorial.title}} - - X -
-
} + + + + + + +
+ @switch (column.key) { + @case ('isSelected') { + + } + @default { + {{ column.label }} + } + } + + + @switch (column.type) { + @case ('isSelected') { + + } + @case('isEdit') { +
+ + +
+ } + @case ('boolean') { + - - @for (tutorial of tutorials; track tutorial) { - +
+ - diff --git a/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.scss b/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.scss index d37713e..e4d8bda 100644 --- a/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.scss +++ b/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.scss @@ -1,3 +1,14 @@ +.table-header { + width: 90%; + margin: auto; + text-align: right; + margin-bottom: 10px; + padding-top: 10px; + display: flex; + align-items: center; + justify-content: space-between; +} + .spacer{ flex: 1 1 auto; } diff --git a/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.ts b/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.ts index 88f5b2d..ba637d4 100644 --- a/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.ts +++ b/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.ts @@ -1,6 +1,6 @@ import {Component, CUSTOM_ELEMENTS_SCHEMA, OnInit} from '@angular/core'; import {MatList, MatListItem} from '@angular/material/list'; -import {Tutorial} from '../../models/tutorial.model'; +import {Tutorial, TutorialColumns} from '../../models/tutorial.model'; import {TutorialService} from '../../services/tutorial.service'; import {UserApiService} from '../user-api.service'; import {MatButton} from '@angular/material/button'; @@ -11,29 +11,80 @@ import {EventBusService} from '../../_shared/event-bus.service'; import {EventData} from '../../_shared/event.class'; import {Router, RouterLink} from '@angular/router'; import {AuthService} from '../../services/auth.service'; +import {FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms'; +import { + MatCell, + MatCellDef, + MatColumnDef, MatHeaderCell, MatHeaderCellDef, + MatHeaderRow, + MatHeaderRowDef, + MatRow, + MatRowDef, + MatTable, + MatTableDataSource +} from '@angular/material/table'; +import {MatFormField, MatInput} from '@angular/material/input'; +import {DatePipe, NgForOf, NgIf, NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common'; +import {MatCheckbox} from '@angular/material/checkbox'; +import {MatDatepicker, MatDatepickerInput, MatDatepickerToggle} from '@angular/material/datepicker'; +import {MatSlideToggle} from '@angular/material/slide-toggle'; -@Component({ - selector: 'app-tutorials-list.component', - imports: [ - MatList, - MatListItem, - MatLine, - MatButton, - MatIcon, - MatOption, - RouterLink - ], - schemas:[CUSTOM_ELEMENTS_SCHEMA], - templateUrl: './tutorials-list.component.html', - styleUrl: './tutorials-list.component.scss' -}) -export class TutorialsListComponent implements OnInit { - tutorials?: Tutorial[]; + + @Component({ + selector: 'app-tutorials-list.component', + imports: [ + MatList, + MatListItem, + MatLine, + MatButton, + MatIcon, + MatOption, + RouterLink, + ReactiveFormsModule, + MatTable, + MatHeaderRowDef, + MatHeaderRow, + MatRowDef, + MatRow, + FormsModule, + MatInput, + MatFormField, + MatColumnDef, + MatHeaderCell, + MatHeaderCellDef, + NgSwitch, + NgSwitchCase, + NgSwitchDefault, + MatCellDef, + MatCheckbox, + MatDatepickerInput, + MatDatepickerToggle, + MatDatepicker, + DatePipe, + NgIf, + NgForOf, + MatCell, + MatSlideToggle + ], + schemas:[CUSTOM_ELEMENTS_SCHEMA], + templateUrl: './tutorials-list.component.html', + styleUrl: './tutorials-list.component.scss' + }) + export class TutorialsListComponent implements OnInit { + //tutorials?: Tutorial[]; + + displayedColumns: string[] = TutorialColumns.map((col) => col.key) + columnsSchema: any = TutorialColumns + dataSource = new MatTableDataSource() + valid: any = {} constructor(private userApiService: UserApiService, private storageService: TokenStorageService, private eventBusService: EventBusService, - private router: Router, private authService: AuthService) { + private router: Router, + private authService: AuthService + + ) { } @@ -41,14 +92,13 @@ export class TutorialsListComponent implements OnInit { this.eventBusService.on('logout', () => { this.logout(); }) - this.retrieveTutorials(); + this.getTutorials(); } - retrieveTutorials(): void { + getTutorials(): void { this.userApiService.getUserAllTutorials() - .subscribe( - data => { - this.tutorials = data; + .subscribe(( data:Tutorial[] ) => { + this.dataSource.data = data; console.log(data); }, error => { @@ -65,9 +115,63 @@ export class TutorialsListComponent implements OnInit { }); } - deleteOption($event: MouseEvent, option: any) { + selectAll(event: any) { + this.dataSource.data = this.dataSource.data.map((item) => ({ + ...item, + isSelected: event.checked + })); + } + + isAllSelected() { + return this.dataSource.data.every((item) => item.isSelected) + } + + isAnySelected() { + return this.dataSource.data.some((item) => item.isSelected) + } + + removeSelectedRows() { + const selectedTutorials = this.dataSource.data.filter((u: Tutorial) => u.isSelected) + /*this.dialog + .open(ConfirmDialogComponent) + .afterClosed() + .subscribe((confirm) => {*/ + // if (confirm) { + this.userApiService.deleteTutorials(selectedTutorials).subscribe(() => { + this.dataSource.data = this.dataSource.data.filter( + (u: Tutorial) => !u.isSelected + ) + }) + // } + // }) + } + + removeRow(id: number) { + this.userApiService.deleteTutorial(id).subscribe(() => { + this.dataSource.data = this.dataSource.data.filter( + (u: Tutorial) => u.id !== id, + ) + }) + } + + editRow(row: Tutorial) { + /*if (row.id === 0) { + this.userService.addUser(row).subscribe((newUser: User) => { + row.id = newUser.id + row.isEdit = false + }) + } else { + this.userService.updateUser(row).subscribe(() => (row.isEdit = false)) + }*/ + } + + disableSubmit(id: number) { + if (this.valid[id]) { + return Object.values(this.valid[id]).some((item) => item === false) + } + return false + } - } logout(): void { this.authService.logout().subscribe({ next: res => { diff --git a/jambotron-ui/src/app/user-module/user-api.service.ts b/jambotron-ui/src/app/user-module/user-api.service.ts index 0ae9fc4..a26176c 100644 --- a/jambotron-ui/src/app/user-module/user-api.service.ts +++ b/jambotron-ui/src/app/user-module/user-api.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import {Observable} from 'rxjs'; +import {forkJoin, Observable} from 'rxjs'; import {Tutorial} from '../models/tutorial.model'; import {HttpClient, HttpHeaders} from '@angular/common/http'; @@ -21,7 +21,28 @@ export class UserApiService { return this.http.get(`${this.baseUrl}/tutorials`, this.httpOptions); } + getTutorial(id: string | null | undefined): Observable { + return this.http.get(`${this.baseUrl}/tutorial-get/${id}`); + } + create(data: any): Observable { return this.http.post(`${this.baseUrl}/tutorial-add`, data); } + + update(id: any,data: any): Observable { + return this.http.put(`${this.baseUrl}/tutorial-update/${id}`, data); + } + + deleteTutorials(tutorials: Tutorial[]): Observable { + return forkJoin( + tutorials.map((tutorial) => + this.http.delete(`${this.baseUrl}/tutorials/${tutorial.id}`) + ) + ); + } + + deleteTutorial(id: number): Observable { + return this.http.delete(`${this.baseUrl}/tutorials/${id}`); + } + } diff --git a/jambotron-ui/src/app/user-module/user.component/user.component.scss b/jambotron-ui/src/app/user-module/user.component/user.component.scss index 070126d..0a94558 100644 --- a/jambotron-ui/src/app/user-module/user.component/user.component.scss +++ b/jambotron-ui/src/app/user-module/user.component/user.component.scss @@ -47,4 +47,6 @@ mat-sidenav-content{ } .pc-container{ top: 0px; + padding-left: 5px; + padding-right: 5px; } diff --git a/jambotron-ui/src/app/user-module/user.routing.ts b/jambotron-ui/src/app/user-module/user.routing.ts index cb542bb..728a19d 100644 --- a/jambotron-ui/src/app/user-module/user.routing.ts +++ b/jambotron-ui/src/app/user-module/user.routing.ts @@ -20,6 +20,10 @@ const USER_ROUTES: Routes = [ path: 'tutorial-add', loadComponent: () => import('../user-module/tutorial-add.component/tutorial-add.component').then((c) => c.TutorialAddComponent) }, + { + path: 'tutorial-edit', + loadComponent: () => import('../user-module/tutorial-edit.component/tutorial-edit.component').then((c) => c.TutorialEditComponent) + }, { path: 'ai-models', loadComponent: () => import('../user-module/ai-models.component/ai-models.component').then((c) => c.AiModelsComponent) diff --git a/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/MyAIImageModel.java b/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/MyAIImageModel.java new file mode 100644 index 0000000..192492d --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/MyAIImageModel.java @@ -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 imageResponseEntity = this.zhiPuAiImageApi + .createImage(imageRequest); + + // Convert to org.springframework.ai.model derived ImageResponse data type + return convertResponse(imageResponseEntity, imageRequest); + }); + } + + private ImageResponse convertResponse(ResponseEntity 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 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(); + } + +} diff --git a/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/MyImageOptions.java b/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/MyImageOptions.java new file mode 100644 index 0000000..ebffdb1 --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/MyImageOptions.java @@ -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; + } + + } + +} diff --git a/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/ZhiPuAiImageService.java b/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/ZhiPuAiImageService.java index 59f5b96..2fc88ad 100644 --- a/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/ZhiPuAiImageService.java +++ b/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/ZhiPuAiImageService.java @@ -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); diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java index 9f9e334..b897497 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java @@ -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 getTutorialById(@PathVariable("id") long id) { - Optional 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 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 updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) { + @GetMapping("user/tutorial-get/{id}") + public ResponseEntity getTutorial(@PathVariable("id") long id) { Optional 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(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 tutorialData = tutorialRepository.findById(id); + Map map = new LinkedHashMap(); + 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 deleteTutorial(@PathVariable("id") long id) { try { tutorialRepository.deleteById(id); diff --git a/src/main/java/com/jambotronGroup/jambotron/model/Tutorial.java b/src/main/java/com/jambotronGroup/jambotron/model/Tutorial.java index 1a773c3..3f41dde 100644 --- a/src/main/java/com/jambotronGroup/jambotron/model/Tutorial.java +++ b/src/main/java/com/jambotronGroup/jambotron/model/Tutorial.java @@ -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 + "]"; diff --git a/src/main/java/com/jambotronGroup/jambotron/utils/DateTimeHelper.java b/src/main/java/com/jambotronGroup/jambotron/utils/DateTimeHelper.java new file mode 100644 index 0000000..930bc47 --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/utils/DateTimeHelper.java @@ -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); + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 290a27a..c8429f1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -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} From 728ff2f313932f62a844a0af0e6bb40ada5b6cf5 Mon Sep 17 00:00:00 2001 From: liosha84 <138026690+liosha84@users.noreply.github.com> Date: Sat, 12 Jul 2025 18:57:44 +0300 Subject: [PATCH 2/2] Create tutorials table with constraints and drop existing table --- .../db/migration/V8__tutorial_remake.sql | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/resources/db/migration/V8__tutorial_remake.sql diff --git a/src/main/resources/db/migration/V8__tutorial_remake.sql b/src/main/resources/db/migration/V8__tutorial_remake.sql new file mode 100644 index 0000000..3c476c6 --- /dev/null +++ b/src/main/resources/db/migration/V8__tutorial_remake.sql @@ -0,0 +1,18 @@ +DROP TABLE tutorials; + +CREATE TABLE tutorials +( + id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ), + published boolean, + title character varying(255) COLLATE pg_catalog."default", + userid bigint, + created timestamp with time zone, + modified timestamp with time zone, + description character varying(255) COLLATE pg_catalog."default", + CONSTRAINT tutorials_title_key UNIQUE (title), + CONSTRAINT "tutorial_user_FK" FOREIGN KEY (userid) + REFERENCES public.users (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION +) +