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
Binary file not shown.
@@ -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: '',
}
];
@@ -0,0 +1,41 @@
<mat-card appearance="outlined">
<mat-card-header>
<mat-card-title>Edit tutorial</mat-card-title>
</mat-card-header>
<mat-card-content >
<mat-form-field class="example-full-width">
<mat-label>Title</mat-label>
<input matInput required
[(ngModel)]="tutorial.title">
</mat-form-field>
<mat-tab-group>
<mat-tab label="Markdown text">
<textarea class="variable-textarea" [(ngModel)]="tutorial.description"></textarea>
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
</mat-tab>
<mat-tab label="Result">
<markdown class="preview" [data]="tutorial.description"></markdown>
</mat-tab>
<mat-tab label="Example">
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
<markdown class="variable-binding" [data]="markdown"></markdown>
</mat-tab>
</mat-tab-group>
</mat-card-content>
<mat-card-actions>
<button matButton (click)="updateTutorial()">Save</button>
@if (hasError){
<mat-error>
{{errorMessage}}
</mat-error>
}
@if(submitted) {
<h4>Tutorial was submitted successfully!</h4>
<button matButton routerLink="../tutorial-add">Add new tutorial</button>
<button matButton routerLink="../tutorials-list">Back to list</button>
}
</mat-card-actions>
</mat-card>
@@ -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;*/
}
@@ -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<TutorialEditComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TutorialEditComponent]
})
.compileComponents();
fixture = TestBed.createComponent(TutorialEditComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -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;
});
}
}
@@ -1,29 +1,148 @@
<p>tutorials-list.component works!</p>
<article class="table-header">
<button
class="button-remove-rows"
mat-button
(click)="removeSelectedRows()"
>
Remove Rows
</button>
<!--<button class="button-add-row" mat-button (click)="addRow()">
Add Row
</button>-->
<button mat-raised-button routerLink="../tutorial-add">Add tutorial</button>
<button mat-raised-button routerLink="../tutorial-add">Add tutorial</button>
</article>
<table mat-table [dataSource]="dataSource">
@for (column of columnsSchema; track column){
<ng-container [matColumnDef]="column.key">
<th mat-header-cell *matHeaderCellDef>
@switch (column.key) {
@case ('isSelected') {
<mat-checkbox
(change)="selectAll($event)"
[checked]="isAllSelected()"
[indeterminate]="!isAllSelected() && isAnySelected()"
></mat-checkbox>
}
@default {
{{ column.label }}
}
}
</th>
<td mat-cell *matCellDef="let element">
<!-- @if (element.isEdit) {-->
@switch (column.type) {
@case ('isSelected') {
<mat-checkbox
(change)="element.isSelected = $event.checked"
[checked]="element.isSelected"
></mat-checkbox>
}
@case('isEdit') {
<div class="btn-edit" >
<button mat-button routerLink='../tutorial-edit' [queryParams]="{id:element.id}" (click)="element.isEdit = !element.isEdit">
Edit
</button>
<button
mat-button
class="button-remove"
(click)="removeRow(element.id)"
>
Delete
</button>
</div>
}
@case ('boolean') {
<mat-slide-toggle
class="example-margin"
[checked]="element[column.key]"
[disabled]="true">
<mat-list role="list">
@for (tutorial of tutorials; track tutorial) {
<!--
<mat-list-item role="listitem">
{{tutorial.title}}
</mat-slide-toggle>
}
@case ('datetime') {
{{ element[column.key] | date: 'medium' }}
}
@default {
{{ element[column.key] }}
}
}
<!-- }-->
</td>
</ng-container>
<span class="spacer"></span>
<button mat-button>
<mat-icon>edit</mat-icon>
</button>
<button mat-button>
<mat-icon>delete</mat-icon>
</button>
</mat-list-item>-->
<mat-option >
<div style="display:flex; justify-content: space-between">
<span>{{tutorial.title}}</span>
<span></span>
<span (click)="deleteOption($event, tutorial)">X</span>
</div>
</mat-option>
}
<!--<ng-container [matColumnDef]="col.key" *ngFor="let col of columnsSchema">-->
<!--<th mat-header-cell *matHeaderCellDef [ngSwitch]="col.key">
<span *ngSwitchCase="'isSelected'">
<mat-checkbox
(change)="selectAll($event)"
[checked]="isAllSelected()"
[indeterminate]="!isAllSelected() && isAnySelected()"
></mat-checkbox>
</span>
<span *ngSwitchDefault>{{ col.label }}</span>
</th>-->
<!-- <mat-form-field-->
<!--<td mat-cell *matCellDef="let element">
<div [ngSwitch]="col.type" *ngIf="!element.isEdit">
<ng-container *ngSwitchCase="'isSelected'">
<mat-checkbox
(change)="element.isSelected = $event.checked"
[checked]="element.isSelected"
></mat-checkbox>
</ng-container>
<div class="btn-edit" *ngSwitchCase="'isEdit'">
<button mat-button (click)="element.isEdit = !element.isEdit">
Edit
</button>
<button
mat-button
class="button-remove"
(click)="removeRow(element.id)"
>
Delete
</button>
</div>
<span *ngSwitchCase="'date'">
{{ element[col.key] | date: 'mediumDate' }}
</span>
<span *ngSwitchDefault>
{{ element[col.key] }}
</span>
</div>
<div [ngSwitch]="col.type" *ngIf="element.isEdit">
<div *ngSwitchCase="'isSelected'"></div>
<div class="btn-edit" *ngSwitchCase="'isEdit'">
<button
mat-button
(click)="editRow(element)"
[disabled]="disableSubmit(element.id)"
>
Done
</button>
</div>
class="form-input"
*ngSwitchCase="'date'"
appearance="fill"
>
<mat-label>Choose a date</mat-label>
<input
matInput
[matDatepicker]="picker"
[(ngModel)]="element[col.key]"
/>
<mat-datepicker-toggle
matSuffix
[for]="picker"
></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
</div>
</td>
</ng-container>-->
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</mat-list>
@@ -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;
}
@@ -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<Tutorial>()
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 => {
@@ -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<Tutorial[]>(`${this.baseUrl}/tutorials`, this.httpOptions);
}
getTutorial(id: string | null | undefined): Observable<Tutorial> {
return this.http.get<Tutorial>(`${this.baseUrl}/tutorial-get/${id}`);
}
create(data: any): Observable<any> {
return this.http.post(`${this.baseUrl}/tutorial-add`, data);
}
update(id: any,data: any): Observable<any> {
return this.http.put(`${this.baseUrl}/tutorial-update/${id}`, data);
}
deleteTutorials(tutorials: Tutorial[]): Observable<Tutorial[]> {
return forkJoin(
tutorials.map((tutorial) =>
this.http.delete<Tutorial>(`${this.baseUrl}/tutorials/${tutorial.id}`)
)
);
}
deleteTutorial(id: number): Observable<Tutorial> {
return this.http.delete<Tutorial>(`${this.baseUrl}/tutorials/${id}`);
}
}
@@ -47,4 +47,6 @@ mat-sidenav-content{
}
.pc-container{
top: 0px;
padding-left: 5px;
padding-right: 5px;
}
@@ -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)
@@ -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}