Skip to content

Commit 7d4351a

Browse files
committed
feat: ai image
1 parent 90b7d21 commit 7d4351a

File tree

13 files changed

+301
-62
lines changed

13 files changed

+301
-62
lines changed

apps/backend/src/api/routes/billing.controller.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,14 @@ export class BillingController {
6161
@Post('/cancel')
6262
async cancel(
6363
@GetOrgFromRequest() org: Organization,
64+
@GetUserFromRequest() user: User,
6465
@Body() body: { feedback: string }
6566
) {
6667
await this._notificationService.sendEmail(
6768
process.env.EMAIL_FROM_ADDRESS,
6869
'Subscription Cancelled',
69-
`Organization ${org.name} has cancelled their subscription because: ${body.feedback}`
70+
`${user.name} from Organization ${org.name} has cancelled their subscription because: ${body.feedback}`,
71+
user.email
7072
);
7173

7274
return this._stripeService.setToCancel(org.id);

apps/backend/src/api/routes/media.controller.ts

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import {
2-
Body, Controller, Get, Param, Post, Query, Req, Res, UploadedFile, UseInterceptors, UsePipes
2+
Body,
3+
Controller,
4+
Get,
5+
Param,
6+
Post,
7+
Query,
8+
Req,
9+
Res,
10+
UploadedFile,
11+
UseInterceptors,
12+
UsePipes,
313
} from '@nestjs/common';
414
import { Request, Response } from 'express';
515
import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';
@@ -25,14 +35,35 @@ export class MediaController {
2535
async generateImage(
2636
@GetOrgFromRequest() org: Organization,
2737
@Req() req: Request,
28-
@Body('prompt') prompt: string
38+
@Body('prompt') prompt: string,
39+
isPicturePrompt = false
2940
) {
3041
const total = await this._subscriptionService.checkCredits(org);
3142
if (total.credits <= 0) {
3243
return false;
3344
}
3445

35-
return {output: 'data:image/png;base64,' + await this._mediaService.generateImage(prompt, org)};
46+
return {
47+
output:
48+
(isPicturePrompt ? '' : 'data:image/png;base64,') +
49+
(await this._mediaService.generateImage(prompt, org, isPicturePrompt)),
50+
};
51+
}
52+
53+
@Post('/generate-image-with-prompt')
54+
async generateImageFromText(
55+
@GetOrgFromRequest() org: Organization,
56+
@Req() req: Request,
57+
@Body('prompt') prompt: string
58+
) {
59+
const image = await this.generateImage(org, req, prompt, true);
60+
if (!image) {
61+
return false;
62+
}
63+
64+
const file = await this.storage.uploadSimple(image.output);
65+
66+
return this._mediaService.saveFile(org.id, file.split('/').pop(), file);
3667
}
3768

3869
@Post('/upload-server')
@@ -43,7 +74,11 @@ export class MediaController {
4374
@UploadedFile() file: Express.Multer.File
4475
) {
4576
const uploadedFile = await this.storage.uploadFile(file);
46-
return this._mediaService.saveFile(org.id, uploadedFile.originalname, uploadedFile.path);
77+
return this._mediaService.saveFile(
78+
org.id,
79+
uploadedFile.originalname,
80+
uploadedFile.path
81+
);
4782
}
4883

4984
@Post('/upload-simple')
@@ -53,7 +88,11 @@ export class MediaController {
5388
@UploadedFile('file') file: Express.Multer.File
5489
) {
5590
const getFile = await this.storage.uploadFile(file);
56-
return this._mediaService.saveFile(org.id, getFile.originalname, getFile.path);
91+
return this._mediaService.saveFile(
92+
org.id,
93+
getFile.originalname,
94+
getFile.path
95+
);
5796
}
5897

5998
@Post('/:endpoint')
@@ -75,10 +114,14 @@ export class MediaController {
75114
// @ts-ignore
76115
const name = upload.Location.split('/').pop();
77116

78-
// @ts-ignore
79-
const saveFile = await this._mediaService.saveFile(org.id, name, upload.Location);
117+
const saveFile = await this._mediaService.saveFile(
118+
org.id,
119+
name,
120+
// @ts-ignore
121+
upload.Location
122+
);
80123

81-
res.status(200).json({...upload, saved: saveFile});
124+
res.status(200).json({ ...upload, saved: saveFile });
82125
// const filePath =
83126
// file.path.indexOf('http') === 0
84127
// ? file.path

apps/backend/src/api/routes/posts.controller.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ export class PostsController {
4545
return this._messagesService.getMarketplaceAvailableOffers(org.id, id);
4646
}
4747

48+
@Post('/posts/generate-image')
49+
@CheckPolicies([AuthorizationActions.Create, Sections.POSTS_PER_MONTH])
50+
generateImage(@Body() body: { text: string; type: string }) {
51+
52+
}
53+
4854
@Get('/')
4955
async getPosts(
5056
@GetOrgFromRequest() org: Organization,

apps/frontend/src/components/launches/add.edit.model.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,7 @@ export const AddEditModal: FC<{
655655
<div className="flex">
656656
<div className="flex-1">
657657
<MultiMediaComponent
658+
text={p.content}
658659
label="Attachments"
659660
description=""
660661
value={p.image}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Button } from '@gitroom/react/form/button';
2+
import { FC, useCallback, useState } from 'react';
3+
import clsx from 'clsx';
4+
import Loading from 'react-loading';
5+
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
6+
7+
const list = [
8+
'Realistic',
9+
'Cartoon',
10+
'Anime',
11+
'Fantasy',
12+
'Abstract',
13+
'Pixel Art',
14+
'Sketch',
15+
'Watercolor',
16+
'Minimalist',
17+
'Cyberpunk',
18+
'Monochromatic',
19+
'Surreal',
20+
'Pop Art',
21+
'Fantasy Realism',
22+
];
23+
24+
export const AiImage: FC<{
25+
value: string;
26+
onChange: (params: { id: string; path: string }) => void;
27+
}> = (props) => {
28+
const { value, onChange } = props;
29+
const [loading, setLoading] = useState(false);
30+
const fetch = useFetch();
31+
32+
const generateImage = useCallback(
33+
(type: string) => async () => {
34+
setLoading(true);
35+
const image = await (
36+
await fetch('/media/generate-image-with-prompt', {
37+
method: 'POST',
38+
body: JSON.stringify({
39+
prompt: `
40+
<!-- description -->
41+
${value}
42+
<!-- /description -->
43+
44+
<!-- style -->
45+
${type}
46+
<!-- /style -->
47+
48+
`,
49+
}),
50+
})
51+
).json();
52+
setLoading(false);
53+
onChange(image);
54+
},
55+
[value, onChange]
56+
);
57+
58+
return (
59+
<div className="relative group">
60+
<Button
61+
{...(value.length < 30
62+
? {
63+
'data-tooltip-id': 'tooltip',
64+
'data-tooltip-content':
65+
'Please add at least 30 characters to generate AI image',
66+
}
67+
: {})}
68+
className={clsx(
69+
'relative ml-[10px] rounded-[4px] mb-[10px] gap-[8px] !text-primary justify-center items-center flex border border-dashed border-customColor21 bg-input',
70+
value.length < 30 && 'opacity-25'
71+
)}
72+
>
73+
{loading && (
74+
<div className="absolute left-[50%] -translate-x-[50%]">
75+
<Loading height={30} width={30} type="spin" color="#fff" />
76+
</div>
77+
)}
78+
<div
79+
className={clsx(
80+
'flex gap-[5px] items-center',
81+
loading && 'invisible'
82+
)}
83+
>
84+
<div>
85+
<svg
86+
xmlns="http://www.w3.org/2000/svg"
87+
width="24"
88+
height="24"
89+
viewBox="0 0 24 24"
90+
fill="none"
91+
>
92+
<path
93+
d="M19.5 3H7.5C7.10218 3 6.72064 3.15804 6.43934 3.43934C6.15804 3.72064 6 4.10218 6 4.5V6H4.5C4.10218 6 3.72064 6.15804 3.43934 6.43934C3.15804 6.72064 3 7.10218 3 7.5V19.5C3 19.8978 3.15804 20.2794 3.43934 20.5607C3.72064 20.842 4.10218 21 4.5 21H16.5C16.8978 21 17.2794 20.842 17.5607 20.5607C17.842 20.2794 18 19.8978 18 19.5V18H19.5C19.8978 18 20.2794 17.842 20.5607 17.5607C20.842 17.2794 21 16.8978 21 16.5V4.5C21 4.10218 20.842 3.72064 20.5607 3.43934C20.2794 3.15804 19.8978 3 19.5 3ZM7.5 4.5H19.5V11.0044L17.9344 9.43875C17.6531 9.15766 17.2717 8.99976 16.8741 8.99976C16.4764 8.99976 16.095 9.15766 15.8137 9.43875L8.75344 16.5H7.5V4.5ZM16.5 19.5H4.5V7.5H6V16.5C6 16.8978 6.15804 17.2794 6.43934 17.5607C6.72064 17.842 7.10218 18 7.5 18H16.5V19.5ZM19.5 16.5H10.875L16.875 10.5L19.5 13.125V16.5ZM11.25 10.5C11.695 10.5 12.13 10.368 12.5 10.1208C12.87 9.87357 13.1584 9.52217 13.3287 9.11104C13.499 8.6999 13.5436 8.2475 13.4568 7.81105C13.37 7.37459 13.1557 6.97368 12.841 6.65901C12.5263 6.34434 12.1254 6.13005 11.689 6.04323C11.2525 5.95642 10.8001 6.00097 10.389 6.17127C9.97783 6.34157 9.62643 6.62996 9.37919 6.99997C9.13196 7.36998 9 7.80499 9 8.25C9 8.84674 9.23705 9.41903 9.65901 9.84099C10.081 10.2629 10.6533 10.5 11.25 10.5ZM11.25 7.5C11.3983 7.5 11.5433 7.54399 11.6667 7.6264C11.79 7.70881 11.8861 7.82594 11.9429 7.96299C11.9997 8.10003 12.0145 8.25083 11.9856 8.39632C11.9566 8.5418 11.8852 8.67544 11.7803 8.78033C11.6754 8.88522 11.5418 8.95665 11.3963 8.98559C11.2508 9.01453 11.1 8.99968 10.963 8.94291C10.8259 8.88614 10.7088 8.79001 10.6264 8.66668C10.544 8.54334 10.5 8.39834 10.5 8.25C10.5 8.05109 10.579 7.86032 10.7197 7.71967C10.8603 7.57902 11.0511 7.5 11.25 7.5Z"
94+
fill="white"
95+
/>
96+
</svg>
97+
</div>
98+
<div className="text-[12px] font-[500] !text-white">AI</div>
99+
</div>
100+
</Button>
101+
{value.length >= 30 && !loading && (
102+
<div className="text-[12px] ml-[10px] -mt-[10px] w-[200px] absolute top-[100%] z-[500] left-0 hidden group-hover:block">
103+
<ul className="cursor-pointer rounded-[4px] border border-dashed border-customColor21 mt-[3px] p-[5px] bg-customColor2">
104+
{list.map((p) => (
105+
<li onClick={generateImage(p)} key={p} className="hover:bg-sixth">
106+
{p}
107+
</li>
108+
))}
109+
</ul>
110+
</div>
111+
)}
112+
</div>
113+
);
114+
};

apps/frontend/src/components/launches/providers/high.order.provider.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,7 @@ export const withProvider = function <T extends object>(
474474
<div className="flex">
475475
<div className="flex-1">
476476
<MultiMediaComponent
477+
text={val.content}
477478
label="Attachments"
478479
description=""
479480
name="image"
@@ -558,7 +559,11 @@ export const withProvider = function <T extends object>(
558559
? undefined
559560
: typeof maximumCharacters === 'number'
560561
? maximumCharacters
561-
: maximumCharacters(JSON.parse(integration?.additionalSettings || '[]'))
562+
: maximumCharacters(
563+
JSON.parse(
564+
integration?.additionalSettings || '[]'
565+
)
566+
)
562567
}
563568
/>
564569
) : (
@@ -568,7 +573,11 @@ export const withProvider = function <T extends object>(
568573
? undefined
569574
: typeof maximumCharacters === 'number'
570575
? maximumCharacters
571-
: maximumCharacters(JSON.parse(integration?.additionalSettings || '[]'))
576+
: maximumCharacters(
577+
JSON.parse(
578+
integration?.additionalSettings || '[]'
579+
)
580+
)
572581
}
573582
/>
574583
)

0 commit comments

Comments
 (0)