feat: elevans labs
This commit is contained in:
parent
9f9bce6906
commit
05720ec59e
|
|
@ -23,6 +23,7 @@ import { CustomFileValidationPipe } from '@gitroom/nestjs-libraries/upload/custo
|
|||
import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';
|
||||
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
||||
import { SaveMediaInformationDto } from '@gitroom/nestjs-libraries/dtos/media/save.media.information.dto';
|
||||
import { VideoDto } from '@gitroom/nestjs-libraries/dtos/videos/video.dto';
|
||||
|
||||
@ApiTags('Media')
|
||||
@Controller('/media')
|
||||
|
|
@ -108,10 +109,7 @@ export class MediaController {
|
|||
@GetOrgFromRequest() org: Organization,
|
||||
@Body() body: SaveMediaInformationDto
|
||||
) {
|
||||
return this._mediaService.saveMediaInformation(
|
||||
org.id,
|
||||
body
|
||||
);
|
||||
return this._mediaService.saveMediaInformation(org.id, body);
|
||||
}
|
||||
|
||||
@Post('/upload-simple')
|
||||
|
|
@ -167,4 +165,18 @@ export class MediaController {
|
|||
) {
|
||||
return this._mediaService.getMedia(org.id, page);
|
||||
}
|
||||
|
||||
@Get('/video-options')
|
||||
getVideos() {
|
||||
return this._mediaService.getVideoOptions();
|
||||
}
|
||||
|
||||
@Post('/generate-video/:type')
|
||||
generateVideo(
|
||||
@GetOrgFromRequest() org: Organization,
|
||||
@Body() body: VideoDto,
|
||||
@Param('type') type: string,
|
||||
) {
|
||||
return this._mediaService.generateVideo(org.id, body, type);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { ThrottlerModule } from '@nestjs/throttler';
|
|||
import { AgentModule } from '@gitroom/nestjs-libraries/agent/agent.module';
|
||||
import { McpModule } from '@gitroom/backend/mcp/mcp.module';
|
||||
import { ThirdPartyModule } from '@gitroom/nestjs-libraries/3rdparties/thirdparty.module';
|
||||
import { VideoModule } from '@gitroom/nestjs-libraries/videos/video.module';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
|
|
@ -24,6 +25,7 @@ import { ThirdPartyModule } from '@gitroom/nestjs-libraries/3rdparties/thirdpart
|
|||
AgentModule,
|
||||
McpModule,
|
||||
ThirdPartyModule,
|
||||
VideoModule,
|
||||
ThrottlerModule.forRoot([
|
||||
{
|
||||
ttl: 3600000,
|
||||
|
|
|
|||
|
|
@ -1,445 +0,0 @@
|
|||
import { mock } from 'jest-mock-extended';
|
||||
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
||||
import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';
|
||||
import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';
|
||||
import { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';
|
||||
import { PermissionsService } from './permissions.service';
|
||||
import { AuthorizationActions, Sections } from './permissions.service';
|
||||
import { Period, SubscriptionTier } from '@prisma/client';
|
||||
|
||||
// Mock of dependent services
|
||||
const mockSubscriptionService = mock<SubscriptionService>();
|
||||
const mockPostsService = mock<PostsService>();
|
||||
const mockIntegrationService = mock<IntegrationService>();
|
||||
const mockWebHookService = mock<WebhooksService>();
|
||||
|
||||
describe('PermissionsService', () => {
|
||||
let service: PermissionsService;
|
||||
|
||||
// Initial setup before each test
|
||||
beforeEach(() => {
|
||||
process.env.STRIPE_PUBLISHABLE_KEY = 'mock_stripe_key';
|
||||
service = new PermissionsService(
|
||||
mockSubscriptionService,
|
||||
mockPostsService,
|
||||
mockIntegrationService,
|
||||
mockWebHookService
|
||||
);
|
||||
});
|
||||
|
||||
// Reusable mocks for `getPackageOptions`
|
||||
const baseSubscription = {
|
||||
id: 'mock-id',
|
||||
organizationId: 'mock-org-id',
|
||||
subscriptionTier: 'PRO' as SubscriptionTier,
|
||||
identifier: 'mock-identifier',
|
||||
cancelAt: new Date(),
|
||||
period: {} as Period,
|
||||
totalChannels: 5,
|
||||
isLifetime: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
disabled: false,
|
||||
tokenExpiration: new Date(),
|
||||
profile: 'mock-profile',
|
||||
postingTimes: '[]',
|
||||
lastPostedAt: new Date(),
|
||||
};
|
||||
|
||||
const baseOptions = {
|
||||
channel: 10,
|
||||
current: 'mock-current',
|
||||
month_price: 20,
|
||||
year_price: 200,
|
||||
posts_per_month: 100,
|
||||
team_members: true,
|
||||
community_features: true,
|
||||
featured_by_gitroom: true,
|
||||
ai: true,
|
||||
import_from_channels: true,
|
||||
image_generator: false,
|
||||
image_generation_count: 50,
|
||||
public_api: true,
|
||||
webhooks: 10,
|
||||
autoPost: true, // Added the missing property
|
||||
};
|
||||
|
||||
const baseIntegration = {
|
||||
id: 'mock-integration-id',
|
||||
organizationId: 'mock-org-id',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: new Date(),
|
||||
additionalSettings: '{}',
|
||||
refreshNeeded: false,
|
||||
refreshToken: 'mock-refresh-token',
|
||||
name: 'Mock Integration',
|
||||
internalId: 'mock-internal-id',
|
||||
picture: 'mock-picture-url',
|
||||
providerIdentifier: 'mock-provider',
|
||||
token: 'mock-token',
|
||||
type: 'social',
|
||||
inBetweenSteps: false,
|
||||
disabled: false,
|
||||
tokenExpiration: new Date(),
|
||||
profile: 'mock-profile',
|
||||
postingTimes: '[]',
|
||||
lastPostedAt: new Date(),
|
||||
customInstanceDetails: 'mock-details',
|
||||
customerId: 'mock-customer-id',
|
||||
rootInternalId: 'mock-root-id',
|
||||
customer: {
|
||||
id: 'mock-customer-id',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: new Date(),
|
||||
name: 'Mock Customer',
|
||||
orgId: 'mock-org-id',
|
||||
},
|
||||
};
|
||||
|
||||
describe('check()', () => {
|
||||
describe('Verification Bypass (64)', () => {
|
||||
it('Bypass for Empty List', async () => {
|
||||
// Setup: STRIPE_PUBLISHABLE_KEY exists and requestedPermission is empty
|
||||
|
||||
// Execution: call the check method with an empty list of permissions
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'ADMIN',
|
||||
[] // empty requestedPermission
|
||||
);
|
||||
|
||||
// Verification: not requested, no authorization
|
||||
expect(
|
||||
result.cannot(AuthorizationActions.Create, Sections.CHANNEL)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('Bypass for Missing Stripe', async () => {
|
||||
// Setup: STRIPE_PUBLISHABLE_KEY does not exist
|
||||
process.env.STRIPE_PUBLISHABLE_KEY = undefined;
|
||||
// Necessary mock to avoid undefined filter error
|
||||
jest
|
||||
.spyOn(mockIntegrationService, 'getIntegrationsList')
|
||||
.mockResolvedValue([{ ...baseIntegration, refreshNeeded: false }]);
|
||||
// Mock of getPackageOptions (even if not used due to bypass)
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: baseSubscription,
|
||||
options: baseOptions,
|
||||
});
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Read, Sections.CHANNEL],
|
||||
[AuthorizationActions.Create, Sections.AI],
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should allow all requested actions due to the absence of the Stripe key
|
||||
expect(result.can(AuthorizationActions.Read, Sections.CHANNEL)).toBe(
|
||||
true
|
||||
);
|
||||
expect(result.can(AuthorizationActions.Create, Sections.AI)).toBe(true);
|
||||
});
|
||||
|
||||
it('No Bypass', async () => {
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Read, Sections.CHANNEL],
|
||||
[AuthorizationActions.Create, Sections.AI],
|
||||
];
|
||||
// Mock of getPackageOptions to force a scenario without permissions
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: { ...baseSubscription, totalChannels: 0 },
|
||||
options: {
|
||||
...baseOptions,
|
||||
channel: 0,
|
||||
ai: false,
|
||||
},
|
||||
});
|
||||
// Mock of getIntegrationsList for the channel scenario
|
||||
jest
|
||||
.spyOn(mockIntegrationService, 'getIntegrationsList')
|
||||
.mockResolvedValue([{ ...baseIntegration, refreshNeeded: false }]);
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should not allow the requested actions as there is no bypass
|
||||
expect(result.can(AuthorizationActions.Read, Sections.CHANNEL)).toBe(
|
||||
false
|
||||
);
|
||||
expect(result.can(AuthorizationActions.Create, Sections.AI)).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Channel Permission (82/87)', () => {
|
||||
it('All Conditions True', async () => {
|
||||
// Mock of getPackageOptions to set channel limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: { ...baseSubscription, totalChannels: 10 },
|
||||
options: { ...baseOptions, channel: 10 },
|
||||
});
|
||||
|
||||
// Mock of getIntegrationsList to set existing channels
|
||||
jest
|
||||
.spyOn(mockIntegrationService, 'getIntegrationsList')
|
||||
.mockResolvedValue([
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
]);
|
||||
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.CHANNEL],
|
||||
];
|
||||
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should allow the requested action
|
||||
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('Channel With Option Limit', async () => {
|
||||
// Mock of getPackageOptions to set channel limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: { ...baseSubscription, totalChannels: 3 },
|
||||
options: { ...baseOptions, channel: 10 },
|
||||
});
|
||||
// Mock of getIntegrationsList to set existing channels
|
||||
jest
|
||||
.spyOn(mockIntegrationService, 'getIntegrationsList')
|
||||
.mockResolvedValue([
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
]);
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.CHANNEL],
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should allow the requested action
|
||||
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('Channel With Subscription Limit', async () => {
|
||||
// Mock of getPackageOptions to set channel limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: { ...baseSubscription, totalChannels: 10 },
|
||||
options: { ...baseOptions, channel: 3 },
|
||||
});
|
||||
// Mock of getIntegrationsList to set existing channels
|
||||
jest
|
||||
.spyOn(mockIntegrationService, 'getIntegrationsList')
|
||||
.mockResolvedValue([
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
]);
|
||||
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.CHANNEL],
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should allow the requested action
|
||||
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
it('Channel Without Available Limits', async () => {
|
||||
// Mock of getPackageOptions to set channel limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: { ...baseSubscription, totalChannels: 3 },
|
||||
options: { ...baseOptions, channel: 3 },
|
||||
});
|
||||
// Mock of getIntegrationsList to set existing channels
|
||||
jest
|
||||
.spyOn(mockIntegrationService, 'getIntegrationsList')
|
||||
.mockResolvedValue([
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
]);
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.CHANNEL],
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should not allow the requested action
|
||||
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
it('Section Different from Channel', async () => {
|
||||
// Mock of getPackageOptions to set channel limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: { ...baseSubscription, totalChannels: 10 },
|
||||
options: { ...baseOptions, channel: 10 },
|
||||
});
|
||||
// Mock of getIntegrationsList to set existing channels
|
||||
jest
|
||||
.spyOn(mockIntegrationService, 'getIntegrationsList')
|
||||
.mockResolvedValue([
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
]);
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.AI], // Requesting permission for AI instead of CHANNEL
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should not allow the requested action in CHANNEL
|
||||
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('Monthly Posts Permission (97/110)', () => {
|
||||
it('Posts Within Limit', async () => {
|
||||
// Mock of getPackageOptions to set post limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: baseSubscription,
|
||||
options: { ...baseOptions, posts_per_month: 100 },
|
||||
});
|
||||
// Mock of getSubscription
|
||||
jest
|
||||
.spyOn(mockSubscriptionService, 'getSubscription')
|
||||
.mockResolvedValue({
|
||||
...baseSubscription,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
// Mock of countPostsFromDay to return quantity within the limit
|
||||
jest.spyOn(mockPostsService, 'countPostsFromDay').mockResolvedValue(50);
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.POSTS_PER_MONTH],
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should allow the requested action
|
||||
expect(
|
||||
result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)
|
||||
).toBe(true);
|
||||
});
|
||||
it('Posts Exceed Limit', async () => {
|
||||
// Mock of getPackageOptions to set post limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: baseSubscription,
|
||||
options: { ...baseOptions, posts_per_month: 100 },
|
||||
});
|
||||
// Mock of getSubscription
|
||||
jest
|
||||
.spyOn(mockSubscriptionService, 'getSubscription')
|
||||
.mockResolvedValue({
|
||||
...baseSubscription,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
// Mock of countPostsFromDay to return quantity above the limit
|
||||
jest
|
||||
.spyOn(mockPostsService, 'countPostsFromDay')
|
||||
.mockResolvedValue(150);
|
||||
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.POSTS_PER_MONTH],
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should not allow the requested action
|
||||
expect(
|
||||
result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)
|
||||
).toBe(false);
|
||||
});
|
||||
it('Section Different with Posts Within Limit', async () => {
|
||||
// Mock of getPackageOptions to set post limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: baseSubscription,
|
||||
options: { ...baseOptions, posts_per_month: 100 },
|
||||
});
|
||||
// Mock of getSubscription
|
||||
jest
|
||||
.spyOn(mockSubscriptionService, 'getSubscription')
|
||||
.mockResolvedValue({
|
||||
...baseSubscription,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
// Mock of countPostsFromDay to return quantity within the limit
|
||||
jest.spyOn(mockPostsService, 'countPostsFromDay').mockResolvedValue(50);
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.AI], // Requesting permission for AI instead of POSTS_PER_MONTH
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should not allow the requested action in POSTS_PER_MONTH
|
||||
expect(
|
||||
result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -98,7 +98,7 @@ ${type}
|
|||
</svg>
|
||||
</div>
|
||||
<div className="text-[12px] font-[500] !text-current">
|
||||
{t('ai', 'AI')}
|
||||
{t('ai', 'AI')} Image
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,203 @@
|
|||
import { Button } from '@gitroom/react/form/button';
|
||||
import React, { FC, useCallback, useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Loading from 'react-loading';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';
|
||||
import useSWR from 'swr';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
import { timer } from '@gitroom/helpers/utils/timer';
|
||||
|
||||
export const Modal: FC<{
|
||||
close: () => void;
|
||||
value: string;
|
||||
type: any;
|
||||
setLoading: (loading: boolean) => void;
|
||||
onChange: (params: { id: string; path: string }) => void;
|
||||
}> = (props) => {
|
||||
const { type, value, onChange, close, setLoading } = props;
|
||||
const fetch = useFetch();
|
||||
const setLocked = useLaunchStore(state => state.setLocked)
|
||||
|
||||
const generate = useCallback(
|
||||
(output: string) => async () => {
|
||||
setLoading(true);
|
||||
close();
|
||||
setLocked(true);
|
||||
|
||||
await timer(5000);
|
||||
const image = await (
|
||||
await fetch(`/media/generate-video/${type.identifier}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
prompt: [{ type: 'prompt', value }],
|
||||
output: output,
|
||||
}),
|
||||
})
|
||||
).json();
|
||||
|
||||
setLocked(false);
|
||||
setLoading(false);
|
||||
onChange(image);
|
||||
},
|
||||
[type, value]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="text-textColor fixed start-0 top-0 bg-primary/80 z-[300] w-full h-full p-[60px] animate-fade justify-center flex bg-black/50">
|
||||
<div className="flex flex-col w-[500px] h-[250px] bg-sixth border-tableBorder border-2 rounded-xl pb-[20px] px-[20px] relative">
|
||||
<div className="flex">
|
||||
<div className="flex-1">
|
||||
<TopTitle title={'Video Type'} />
|
||||
</div>
|
||||
<button
|
||||
onClick={props.close}
|
||||
className="outline-none absolute end-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root bg-primary 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="mt-[10px] flex h-full w-full justify-center items-center gap-[10px]">
|
||||
<Button onClick={generate('vertical')}>
|
||||
Vertical (Stories, Reels)
|
||||
</Button>
|
||||
<Button onClick={generate('horizontal')}>
|
||||
Horizontal (Portrait)
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AiVideo: FC<{
|
||||
value: string;
|
||||
onChange: (params: { id: string; path: string }) => void;
|
||||
}> = (props) => {
|
||||
const t = useT();
|
||||
const { value, onChange } = props;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [type, setType] = useState<any | null>(null);
|
||||
const [modal, setModal] = useState(false);
|
||||
const fetch = useFetch();
|
||||
|
||||
const loadVideoList = useCallback(async () => {
|
||||
return (await (await fetch('/media/video-options')).json()).filter(
|
||||
(f: any) => f.placement === 'text-to-image'
|
||||
);
|
||||
}, []);
|
||||
|
||||
const { isLoading, data } = useSWR('load-videos-ai', loadVideoList, {
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
refreshWhenHidden: false,
|
||||
revalidateIfStale: false,
|
||||
refreshWhenOffline: false,
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
const generateVideo = useCallback(
|
||||
(type: { identifier: string }) => async () => {
|
||||
setType(type);
|
||||
setModal(true);
|
||||
},
|
||||
[value, onChange]
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{modal && (
|
||||
<Modal
|
||||
onChange={onChange}
|
||||
setLoading={setLoading}
|
||||
close={() => {
|
||||
setModal(false);
|
||||
setType(null);
|
||||
}}
|
||||
type={type}
|
||||
value={props.value}
|
||||
/>
|
||||
)}
|
||||
<div className="relative group">
|
||||
<Button
|
||||
{...(value.length < 30
|
||||
? {
|
||||
'data-tooltip-id': 'tooltip',
|
||||
'data-tooltip-content':
|
||||
'Please add at least 30 characters to generate AI video',
|
||||
}
|
||||
: {})}
|
||||
className={clsx(
|
||||
'relative ms-[10px] rounded-[4px] gap-[8px] !text-primary justify-center items-center flex border border-dashed border-customColor21 bg-input',
|
||||
value.length < 30 && 'opacity-25'
|
||||
)}
|
||||
>
|
||||
{loading && (
|
||||
<div className="absolute start-[50%] -translate-x-[50%]">
|
||||
<Loading height={30} width={30} type="spin" color="#fff" />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={clsx(
|
||||
'flex gap-[5px] items-center',
|
||||
loading && 'invisible'
|
||||
)}
|
||||
>
|
||||
<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="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="text-[12px] font-[500] !text-current">
|
||||
{t('ai', 'AI')} Video
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
{value.length >= 30 && !loading && (
|
||||
<div className="text-[12px] ms-[10px] -mt-[10px] w-[200px] absolute top-[100%] z-[500] start-0 hidden group-hover:block">
|
||||
<ul className="cursor-pointer rounded-[4px] border border-dashed border-customColor21 mt-[3px] p-[5px] bg-customColor2">
|
||||
{data.map((p: any) => (
|
||||
<li
|
||||
onClick={generateVideo(p)}
|
||||
key={p}
|
||||
className="hover:bg-sixth"
|
||||
>
|
||||
{p.title}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -32,6 +32,7 @@ import { ThirdPartyMedia } from '@gitroom/frontend/components/third-parties/thir
|
|||
import { ReactSortable } from 'react-sortablejs';
|
||||
import { useMediaSettings } from '@gitroom/frontend/components/launches/helpers/media.settings.component';
|
||||
import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';
|
||||
import { AiVideo } from '@gitroom/frontend/components/launches/ai.video';
|
||||
const Polonto = dynamic(
|
||||
() => import('@gitroom/frontend/components/launches/polonto')
|
||||
);
|
||||
|
|
@ -710,7 +711,10 @@ export const MultiMediaComponent: FC<{
|
|||
<ThirdPartyMedia allData={allData} onChange={changeMedia} />
|
||||
|
||||
{!!user?.tier?.ai && (
|
||||
<AiImage value={text} onChange={changeMedia} />
|
||||
<>
|
||||
<AiImage value={text} onChange={changeMedia} />
|
||||
<AiVideo value={text} onChange={changeMedia} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import { SetsService } from '@gitroom/nestjs-libraries/database/prisma/sets/sets
|
|||
import { SetsRepository } from '@gitroom/nestjs-libraries/database/prisma/sets/sets.repository';
|
||||
import { ThirdPartyRepository } from '@gitroom/nestjs-libraries/database/prisma/third-party/third-party.repository';
|
||||
import { ThirdPartyService } from '@gitroom/nestjs-libraries/database/prisma/third-party/third-party.service';
|
||||
import { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
|
|
@ -86,6 +87,7 @@ import { ThirdPartyService } from '@gitroom/nestjs-libraries/database/prisma/thi
|
|||
SetsRepository,
|
||||
ThirdPartyRepository,
|
||||
ThirdPartyService,
|
||||
VideoManager,
|
||||
],
|
||||
get exports() {
|
||||
return this.providers;
|
||||
|
|
|
|||
|
|
@ -4,13 +4,19 @@ import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
|
|||
import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';
|
||||
import { Organization } from '@prisma/client';
|
||||
import { SaveMediaInformationDto } from '@gitroom/nestjs-libraries/dtos/media/save.media.information.dto';
|
||||
import { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';
|
||||
import { VideoDto } from '@gitroom/nestjs-libraries/dtos/videos/video.dto';
|
||||
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
||||
|
||||
@Injectable()
|
||||
export class MediaService {
|
||||
private storage = UploadFactory.createStorage();
|
||||
|
||||
constructor(
|
||||
private _mediaRepository: MediaRepository,
|
||||
private _openAi: OpenaiService,
|
||||
private _subscriptionService: SubscriptionService
|
||||
private _subscriptionService: SubscriptionService,
|
||||
private _videoManager: VideoManager
|
||||
) {}
|
||||
|
||||
async deleteMedia(org: string, id: string) {
|
||||
|
|
@ -49,4 +55,20 @@ export class MediaService {
|
|||
saveMediaInformation(org: string, data: SaveMediaInformationDto) {
|
||||
return this._mediaRepository.saveMediaInformation(org, data);
|
||||
}
|
||||
|
||||
getVideoOptions() {
|
||||
return this._videoManager.getAllVideos();
|
||||
}
|
||||
|
||||
async generateVideo(org: string, body: VideoDto, type: string) {
|
||||
const video = this._videoManager.getVideoByName(type);
|
||||
if (!video) {
|
||||
throw new Error(`Video type ${type} not found`);
|
||||
}
|
||||
|
||||
const loadedData = await video.instance.process(body.prompt, body.output);
|
||||
|
||||
// const file = await this.storage.uploadSimple(loadedData);
|
||||
// return this.saveFile(org, file.split('/').pop(), file);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export interface PricingInnerInterface {
|
|||
import_from_channels: boolean;
|
||||
image_generator?: boolean;
|
||||
image_generation_count: number;
|
||||
generate_videos: number;
|
||||
public_api: boolean;
|
||||
webhooks: number;
|
||||
autoPost: boolean;
|
||||
|
|
@ -35,6 +36,7 @@ export const pricing: PricingInterface = {
|
|||
public_api: false,
|
||||
webhooks: 0,
|
||||
autoPost: false,
|
||||
generate_videos: 0,
|
||||
},
|
||||
STANDARD: {
|
||||
current: 'STANDARD',
|
||||
|
|
@ -52,6 +54,7 @@ export const pricing: PricingInterface = {
|
|||
public_api: true,
|
||||
webhooks: 2,
|
||||
autoPost: false,
|
||||
generate_videos: 3,
|
||||
},
|
||||
TEAM: {
|
||||
current: 'TEAM',
|
||||
|
|
@ -69,6 +72,7 @@ export const pricing: PricingInterface = {
|
|||
public_api: true,
|
||||
webhooks: 10,
|
||||
autoPost: true,
|
||||
generate_videos: 10,
|
||||
},
|
||||
PRO: {
|
||||
current: 'PRO',
|
||||
|
|
@ -86,6 +90,7 @@ export const pricing: PricingInterface = {
|
|||
public_api: true,
|
||||
webhooks: 30,
|
||||
autoPost: true,
|
||||
generate_videos: 50,
|
||||
},
|
||||
ULTIMATE: {
|
||||
current: 'ULTIMATE',
|
||||
|
|
@ -103,5 +108,6 @@ export const pricing: PricingInterface = {
|
|||
public_api: true,
|
||||
webhooks: 10000,
|
||||
autoPost: true,
|
||||
generate_videos: 100,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsIn, IsString, ValidateNested } from 'class-validator';
|
||||
|
||||
export class Prompt {
|
||||
@IsIn(['prompt', 'image'])
|
||||
type: 'prompt' | 'image';
|
||||
|
||||
@IsString()
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class VideoDto {
|
||||
@ValidateNested({ each: true })
|
||||
@IsArray()
|
||||
@Type(() => Prompt)
|
||||
prompt: Prompt[];
|
||||
|
||||
@IsIn(['vertical', 'horizontal'])
|
||||
output: 'vertical' | 'horizontal';
|
||||
}
|
||||
|
|
@ -18,12 +18,13 @@ const VoicePrompt = z.object({
|
|||
|
||||
@Injectable()
|
||||
export class OpenaiService {
|
||||
async generateImage(prompt: string, isUrl: boolean) {
|
||||
async generateImage(prompt: string, isUrl: boolean, isVertical = false) {
|
||||
const generate = (
|
||||
await openai.images.generate({
|
||||
prompt,
|
||||
response_format: isUrl ? 'url' : 'b64_json',
|
||||
model: 'dall-e-3',
|
||||
...(isVertical ? { size: '1024x1792' } : {}),
|
||||
})
|
||||
).data[0];
|
||||
|
||||
|
|
@ -224,4 +225,36 @@ export class OpenaiService {
|
|||
),
|
||||
};
|
||||
}
|
||||
|
||||
async generateSlidesFromText(text: string) {
|
||||
const message = `You are an assistant that takes a text and break it into slides, each slide should have an image prompt and voice text to be later used to generate a video and voice, image prompt should capture the essence of the slide, generate between 3-5 slides maximum`;
|
||||
return (
|
||||
(
|
||||
await openai.beta.chat.completions.parse({
|
||||
model: 'gpt-4.1',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: message,
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: text,
|
||||
},
|
||||
],
|
||||
response_format: zodResponseFormat(
|
||||
z.object({
|
||||
slides: z.array(
|
||||
z.object({
|
||||
imagePrompt: z.string(),
|
||||
voiceText: z.string(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
'slides'
|
||||
),
|
||||
})
|
||||
).choices[0].message.parsed?.slides || []
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,207 @@
|
|||
import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
|
||||
import {
|
||||
Prompt,
|
||||
Video,
|
||||
VideoAbstract,
|
||||
} from '@gitroom/nestjs-libraries/videos/video.interface';
|
||||
import { chunk } from 'lodash';
|
||||
import Transloadit from 'transloadit';
|
||||
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
||||
import { Readable } from 'stream';
|
||||
import { parseBuffer } from 'music-metadata';
|
||||
import { stringifySync } from 'subtitle';
|
||||
|
||||
const transloadit = new Transloadit({
|
||||
authKey: process.env.TRANSLOADIT_AUTH,
|
||||
authSecret: process.env.TRANSLOADIT_SECRET,
|
||||
});
|
||||
|
||||
async function getAudioDuration(buffer: Buffer): Promise<number> {
|
||||
const metadata = await parseBuffer(buffer, 'audio/mpeg');
|
||||
return metadata.format.duration || 0;
|
||||
}
|
||||
|
||||
@Video({
|
||||
identifier: 'image-text-slides',
|
||||
title: 'Image Text Slides',
|
||||
description: 'Generate videos slides from images and text',
|
||||
placement: 'text-to-image',
|
||||
})
|
||||
export class ImagesSlides extends VideoAbstract {
|
||||
private storage = UploadFactory.createStorage();
|
||||
constructor(private _openaiService: OpenaiService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async process(
|
||||
prompt: Prompt[],
|
||||
output: 'vertical' | 'horizontal'
|
||||
): Promise<string> {
|
||||
const list = await this._openaiService.generateSlidesFromText(
|
||||
prompt[0].value
|
||||
);
|
||||
const generated = await Promise.all(
|
||||
list.reduce((all, current) => {
|
||||
all.push(
|
||||
new Promise(async (res) => {
|
||||
res({
|
||||
len: 0,
|
||||
url: await this._openaiService.generateImage(
|
||||
current.imagePrompt +
|
||||
(output === 'vertical' ? ', vertical composition' : ''),
|
||||
true,
|
||||
output === 'vertical'
|
||||
),
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
all.push(
|
||||
new Promise(async (res) => {
|
||||
const buffer = Buffer.from(
|
||||
await (
|
||||
await fetch(
|
||||
`https://api.elevenlabs.io/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb?output_format=mp3_44100_128`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'xi-api-key': process.env.ELEVENSLABS_API_KEY || '',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
text: current.voiceText,
|
||||
voice_settings: {
|
||||
stability: 0.75,
|
||||
similarity_boost: 0.75,
|
||||
},
|
||||
}),
|
||||
}
|
||||
)
|
||||
).arrayBuffer()
|
||||
);
|
||||
|
||||
const { path } = await this.storage.uploadFile({
|
||||
buffer,
|
||||
mimetype: 'audio/mp3',
|
||||
size: buffer.length,
|
||||
path: '',
|
||||
fieldname: '',
|
||||
destination: '',
|
||||
stream: new Readable(),
|
||||
filename: '',
|
||||
originalname: '',
|
||||
encoding: '',
|
||||
});
|
||||
|
||||
res({
|
||||
len: await getAudioDuration(buffer),
|
||||
url:
|
||||
path.indexOf('http') === -1
|
||||
? process.env.FRONTEND_URL +
|
||||
'/' +
|
||||
process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +
|
||||
path
|
||||
: path,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return all;
|
||||
}, [] as Promise<any>[])
|
||||
);
|
||||
|
||||
const split = chunk(generated, 2);
|
||||
|
||||
const srt = stringifySync(
|
||||
list
|
||||
.reduce((all, current, index) => {
|
||||
const start = all.length ? all[all.length - 1].end : 0;
|
||||
const end = start + split[index][1].len * 1000 + 1000;
|
||||
all.push({
|
||||
start: start,
|
||||
end: end,
|
||||
text: current.voiceText,
|
||||
});
|
||||
|
||||
return all;
|
||||
}, [] as { start: number; end: number; text: string }[])
|
||||
.map((item) => ({
|
||||
type: 'cue',
|
||||
data: item,
|
||||
})),
|
||||
{ format: 'SRT' }
|
||||
);
|
||||
console.log(srt);
|
||||
|
||||
await transloadit.createAssembly({
|
||||
uploads: {
|
||||
'subtitles.srt': srt,
|
||||
},
|
||||
params: {
|
||||
steps: {
|
||||
...split.reduce((all, current, index) => {
|
||||
all[`image${index}`] = {
|
||||
robot: '/http/import',
|
||||
url: current[0].url,
|
||||
};
|
||||
all[`audio${index}`] = {
|
||||
robot: '/http/import',
|
||||
url: current[1].url,
|
||||
};
|
||||
all[`merge${index}`] = {
|
||||
use: [
|
||||
{
|
||||
name: `image${index}`,
|
||||
as: 'image',
|
||||
},
|
||||
{
|
||||
name: `audio${index}`,
|
||||
as: 'audio',
|
||||
},
|
||||
],
|
||||
robot: '/video/merge',
|
||||
duration: current[1].len + 1,
|
||||
audio_delay: 0.5,
|
||||
preset: 'hls-1080p',
|
||||
resize_strategy: 'min_fit',
|
||||
loop: true,
|
||||
};
|
||||
return all;
|
||||
}, {} as any),
|
||||
concatenated: {
|
||||
robot: '/video/concat',
|
||||
result: false,
|
||||
video_fade_seconds: 0.5,
|
||||
use: split.map((p, index) => ({
|
||||
name: `merge${index}`,
|
||||
as: `video_${index + 1}`,
|
||||
})),
|
||||
},
|
||||
subtitled: {
|
||||
robot: '/video/subtitle',
|
||||
result: true,
|
||||
preset: 'hls-1080p',
|
||||
use: {
|
||||
bundle_steps: true,
|
||||
steps: [
|
||||
{
|
||||
name: 'concatenated',
|
||||
as: 'video',
|
||||
},
|
||||
{
|
||||
name: ':original',
|
||||
as: 'subtitles',
|
||||
},
|
||||
],
|
||||
},
|
||||
position: 'center',
|
||||
font_size: 10,
|
||||
subtitles_type: 'burned',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
export interface Prompt {
|
||||
type: 'prompt' | 'image';
|
||||
value: string;
|
||||
}
|
||||
|
||||
export abstract class VideoAbstract {
|
||||
abstract process(
|
||||
prompt: Prompt[],
|
||||
output: 'vertical' | 'horizontal'
|
||||
): Promise<string>;
|
||||
}
|
||||
|
||||
export interface VideoParams {
|
||||
identifier: string;
|
||||
title: string;
|
||||
description: string;
|
||||
placement: 'text-to-image' | 'image-to-video' | 'video-to-video';
|
||||
}
|
||||
|
||||
export function Video(params: VideoParams) {
|
||||
return function (target: any) {
|
||||
// Apply @Injectable decorator to the target class
|
||||
Injectable()(target);
|
||||
|
||||
// Retrieve existing metadata or initialize an empty array
|
||||
const existingMetadata = Reflect.getMetadata('video', VideoAbstract) || [];
|
||||
|
||||
// Add the metadata information for this method
|
||||
existingMetadata.push({ target, ...params });
|
||||
|
||||
// Define metadata on the class prototype (so it can be retrieved from the class)
|
||||
Reflect.defineMetadata('video', existingMetadata, VideoAbstract);
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
|
||||
import {
|
||||
VideoAbstract,
|
||||
VideoParams,
|
||||
} from '@gitroom/nestjs-libraries/videos/video.interface';
|
||||
|
||||
@Injectable()
|
||||
export class VideoManager {
|
||||
constructor(private _moduleRef: ModuleRef) {}
|
||||
|
||||
getAllVideos(): any[] {
|
||||
return (Reflect.getMetadata('video', VideoAbstract) || []).map(
|
||||
(p: any) => ({
|
||||
identifier: p.identifier,
|
||||
title: p.title,
|
||||
description: p.description,
|
||||
placement: p.placement,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
getVideoByName(
|
||||
identifier: string
|
||||
): (VideoParams & { instance: VideoAbstract }) | undefined {
|
||||
const video = (Reflect.getMetadata('video', VideoAbstract) || []).find(
|
||||
(p: any) => p.identifier === identifier
|
||||
);
|
||||
|
||||
return {
|
||||
...video,
|
||||
instance: this._moduleRef.get(video.target, {
|
||||
strict: false,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Global, Module } from '@nestjs/common';
|
||||
import { ImagesSlides } from '@gitroom/nestjs-libraries/videos/images-slides/images.slides';
|
||||
import { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [ImagesSlides, VideoManager],
|
||||
get exports() {
|
||||
return this.providers;
|
||||
},
|
||||
})
|
||||
export class VideoModule {}
|
||||
|
|
@ -159,6 +159,7 @@
|
|||
"mime": "^3.0.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"music-metadata": "^11.6.0",
|
||||
"nestjs-command": "^3.1.4",
|
||||
"nestjs-real-ip": "^3.0.1",
|
||||
"next": "^14.2.14",
|
||||
|
|
@ -197,12 +198,14 @@
|
|||
"simple-statistics": "^7.8.3",
|
||||
"stripe": "^15.5.0",
|
||||
"striptags": "^3.2.0",
|
||||
"subtitle": "4.2.2-alpha.0",
|
||||
"sweetalert2": "11.4.8",
|
||||
"swr": "^2.2.5",
|
||||
"tailwind-scrollbar": "^3.1.0",
|
||||
"tailwindcss": "3.4.17",
|
||||
"tailwindcss-rtl": "^0.9.0",
|
||||
"tldts": "^6.1.47",
|
||||
"transloadit": "^3.0.2",
|
||||
"tslib": "^2.3.0",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"twitter-api-v2": "^1.23.2",
|
||||
|
|
|
|||
267
pnpm-lock.yaml
267
pnpm-lock.yaml
|
|
@ -248,7 +248,7 @@ importers:
|
|||
version: 4.0.0
|
||||
axios:
|
||||
specifier: ^1.7.7
|
||||
version: 1.9.0(debug@4.4.0)
|
||||
version: 1.9.0
|
||||
bcrypt:
|
||||
specifier: ^5.1.1
|
||||
version: 5.1.1
|
||||
|
|
@ -354,6 +354,9 @@ importers:
|
|||
multer:
|
||||
specifier: ^1.4.5-lts.1
|
||||
version: 1.4.5-lts.2
|
||||
music-metadata:
|
||||
specifier: ^11.6.0
|
||||
version: 11.6.0
|
||||
nestjs-command:
|
||||
specifier: ^3.1.4
|
||||
version: 3.1.5(@nestjs/common@10.4.17(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.17)(yargs@17.7.2)
|
||||
|
|
@ -468,6 +471,9 @@ importers:
|
|||
striptags:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.0
|
||||
subtitle:
|
||||
specifier: 4.2.2-alpha.0
|
||||
version: 4.2.2-alpha.0
|
||||
sweetalert2:
|
||||
specifier: 11.4.8
|
||||
version: 11.4.8
|
||||
|
|
@ -486,6 +492,9 @@ importers:
|
|||
tldts:
|
||||
specifier: ^6.1.47
|
||||
version: 6.1.86
|
||||
transloadit:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
tslib:
|
||||
specifier: ^2.3.0
|
||||
version: 2.8.1
|
||||
|
|
@ -6227,6 +6236,9 @@ packages:
|
|||
'@types/multer@1.4.12':
|
||||
resolution: {integrity: sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==}
|
||||
|
||||
'@types/multipipe@3.0.5':
|
||||
resolution: {integrity: sha512-mHBbV67bsmUtLtio0gj/GPzGsjv+Y6K1ff/48iR6YAfFfLkBtRIR0M5lZPbkMCyHGrCZM9p3VNnfY1QCws4t4w==}
|
||||
|
||||
'@types/node-fetch@2.6.12':
|
||||
resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==}
|
||||
|
||||
|
|
@ -6282,6 +6294,9 @@ packages:
|
|||
'@types/react@18.3.1':
|
||||
resolution: {integrity: sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==}
|
||||
|
||||
'@types/readable-stream@4.0.21':
|
||||
resolution: {integrity: sha512-19eKVv9tugr03IgfXlA9UVUVRbW6IuqRO5B92Dl4a6pT7K8uaGrNS0GkxiZD0BOk6PLuXl5FhWl//eX/pzYdTQ==}
|
||||
|
||||
'@types/redis@2.8.32':
|
||||
resolution: {integrity: sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==}
|
||||
|
||||
|
|
@ -7037,6 +7052,10 @@ packages:
|
|||
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
|
||||
aggregate-error@3.1.0:
|
||||
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
ai@4.3.13:
|
||||
resolution: {integrity: sha512-cC5HXItuOwGykSMacCPzNp6+NMTxeuTjOenztVgSJhdC9Z4OrzBxwkyeDAf4h1QP938ZFi7IBdq3u4lxVoVmvw==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -7814,6 +7833,10 @@ packages:
|
|||
classnames@2.5.1:
|
||||
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
||||
|
||||
clean-stack@2.2.0:
|
||||
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
cli-cursor@3.1.0:
|
||||
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -8405,6 +8428,15 @@ packages:
|
|||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.4.1:
|
||||
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
decamelize@1.2.0:
|
||||
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -8689,6 +8721,9 @@ packages:
|
|||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
duplexer2@0.1.4:
|
||||
resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==}
|
||||
|
||||
duplexer@0.1.2:
|
||||
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
|
||||
|
||||
|
|
@ -9301,6 +9336,10 @@ packages:
|
|||
resolution: {integrity: sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
file-type@21.0.0:
|
||||
resolution: {integrity: sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
file-type@3.9.0:
|
||||
resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -9439,6 +9478,10 @@ packages:
|
|||
resolution: {integrity: sha512-XHIrMD0NpDrNM/Ckf7XJiBbLl57KEhT3+i3yY+eWm+cqYZJQTZrKo8Y8AWKnuV5GT4scfuUGt9LzNoIx3dU1nQ==}
|
||||
engines: {node: '>= 0.12'}
|
||||
|
||||
form-data@3.0.3:
|
||||
resolution: {integrity: sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
form-data@4.0.0:
|
||||
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
|
||||
engines: {node: '>= 6'}
|
||||
|
|
@ -9470,6 +9513,9 @@ packages:
|
|||
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
from2@2.3.0:
|
||||
resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==}
|
||||
|
||||
front-matter@4.0.2:
|
||||
resolution: {integrity: sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==}
|
||||
|
||||
|
|
@ -10133,6 +10179,10 @@ packages:
|
|||
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
||||
engines: {node: '>=0.8.19'}
|
||||
|
||||
indent-string@4.0.0:
|
||||
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
inflight@1.0.6:
|
||||
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
|
||||
|
|
@ -10175,6 +10225,10 @@ packages:
|
|||
resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
into-stream@6.0.0:
|
||||
resolution: {integrity: sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
invariant@2.2.4:
|
||||
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
|
||||
|
||||
|
|
@ -10721,6 +10775,9 @@ packages:
|
|||
jpeg-exif@1.1.4:
|
||||
resolution: {integrity: sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==}
|
||||
|
||||
js-base64@2.6.4:
|
||||
resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==}
|
||||
|
||||
js-base64@3.7.7:
|
||||
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
|
||||
|
||||
|
|
@ -12025,6 +12082,13 @@ packages:
|
|||
multiformats@9.9.0:
|
||||
resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==}
|
||||
|
||||
multipipe@4.0.0:
|
||||
resolution: {integrity: sha512-jzcEAzFXoWwWwUbvHCNPwBlTz3WCWe/jPcXSmTfbo/VjRwRTfvLZ/bdvtiTdqCe8d4otCSsPCbhGYcX+eggpKQ==}
|
||||
|
||||
music-metadata@11.6.0:
|
||||
resolution: {integrity: sha512-l7MbWpuGM5GK8gol22L9tou8d/IoFyS8dnsfLbO6cocjlyMwgyLaCIqdwhp4sN1Nzz/Ql/K9kRLvRJDCVKjO3g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
mustache@4.2.0:
|
||||
resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
|
||||
hasBin: true
|
||||
|
|
@ -12482,6 +12546,10 @@ packages:
|
|||
resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
p-is-promise@3.0.0:
|
||||
resolution: {integrity: sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
p-limit@2.3.0:
|
||||
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
@ -12506,6 +12574,10 @@ packages:
|
|||
resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
p-map@4.0.0:
|
||||
resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
p-queue@6.6.2:
|
||||
resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -13128,6 +13200,10 @@ packages:
|
|||
prop-types@15.8.1:
|
||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||
|
||||
proper-lockfile@2.0.1:
|
||||
resolution: {integrity: sha512-rjaeGbsmhNDcDInmwi4MuI6mRwJu6zq8GjYCLuSuE7GF+4UjgzkL69sVKKJ2T2xH61kK7rXvGYpvaTu909oXaQ==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
proper-lockfile@4.1.2:
|
||||
resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==}
|
||||
|
||||
|
|
@ -13830,6 +13906,9 @@ packages:
|
|||
peerDependencies:
|
||||
axios: '*'
|
||||
|
||||
retry@0.10.1:
|
||||
resolution: {integrity: sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ==}
|
||||
|
||||
retry@0.12.0:
|
||||
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
|
||||
engines: {node: '>= 4'}
|
||||
|
|
@ -14308,6 +14387,9 @@ packages:
|
|||
resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
split2@3.2.2:
|
||||
resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==}
|
||||
|
||||
split2@4.2.0:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||
engines: {node: '>= 10.x'}
|
||||
|
|
@ -14499,6 +14581,10 @@ packages:
|
|||
resolution: {integrity: sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
strtok3@10.3.1:
|
||||
resolution: {integrity: sha512-3JWEZM6mfix/GCJBBUrkA8p2Id2pBkyTkVCJKto55w080QBKZ+8R171fGrbiSp+yMO/u6F8/yUh7K4V9K+YCnw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
strtok3@6.3.0:
|
||||
resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -14563,6 +14649,10 @@ packages:
|
|||
resolution: {integrity: sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==}
|
||||
hasBin: true
|
||||
|
||||
subtitle@4.2.2-alpha.0:
|
||||
resolution: {integrity: sha512-IMS+L8lXjOLveg5BC/bVZy+36/x2NqMIQmVDhbquDpxLnXugzmz7/yHHFZ7b9YLfqNaBdXwh1lsnAds3g1FnCQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
sucrase@3.35.0:
|
||||
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
|
@ -14819,6 +14909,10 @@ packages:
|
|||
resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
token-types@6.0.3:
|
||||
resolution: {integrity: sha512-IKJ6EzuPPWtKtEIEPpIdXv9j5j2LGJEYk0CKY2efgKoYKLBiZdh6iQkLVBow/CB3phyWAWCyk+bZeaimJn6uRQ==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
toposort@2.0.2:
|
||||
resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==}
|
||||
|
||||
|
|
@ -14853,6 +14947,10 @@ packages:
|
|||
resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
transloadit@3.0.2:
|
||||
resolution: {integrity: sha512-FvhKs0EBiQufK29irGLM/4aMIrfU5S/TiHB3h+DcO2hjRnVVM2WC278UQJCrNO4L/REE8IKWx/mQzQW2MrrLsg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
tree-dump@1.0.2:
|
||||
resolution: {integrity: sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==}
|
||||
engines: {node: '>=10.0'}
|
||||
|
|
@ -14998,6 +15096,9 @@ packages:
|
|||
tunnel-agent@0.6.0:
|
||||
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
||||
|
||||
tus-js-client@2.3.2:
|
||||
resolution: {integrity: sha512-5a2rm7gp+G7Z+ZB0AO4PzD/dwczB3n1fZeWO5W8AWLJ12RRk1rY4Aeb2VAYX9oKGE+/rGPrdxoFPA/vDSVKnpg==}
|
||||
|
||||
tus-js-client@4.3.1:
|
||||
resolution: {integrity: sha512-ZLeYmjrkaU1fUsKbIi8JML52uAocjEZtBx4DKjRrqzrZa0O4MYwT6db+oqePlspV+FxXJAyFBc/L5gwUi2OFsg==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -19134,7 +19235,7 @@ snapshots:
|
|||
'@module-federation/third-party-dts-extractor': 0.6.16
|
||||
adm-zip: 0.5.16
|
||||
ansi-colors: 4.1.3
|
||||
axios: 1.9.0(debug@4.4.0)
|
||||
axios: 1.9.0
|
||||
chalk: 3.0.0
|
||||
fs-extra: 9.1.0
|
||||
isomorphic-ws: 5.0.0(ws@8.18.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))
|
||||
|
|
@ -23505,6 +23606,10 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/express': 5.0.1
|
||||
|
||||
'@types/multipipe@3.0.5':
|
||||
dependencies:
|
||||
'@types/node': 18.16.9
|
||||
|
||||
'@types/node-fetch@2.6.12':
|
||||
dependencies:
|
||||
'@types/node': 18.16.9
|
||||
|
|
@ -23562,6 +23667,10 @@ snapshots:
|
|||
'@types/prop-types': 15.7.14
|
||||
csstype: 3.1.3
|
||||
|
||||
'@types/readable-stream@4.0.21':
|
||||
dependencies:
|
||||
'@types/node': 18.16.9
|
||||
|
||||
'@types/redis@2.8.32':
|
||||
dependencies:
|
||||
'@types/node': 18.16.9
|
||||
|
|
@ -24772,7 +24881,7 @@ snapshots:
|
|||
|
||||
'@wyw-in-js/shared@0.5.5':
|
||||
dependencies:
|
||||
debug: 4.4.0(supports-color@5.5.0)
|
||||
debug: 4.4.1
|
||||
find-up: 5.0.0
|
||||
minimatch: 9.0.5
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -24883,6 +24992,11 @@ snapshots:
|
|||
dependencies:
|
||||
humanize-ms: 1.2.1
|
||||
|
||||
aggregate-error@3.1.0:
|
||||
dependencies:
|
||||
clean-stack: 2.2.0
|
||||
indent-string: 4.0.0
|
||||
|
||||
ai@4.3.13(react@18.3.1)(zod@3.24.4):
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.1.3
|
||||
|
|
@ -25177,7 +25291,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
axios@1.9.0(debug@4.4.0):
|
||||
axios@1.9.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.9(debug@4.4.0)
|
||||
form-data: 4.0.2
|
||||
|
|
@ -25185,6 +25299,14 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
axios@1.9.0(debug@4.4.1):
|
||||
dependencies:
|
||||
follow-redirects: 1.15.9(debug@4.4.1)
|
||||
form-data: 4.0.2
|
||||
proxy-from-env: 1.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
axobject-query@3.2.4: {}
|
||||
|
||||
axobject-query@4.1.0: {}
|
||||
|
|
@ -25839,6 +25961,8 @@ snapshots:
|
|||
|
||||
classnames@2.5.1: {}
|
||||
|
||||
clean-stack@2.2.0: {}
|
||||
|
||||
cli-cursor@3.1.0:
|
||||
dependencies:
|
||||
restore-cursor: 3.1.0
|
||||
|
|
@ -26468,6 +26592,10 @@ snapshots:
|
|||
optionalDependencies:
|
||||
supports-color: 5.5.0
|
||||
|
||||
debug@4.4.1:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decamelize@1.2.0: {}
|
||||
|
||||
decimal.js@10.5.0: {}
|
||||
|
|
@ -26731,6 +26859,10 @@ snapshots:
|
|||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
duplexer2@0.1.4:
|
||||
dependencies:
|
||||
readable-stream: 2.3.8
|
||||
|
||||
duplexer@0.1.2: {}
|
||||
|
||||
duplexify@4.1.3:
|
||||
|
|
@ -27533,7 +27665,7 @@ snapshots:
|
|||
|
||||
facebook-nodejs-business-sdk@21.0.5:
|
||||
dependencies:
|
||||
axios: 1.9.0(debug@4.4.0)
|
||||
axios: 1.9.0
|
||||
currency-codes: 1.5.1
|
||||
iso-3166-1: 2.1.1
|
||||
js-sha256: 0.9.0
|
||||
|
|
@ -27665,6 +27797,15 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
file-type@21.0.0:
|
||||
dependencies:
|
||||
'@tokenizer/inflate': 0.2.7
|
||||
strtok3: 10.3.1
|
||||
token-types: 6.0.3
|
||||
uint8array-extras: 1.4.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
file-type@3.9.0: {}
|
||||
|
||||
filelist@1.0.4:
|
||||
|
|
@ -27774,6 +27915,10 @@ snapshots:
|
|||
optionalDependencies:
|
||||
debug: 4.4.0(supports-color@5.5.0)
|
||||
|
||||
follow-redirects@1.15.9(debug@4.4.1):
|
||||
optionalDependencies:
|
||||
debug: 4.4.1
|
||||
|
||||
fontkit@1.9.0:
|
||||
dependencies:
|
||||
'@swc/helpers': 0.3.17
|
||||
|
|
@ -27847,6 +27992,13 @@ snapshots:
|
|||
mime-types: 2.1.35
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
form-data@3.0.3:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
es-set-tostringtag: 2.1.0
|
||||
mime-types: 2.1.35
|
||||
|
||||
form-data@4.0.0:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
|
|
@ -27875,6 +28027,11 @@ snapshots:
|
|||
|
||||
fresh@2.0.0: {}
|
||||
|
||||
from2@2.3.0:
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
readable-stream: 2.3.8
|
||||
|
||||
front-matter@4.0.2:
|
||||
dependencies:
|
||||
js-yaml: 3.14.1
|
||||
|
|
@ -28756,9 +28913,9 @@ snapshots:
|
|||
'@types/debug': 4.1.12
|
||||
'@types/node': 18.19.87
|
||||
'@types/tough-cookie': 4.0.5
|
||||
axios: 1.9.0(debug@4.4.0)
|
||||
axios: 1.9.0(debug@4.4.1)
|
||||
camelcase: 6.3.0
|
||||
debug: 4.4.0(supports-color@5.5.0)
|
||||
debug: 4.4.1
|
||||
dotenv: 16.5.0
|
||||
extend: 3.0.2
|
||||
file-type: 16.5.4
|
||||
|
|
@ -28827,6 +28984,8 @@ snapshots:
|
|||
|
||||
imurmurhash@0.1.4: {}
|
||||
|
||||
indent-string@4.0.0: {}
|
||||
|
||||
inflight@1.0.6:
|
||||
dependencies:
|
||||
once: 1.4.0
|
||||
|
|
@ -28909,6 +29068,11 @@ snapshots:
|
|||
|
||||
interpret@1.4.0: {}
|
||||
|
||||
into-stream@6.0.0:
|
||||
dependencies:
|
||||
from2: 2.3.0
|
||||
p-is-promise: 3.0.0
|
||||
|
||||
invariant@2.2.4:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
|
@ -29644,6 +29808,8 @@ snapshots:
|
|||
|
||||
jpeg-exif@1.1.4: {}
|
||||
|
||||
js-base64@2.6.4: {}
|
||||
|
||||
js-base64@3.7.7: {}
|
||||
|
||||
js-beautify@1.15.4:
|
||||
|
|
@ -29964,7 +30130,7 @@ snapshots:
|
|||
zod: 3.24.4
|
||||
zod-to-json-schema: 3.24.5(zod@3.24.4)
|
||||
optionalDependencies:
|
||||
axios: 1.9.0(debug@4.4.0)
|
||||
axios: 1.9.0
|
||||
cheerio: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
|
@ -30733,7 +30899,7 @@ snapshots:
|
|||
|
||||
metro-file-map@0.82.2:
|
||||
dependencies:
|
||||
debug: 4.4.0(supports-color@5.5.0)
|
||||
debug: 4.4.1
|
||||
fb-watchman: 2.0.2
|
||||
flow-enums-runtime: 0.0.6
|
||||
graceful-fs: 4.2.11
|
||||
|
|
@ -30829,7 +30995,7 @@ snapshots:
|
|||
chalk: 4.1.2
|
||||
ci-info: 2.0.0
|
||||
connect: 3.7.0
|
||||
debug: 4.4.0(supports-color@5.5.0)
|
||||
debug: 4.4.1
|
||||
error-stack-parser: 2.1.4
|
||||
flow-enums-runtime: 0.0.6
|
||||
graceful-fs: 4.2.11
|
||||
|
|
@ -31423,6 +31589,24 @@ snapshots:
|
|||
|
||||
multiformats@9.9.0: {}
|
||||
|
||||
multipipe@4.0.0:
|
||||
dependencies:
|
||||
duplexer2: 0.1.4
|
||||
object-assign: 4.1.1
|
||||
|
||||
music-metadata@11.6.0:
|
||||
dependencies:
|
||||
'@tokenizer/token': 0.3.0
|
||||
content-type: 1.0.5
|
||||
debug: 4.4.1
|
||||
file-type: 21.0.0
|
||||
media-typer: 1.1.0
|
||||
strtok3: 10.3.1
|
||||
token-types: 6.0.3
|
||||
uint8array-extras: 1.4.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
mustache@4.2.0: {}
|
||||
|
||||
mute-stream@0.0.8: {}
|
||||
|
|
@ -31698,7 +31882,7 @@ snapshots:
|
|||
'@yarnpkg/lockfile': 1.1.0
|
||||
'@yarnpkg/parsers': 3.0.0-rc.46
|
||||
'@zkochan/js-yaml': 0.0.7
|
||||
axios: 1.9.0(debug@4.4.0)
|
||||
axios: 1.9.0
|
||||
chalk: 4.1.2
|
||||
cli-cursor: 3.1.0
|
||||
cli-spinners: 2.6.1
|
||||
|
|
@ -31975,6 +32159,8 @@ snapshots:
|
|||
|
||||
p-finally@1.0.0: {}
|
||||
|
||||
p-is-promise@3.0.0: {}
|
||||
|
||||
p-limit@2.3.0:
|
||||
dependencies:
|
||||
p-try: 2.2.0
|
||||
|
|
@ -31999,6 +32185,10 @@ snapshots:
|
|||
dependencies:
|
||||
p-limit: 4.0.0
|
||||
|
||||
p-map@4.0.0:
|
||||
dependencies:
|
||||
aggregate-error: 3.1.0
|
||||
|
||||
p-queue@6.6.2:
|
||||
dependencies:
|
||||
eventemitter3: 4.0.7
|
||||
|
|
@ -32648,6 +32838,11 @@ snapshots:
|
|||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
|
||||
proper-lockfile@2.0.1:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
retry: 0.10.1
|
||||
|
||||
proper-lockfile@4.1.2:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
|
|
@ -33563,7 +33758,9 @@ snapshots:
|
|||
|
||||
retry-axios@2.6.0(axios@1.9.0):
|
||||
dependencies:
|
||||
axios: 1.9.0(debug@4.4.0)
|
||||
axios: 1.9.0
|
||||
|
||||
retry@0.10.1: {}
|
||||
|
||||
retry@0.12.0: {}
|
||||
|
||||
|
|
@ -34170,6 +34367,10 @@ snapshots:
|
|||
|
||||
split-on-first@1.1.0: {}
|
||||
|
||||
split2@3.2.2:
|
||||
dependencies:
|
||||
readable-stream: 3.6.2
|
||||
|
||||
split2@4.2.0: {}
|
||||
|
||||
sprintf-js@1.0.3: {}
|
||||
|
|
@ -34378,6 +34579,10 @@ snapshots:
|
|||
'@tokenizer/token': 0.3.0
|
||||
peek-readable: 7.0.0
|
||||
|
||||
strtok3@10.3.1:
|
||||
dependencies:
|
||||
'@tokenizer/token': 0.3.0
|
||||
|
||||
strtok3@6.3.0:
|
||||
dependencies:
|
||||
'@tokenizer/token': 0.3.0
|
||||
|
|
@ -34444,6 +34649,15 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
subtitle@4.2.2-alpha.0:
|
||||
dependencies:
|
||||
'@types/multipipe': 3.0.5
|
||||
'@types/readable-stream': 4.0.21
|
||||
multipipe: 4.0.0
|
||||
readable-stream: 4.7.0
|
||||
split2: 3.2.2
|
||||
strip-bom: 4.0.0
|
||||
|
||||
sucrase@3.35.0:
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.8
|
||||
|
|
@ -34712,6 +34926,11 @@ snapshots:
|
|||
'@tokenizer/token': 0.3.0
|
||||
ieee754: 1.2.1
|
||||
|
||||
token-types@6.0.3:
|
||||
dependencies:
|
||||
'@tokenizer/token': 0.3.0
|
||||
ieee754: 1.2.1
|
||||
|
||||
toposort@2.0.2: {}
|
||||
|
||||
totalist@3.0.1: {}
|
||||
|
|
@ -34744,6 +34963,20 @@ snapshots:
|
|||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
transloadit@3.0.2:
|
||||
dependencies:
|
||||
debug: 4.4.0(supports-color@5.5.0)
|
||||
form-data: 3.0.3
|
||||
got: 11.8.6
|
||||
into-stream: 6.0.0
|
||||
is-stream: 2.0.1
|
||||
lodash: 4.17.21
|
||||
p-map: 4.0.0
|
||||
tus-js-client: 2.3.2
|
||||
uuid: 8.3.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
tree-dump@1.0.2(tslib@2.8.1):
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
|
@ -34906,6 +35139,16 @@ snapshots:
|
|||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
tus-js-client@2.3.2:
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
combine-errors: 3.0.3
|
||||
is-stream: 2.0.1
|
||||
js-base64: 2.6.4
|
||||
lodash.throttle: 4.1.1
|
||||
proper-lockfile: 2.0.1
|
||||
url-parse: 1.5.10
|
||||
|
||||
tus-js-client@4.3.1:
|
||||
dependencies:
|
||||
buffer-from: 1.1.2
|
||||
|
|
|
|||
Loading…
Reference in New Issue