feat: merge with polonto
This commit is contained in:
commit
1b235652ac
|
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
Controller, Get, Param, Post, Query, Req, Res
|
||||
Body, Controller, Get, Param, Post, Query, Req, Res, UploadedFile, UseInterceptors, UsePipes
|
||||
} from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';
|
||||
|
|
@ -7,11 +7,38 @@ import { Organization } from '@prisma/client';
|
|||
import { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import handleR2Upload from '@gitroom/nestjs-libraries/upload/r2.uploader';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { CustomFileValidationPipe } from '@gitroom/nestjs-libraries/upload/custom.upload.validation';
|
||||
|
||||
@ApiTags('Media')
|
||||
@Controller('/media')
|
||||
export class MediaController {
|
||||
constructor(private _mediaService: MediaService) {}
|
||||
|
||||
@Post('/generate-image')
|
||||
async generateImage(
|
||||
@GetOrgFromRequest() org: Organization,
|
||||
@Req() req: Request,
|
||||
@Body('prompt') prompt: string
|
||||
) {
|
||||
return {output: 'data:image/png;base64,' + await this._mediaService.generateImage(prompt)};
|
||||
}
|
||||
|
||||
@Post('/upload-simple')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
@UsePipes(new CustomFileValidationPipe())
|
||||
async uploadSimple(
|
||||
@GetOrgFromRequest() org: Organization,
|
||||
@UploadedFile('file')
|
||||
file: Express.Multer.File
|
||||
) {
|
||||
const filePath =
|
||||
file.path.indexOf('http') === 0
|
||||
? file.path
|
||||
: file.path.replace(process.env.UPLOAD_DIRECTORY, '');
|
||||
return this._mediaService.saveFile(org.id, file.originalname, filePath);
|
||||
}
|
||||
|
||||
@Post('/:endpoint')
|
||||
// @UseInterceptors(FileInterceptor('file'))
|
||||
// @UsePipes(new CustomFileValidationPipe())
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "./polonto.css";
|
||||
|
||||
body,
|
||||
html {
|
||||
|
|
@ -288,10 +289,22 @@ html {
|
|||
color: white;
|
||||
}
|
||||
|
||||
.editor .polonto * {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.bp5-portal {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
:empty + .existing-empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mantine-Paper-root {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
:root {
|
||||
--copilot-kit-primary-color: #612ad5 !important;
|
||||
--copilot-kit-background-color: #0b0f1c !important;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -434,7 +434,7 @@ export const AddEditModal: FC<{
|
|||
// .getCommands()
|
||||
// .filter((f) => f.name === 'image'),
|
||||
// newImage,
|
||||
postSelector(dateState),
|
||||
// postSelector(dateState),
|
||||
]}
|
||||
value={p.content}
|
||||
preview="edit"
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ export const LinkedinCompany: FC<{
|
|||
const [company, setCompany] = useState<any>(null);
|
||||
|
||||
const getCompany = async () => {
|
||||
if (!company) {
|
||||
return ;
|
||||
}
|
||||
const {options} = await (
|
||||
await fetch('/integrations/function', {
|
||||
method: 'POST',
|
||||
|
|
@ -141,7 +144,7 @@ export const LinkedinCompany: FC<{
|
|||
};
|
||||
|
||||
export const linkedinCompany = (identifier: string, id: string): ICommand[] => {
|
||||
if (identifier !== 'linkedin') {
|
||||
if (identifier !== 'linkedin' && identifier !== 'linkedin-page') {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
import {
|
||||
createContext,
|
||||
FC,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { createStore } from 'polotno/model/store';
|
||||
import Workspace from 'polotno/canvas/workspace';
|
||||
import { PolotnoContainer, SidePanelWrap, WorkspaceWrap } from 'polotno';
|
||||
import { SidePanel, DEFAULT_SECTIONS } from 'polotno/side-panel';
|
||||
import Toolbar from 'polotno/toolbar/toolbar';
|
||||
import ZoomButtons from 'polotno/toolbar/zoom-buttons';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { PictureGeneratorSection } from '@gitroom/frontend/components/launches/polonto/polonto.picture.generation';
|
||||
import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
||||
|
||||
const store = createStore({
|
||||
key: 'Aqml_02mqf6YTKC0jYZ8',
|
||||
showCredit: false,
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
const CloseContext = createContext({ close: {} as any, setMedia: {} as any });
|
||||
|
||||
const ActionControls = ({ store }: any) => {
|
||||
const close = useContext(CloseContext);
|
||||
const [load, setLoad] = useState(false);
|
||||
const fetch = useFetch();
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
loading={load}
|
||||
className="outline-none"
|
||||
innerClassName="invert outline-none"
|
||||
onClick={async () => {
|
||||
setLoad(true);
|
||||
const blob = await store.toBlob();
|
||||
const formData = new FormData();
|
||||
formData.append('file', blob, 'media.png');
|
||||
const data = await (
|
||||
await fetch('/media/upload-simple', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
).json();
|
||||
close.setMedia({ id: data.id, path: data.path });
|
||||
close.close();
|
||||
}}
|
||||
>
|
||||
Use this media
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Polonto: FC<{
|
||||
setMedia: (params: { id: string; path: string }) => void;
|
||||
type?: 'image' | 'video';
|
||||
closeModal: () => void;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}> = (props) => {
|
||||
const { setMedia, type, closeModal } = props;
|
||||
const user = useUser();
|
||||
|
||||
console.log(user);
|
||||
const features = useMemo(() => {
|
||||
return [
|
||||
...DEFAULT_SECTIONS,
|
||||
...(user?.tier?.image_generator ? [PictureGeneratorSection] : []),
|
||||
] as any[];
|
||||
}, [user?.tier?.image_generator]);
|
||||
|
||||
useEffect(() => {
|
||||
store.addPage({
|
||||
width: props.width || 540,
|
||||
height: props.height || 675,
|
||||
});
|
||||
|
||||
return () => {
|
||||
store.clear();
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<div className="fixed left-0 top-0 bg-black/80 z-[300] w-full min-h-full p-[60px] animate-fade">
|
||||
<div className="w-full h-full bg-[#0B101B] border-tableBorder border-2 rounded-xl pb-[20px] px-[20px] relative">
|
||||
<div className="flex">
|
||||
<div className="flex-1">
|
||||
<TopTitle title="Design Media" />
|
||||
</div>
|
||||
<button
|
||||
onClick={closeModal}
|
||||
className="outline-none absolute right-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root bg-black hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="bg-white text-black relative z-[400] polonto">
|
||||
<CloseContext.Provider
|
||||
value={{ close: () => closeModal(), setMedia }}
|
||||
>
|
||||
<PolotnoContainer style={{ width: '100%', height: '1000px' }}>
|
||||
<SidePanelWrap>
|
||||
<SidePanel store={store} sections={features} />
|
||||
</SidePanelWrap>
|
||||
<WorkspaceWrap>
|
||||
<Toolbar
|
||||
store={store}
|
||||
components={{
|
||||
ActionControls,
|
||||
}}
|
||||
/>
|
||||
<Workspace store={store} />
|
||||
<ZoomButtons store={store} />
|
||||
</WorkspaceWrap>
|
||||
</PolotnoContainer>
|
||||
</CloseContext.Provider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Polonto;
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
import React from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { InputGroup, Button } from '@blueprintjs/core';
|
||||
import { Clean } from '@blueprintjs/icons';
|
||||
|
||||
import { SectionTab } from 'polotno/side-panel';
|
||||
import { getKey } from 'polotno/utils/validate-key';
|
||||
import { getImageSize } from 'polotno/utils/image';
|
||||
|
||||
import { ImagesGrid } from 'polotno/side-panel/images-grid';
|
||||
import { getAPI } from 'polotno/utils/api';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
|
||||
const GenerateTab = observer(({ store }: any) => {
|
||||
const inputRef = React.useRef<any>(null);
|
||||
const [image, setImage] = React.useState(null);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const fetch = useFetch();
|
||||
|
||||
const handleGenerate = async () => {
|
||||
setLoading(true);
|
||||
setImage(null);
|
||||
|
||||
const req = await fetch(`/media/generate-image`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
prompt: inputRef.current.value,
|
||||
}),
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
if (!req.ok) {
|
||||
alert('Something went wrong, please try again later...');
|
||||
return;
|
||||
}
|
||||
const data = await req.json();
|
||||
setImage(data.output);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ height: '40px', paddingTop: '5px' }}>
|
||||
Generate image with AI
|
||||
</div>
|
||||
<InputGroup
|
||||
placeholder="Type your image generation prompt here..."
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleGenerate();
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
marginBottom: '20px',
|
||||
}}
|
||||
inputRef={inputRef}
|
||||
/>
|
||||
<Button
|
||||
onClick={handleGenerate}
|
||||
intent="primary"
|
||||
loading={loading}
|
||||
style={{ marginBottom: '40px' }}
|
||||
>
|
||||
Generate
|
||||
</Button>
|
||||
{image && (
|
||||
<ImagesGrid
|
||||
shadowEnabled={false}
|
||||
images={image ? [image] : []}
|
||||
getPreview={(item) => item}
|
||||
isLoading={loading}
|
||||
onSelect={async (item, pos, element) => {
|
||||
const src = item;
|
||||
if (element && element.type === 'svg' && element.contentEditable) {
|
||||
element.set({ maskSrc: src });
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
element &&
|
||||
element.type === 'image' &&
|
||||
element.contentEditable
|
||||
) {
|
||||
element.set({ src: src });
|
||||
return;
|
||||
}
|
||||
|
||||
const { width, height } = await getImageSize(src);
|
||||
const x = (pos?.x || store.width / 2) - width / 2;
|
||||
const y = (pos?.y || store.height / 2) - height / 2;
|
||||
store.activePage?.addElement({
|
||||
type: 'image',
|
||||
src: src,
|
||||
width,
|
||||
height,
|
||||
x,
|
||||
y,
|
||||
});
|
||||
}}
|
||||
rowsNumber={1}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const PictureGeneratorPanel = observer(({ store }: any) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<GenerateTab store={store} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// define the new custom section
|
||||
export const PictureGeneratorSection = {
|
||||
name: 'picture-generator-ai',
|
||||
Tab: (props: any) => (
|
||||
<SectionTab name="AI Img" {...props}>
|
||||
<Clean />
|
||||
</SectionTab>
|
||||
),
|
||||
// we need observer to update component automatically on any store changes
|
||||
Panel: PictureGeneratorPanel,
|
||||
};
|
||||
|
|
@ -49,6 +49,7 @@ export const MediumTags: FC<{
|
|||
<div>
|
||||
<div className={`${interClass} text-[14px] mb-[6px]`}>{label}</div>
|
||||
<ReactTags
|
||||
placeholderText="Add a tag"
|
||||
suggestions={suggestionsArray}
|
||||
selected={tagValue}
|
||||
onAdd={onAddition}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ const YoutubeSettings: FC = () => {
|
|||
<div className="mt-[20px]">
|
||||
<MediaComponent
|
||||
type="image"
|
||||
width={1280}
|
||||
height={720}
|
||||
label="Thumbnail"
|
||||
description="Thumbnail picture (optional)"
|
||||
{...register('thumbnail')}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,12 @@ import clsx from 'clsx';
|
|||
import { VideoFrame } from '@gitroom/react/helpers/video.frame';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { MultipartFileUploader } from '@gitroom/frontend/components/media/new.uploader';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
||||
const Polonto = dynamic(
|
||||
() => import('@gitroom/frontend/components/launches/polonto')
|
||||
);
|
||||
const showModalEmitter = new EventEmitter();
|
||||
|
||||
export const ShowMediaBoxModal: FC = () => {
|
||||
|
|
@ -210,6 +214,7 @@ export const MultiMediaComponent: FC<{
|
|||
}) => void;
|
||||
}> = (props) => {
|
||||
const { name, label, error, description, onChange, value } = props;
|
||||
const user = useUser();
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
setCurrentMedia(value);
|
||||
|
|
@ -217,6 +222,8 @@ export const MultiMediaComponent: FC<{
|
|||
}, []);
|
||||
|
||||
const [modal, setShowModal] = useState(false);
|
||||
const [mediaModal, setMediaModal] = useState(false);
|
||||
|
||||
const [currentMedia, setCurrentMedia] = useState(value);
|
||||
const mediaDirectory = useMediaDirectory();
|
||||
|
||||
|
|
@ -233,6 +240,10 @@ export const MultiMediaComponent: FC<{
|
|||
setShowModal(!modal);
|
||||
}, [modal]);
|
||||
|
||||
const closeDesignModal = useCallback(() => {
|
||||
setMediaModal(false);
|
||||
}, [modal]);
|
||||
|
||||
const clearMedia = useCallback(
|
||||
(topIndex: number) => () => {
|
||||
const newMedia = currentMedia?.filter((f, index) => index !== topIndex);
|
||||
|
|
@ -242,10 +253,17 @@ export const MultiMediaComponent: FC<{
|
|||
[currentMedia]
|
||||
);
|
||||
|
||||
const designMedia = useCallback(() => {
|
||||
setMediaModal(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-[8px] bg-[#131B2C] rounded-bl-[8px]">
|
||||
{modal && <MediaBox setMedia={changeMedia} closeModal={showModal} />}
|
||||
{mediaModal && !!user?.tier?.ai && (
|
||||
<Polonto setMedia={changeMedia} closeModal={closeDesignModal} />
|
||||
)}
|
||||
<div className="flex gap-[10px]">
|
||||
<div className="flex">
|
||||
<Button
|
||||
|
|
@ -268,6 +286,27 @@ export const MultiMediaComponent: FC<{
|
|||
</div>
|
||||
<div className="text-[12px] font-[500]">Insert Media</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={designMedia}
|
||||
className="ml-[10px] rounded-[4px] mb-[10px] gap-[8px] justify-center items-center w-[127px] flex border border-dashed border-[#506490] !bg-[#832ad5]"
|
||||
>
|
||||
<div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
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"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-[12px] font-[500]">Design Media</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{!!currentMedia &&
|
||||
|
|
@ -311,9 +350,12 @@ export const MediaComponent: FC<{
|
|||
target: { name: string; value?: { id: string; path: string } };
|
||||
}) => void;
|
||||
type?: 'image' | 'video';
|
||||
width?: number;
|
||||
height?: number;
|
||||
}> = (props) => {
|
||||
const { name, type, label, description, onChange, value } = props;
|
||||
const { name, type, label, description, onChange, value, width, height } = props;
|
||||
const { getValues } = useSettings();
|
||||
const user = useUser();
|
||||
useEffect(() => {
|
||||
const settings = getValues()[props.name];
|
||||
if (settings) {
|
||||
|
|
@ -321,9 +363,18 @@ export const MediaComponent: FC<{
|
|||
}
|
||||
}, []);
|
||||
const [modal, setShowModal] = useState(false);
|
||||
const [mediaModal, setMediaModal] = useState(false);
|
||||
const [currentMedia, setCurrentMedia] = useState(value);
|
||||
const mediaDirectory = useMediaDirectory();
|
||||
|
||||
const closeDesignModal = useCallback(() => {
|
||||
setMediaModal(false);
|
||||
}, [modal]);
|
||||
|
||||
const showDesignModal = useCallback(() => {
|
||||
setMediaModal(true);
|
||||
}, [modal]);
|
||||
|
||||
const changeMedia = useCallback((m: { path: string; id: string }) => {
|
||||
setCurrentMedia(m);
|
||||
onChange({ target: { name, value: m } });
|
||||
|
|
@ -343,6 +394,14 @@ export const MediaComponent: FC<{
|
|||
{modal && (
|
||||
<MediaBox setMedia={changeMedia} closeModal={showModal} type={type} />
|
||||
)}
|
||||
{mediaModal && !!user?.tier?.ai && (
|
||||
<Polonto
|
||||
width={width}
|
||||
height={height}
|
||||
setMedia={changeMedia}
|
||||
closeModal={closeDesignModal}
|
||||
/>
|
||||
)}
|
||||
<div className="text-[14px]">{label}</div>
|
||||
<div className="text-[12px]">{description}</div>
|
||||
{!!currentMedia && (
|
||||
|
|
@ -354,8 +413,11 @@ export const MediaComponent: FC<{
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex">
|
||||
<div className="flex gap-[5px]">
|
||||
<Button onClick={showModal}>Select</Button>
|
||||
<Button onClick={showDesignModal} className="!bg-[#832AD5]">
|
||||
Editor
|
||||
</Button>
|
||||
<Button secondary={true} onClick={clearMedia}>
|
||||
Clear
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,18 @@
|
|||
import {Injectable} from "@nestjs/common";
|
||||
import {MediaRepository} from "@gitroom/nestjs-libraries/database/prisma/media/media.repository";
|
||||
import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
|
||||
|
||||
@Injectable()
|
||||
export class MediaService {
|
||||
constructor(
|
||||
private _mediaRepository: MediaRepository
|
||||
private _mediaRepository: MediaRepository,
|
||||
private _openAi: OpenaiService
|
||||
){}
|
||||
|
||||
generateImage(prompt: string) {
|
||||
return this._openAi.generateImage(prompt);
|
||||
}
|
||||
|
||||
saveFile(org: string, fileName: string, filePath: string) {
|
||||
return this._mediaRepository.saveFile(org, fileName, filePath);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ export interface PricingInnerInterface {
|
|||
featured_by_gitroom: boolean;
|
||||
ai: boolean;
|
||||
import_from_channels: boolean;
|
||||
image_generator?: boolean;
|
||||
image_generation_count: number;
|
||||
}
|
||||
export interface PricingInterface {
|
||||
[key: string]: PricingInnerInterface;
|
||||
|
|
@ -19,12 +21,14 @@ export const pricing: PricingInterface = {
|
|||
month_price: 0,
|
||||
year_price: 0,
|
||||
channel: 1,
|
||||
image_generation_count: 0,
|
||||
posts_per_month: 30,
|
||||
team_members: false,
|
||||
community_features: false,
|
||||
featured_by_gitroom: false,
|
||||
ai: false,
|
||||
import_from_channels: false,
|
||||
image_generator: false,
|
||||
},
|
||||
STANDARD: {
|
||||
current: 'STANDARD',
|
||||
|
|
@ -32,11 +36,13 @@ export const pricing: PricingInterface = {
|
|||
year_price: 288,
|
||||
channel: 5,
|
||||
posts_per_month: 400,
|
||||
image_generation_count: 20,
|
||||
team_members: false,
|
||||
ai: true,
|
||||
community_features: false,
|
||||
featured_by_gitroom: false,
|
||||
import_from_channels: true,
|
||||
image_generator: false,
|
||||
},
|
||||
PRO: {
|
||||
current: 'PRO',
|
||||
|
|
@ -44,10 +50,12 @@ export const pricing: PricingInterface = {
|
|||
year_price: 384,
|
||||
channel: 8,
|
||||
posts_per_month: 1000000,
|
||||
image_generation_count: 100,
|
||||
community_features: true,
|
||||
team_members: true,
|
||||
featured_by_gitroom: true,
|
||||
ai: true,
|
||||
import_from_channels: true,
|
||||
image_generator: true,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,14 @@ const openai = new OpenAI({
|
|||
|
||||
@Injectable()
|
||||
export class OpenaiService {
|
||||
async generateImage(prompt: string) {
|
||||
return (await openai.images.generate({
|
||||
prompt,
|
||||
response_format: 'b64_json',
|
||||
model: 'dall-e-3',
|
||||
})).data[0].b64_json;
|
||||
}
|
||||
|
||||
async generatePosts(content: string) {
|
||||
const posts = (
|
||||
await Promise.all([
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ export const Button: FC<
|
|||
DetailedHTMLProps<
|
||||
ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
HTMLButtonElement
|
||||
> & { secondary?: boolean; loading?: boolean }
|
||||
> = ({ children, loading, ...props }) => {
|
||||
> & { secondary?: boolean; loading?: boolean; innerClassName?: string }
|
||||
> = ({ children, loading, innerClassName, ...props }) => {
|
||||
const ref = useRef<HTMLButtonElement | null>(null);
|
||||
const [height, setHeight] = useState<number | null>(null);
|
||||
|
||||
|
|
@ -49,6 +49,7 @@ export const Button: FC<
|
|||
)}
|
||||
<div
|
||||
className={clsx(
|
||||
innerClassName,
|
||||
'flex-1 items-center justify-center flex',
|
||||
loading && 'invisible'
|
||||
)}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -90,6 +90,7 @@
|
|||
"next": "^14.2.4",
|
||||
"next-plausible": "^3.12.0",
|
||||
"openai": "^4.47.1",
|
||||
"polotno": "^2.10.5",
|
||||
"prisma-paginate": "^5.2.1",
|
||||
"react": "18.2.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
|
|
|
|||
Loading…
Reference in New Issue