feat: postiz agent
This commit is contained in:
parent
41e7d0fdeb
commit
4bfeb01c5f
|
|
@ -31,8 +31,6 @@ import { Nowpayments } from '@gitroom/nestjs-libraries/crypto/nowpayments';
|
|||
import { WebhookController } from '@gitroom/backend/api/routes/webhooks.controller';
|
||||
import { SignatureController } from '@gitroom/backend/api/routes/signature.controller';
|
||||
import { AutopostController } from '@gitroom/backend/api/routes/autopost.controller';
|
||||
import { McpService } from '@gitroom/nestjs-libraries/mcp/mcp.service';
|
||||
import { McpController } from '@gitroom/backend/api/routes/mcp.controller';
|
||||
import { SetsController } from '@gitroom/backend/api/routes/sets.controller';
|
||||
import { ThirdPartyController } from '@gitroom/backend/api/routes/third-party.controller';
|
||||
import { MonitorController } from '@gitroom/backend/api/routes/monitor.controller';
|
||||
|
|
@ -63,7 +61,6 @@ const authenticatedController = [
|
|||
StripeController,
|
||||
AuthController,
|
||||
PublicController,
|
||||
McpController,
|
||||
MonitorController,
|
||||
...authenticatedController,
|
||||
],
|
||||
|
|
@ -80,7 +77,6 @@ const authenticatedController = [
|
|||
TrackService,
|
||||
ShortLinkService,
|
||||
Nowpayments,
|
||||
McpService,
|
||||
],
|
||||
get exports() {
|
||||
return [...this.imports, ...this.providers];
|
||||
|
|
|
|||
|
|
@ -1,22 +1,33 @@
|
|||
import { Logger, Controller, Get, Post, Req, Res, Query } from '@nestjs/common';
|
||||
import {
|
||||
Logger,
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Req,
|
||||
Res,
|
||||
Query,
|
||||
Param,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
CopilotRuntime,
|
||||
OpenAIAdapter,
|
||||
copilotRuntimeNodeHttpEndpoint,
|
||||
copilotRuntimeNextJSAppRouterEndpoint,
|
||||
} from '@copilotkit/runtime';
|
||||
import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';
|
||||
import { Organization } from '@prisma/client';
|
||||
import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';
|
||||
import { MastraAgent } from '@ag-ui/mastra';
|
||||
import { MastraService } from '@gitroom/nestjs-libraries/chat/mastra.service';
|
||||
import { Mastra } from '@mastra/core/dist/mastra';
|
||||
import { Request, Response } from 'express';
|
||||
import { RuntimeContext } from '@mastra/core/di';
|
||||
let mastra: Mastra;
|
||||
import { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';
|
||||
import { AuthorizationActions, Sections } from '@gitroom/backend/services/auth/permissions/permission.exception.class';
|
||||
|
||||
export type ChannelsContext = {
|
||||
integrations: string;
|
||||
organization: string;
|
||||
ui: string;
|
||||
};
|
||||
|
||||
@Controller('/copilot')
|
||||
|
|
@ -47,6 +58,7 @@ export class CopilotController {
|
|||
}
|
||||
|
||||
@Post('/agent')
|
||||
@CheckPolicies([AuthorizationActions.Create, Sections.AI])
|
||||
async agent(
|
||||
@Req() req: Request,
|
||||
@Res() res: Response,
|
||||
|
|
@ -59,33 +71,37 @@ export class CopilotController {
|
|||
Logger.warn('OpenAI API key not set, chat functionality will not work');
|
||||
return;
|
||||
}
|
||||
mastra = mastra || (await this._mastraService.mastra());
|
||||
const mastra = await this._mastraService.mastra();
|
||||
const runtimeContext = new RuntimeContext<ChannelsContext>();
|
||||
runtimeContext.set(
|
||||
'integrations',
|
||||
req?.body?.variables?.properties?.integrations || []
|
||||
);
|
||||
|
||||
runtimeContext.set('organization', organization.id);
|
||||
runtimeContext.set('organization', JSON.stringify(organization));
|
||||
runtimeContext.set('ui', 'true');
|
||||
|
||||
const runtime = new CopilotRuntime({
|
||||
agents: MastraAgent.getLocalAgents({
|
||||
mastra,
|
||||
// @ts-ignore
|
||||
runtimeContext,
|
||||
}),
|
||||
const agents = MastraAgent.getLocalAgents({
|
||||
resourceId: organization.id,
|
||||
mastra,
|
||||
// @ts-ignore
|
||||
runtimeContext,
|
||||
});
|
||||
|
||||
const copilotRuntimeHandler = copilotRuntimeNodeHttpEndpoint({
|
||||
const runtime = new CopilotRuntime({
|
||||
agents,
|
||||
});
|
||||
|
||||
const copilotRuntimeHandler = copilotRuntimeNextJSAppRouterEndpoint({
|
||||
endpoint: '/copilot/agent',
|
||||
runtime,
|
||||
properties: req.body.variables.properties,
|
||||
// properties: req.body.variables.properties,
|
||||
serviceAdapter: new OpenAIAdapter({
|
||||
model: 'gpt-4.1',
|
||||
}),
|
||||
});
|
||||
|
||||
return copilotRuntimeHandler(req, res);
|
||||
return copilotRuntimeHandler.handleRequest(req, res);
|
||||
}
|
||||
|
||||
@Get('/credits')
|
||||
|
|
@ -98,4 +114,44 @@ export class CopilotController {
|
|||
type || 'ai_images'
|
||||
);
|
||||
}
|
||||
|
||||
@Get('/:thread/list')
|
||||
@CheckPolicies([AuthorizationActions.Create, Sections.AI])
|
||||
async getMessagesList(
|
||||
@GetOrgFromRequest() organization: Organization,
|
||||
@Param('thread') threadId: string
|
||||
): Promise<any> {
|
||||
const mastra = await this._mastraService.mastra();
|
||||
const memory = await mastra.getAgent('postiz').getMemory();
|
||||
try {
|
||||
return await memory.query({
|
||||
resourceId: organization.id,
|
||||
threadId,
|
||||
});
|
||||
} catch (err) {
|
||||
return { messages: [] };
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/list')
|
||||
@CheckPolicies([AuthorizationActions.Create, Sections.AI])
|
||||
async getList(@GetOrgFromRequest() organization: Organization) {
|
||||
const mastra = await this._mastraService.mastra();
|
||||
// @ts-ignore
|
||||
const memory = await mastra.getAgent('postiz').getMemory();
|
||||
const list = await memory.getThreadsByResourceIdPaginated({
|
||||
resourceId: organization.id,
|
||||
perPage: 100000,
|
||||
page: 0,
|
||||
orderBy: 'createdAt',
|
||||
sortDirection: 'DESC',
|
||||
});
|
||||
|
||||
return {
|
||||
threads: list.threads.map((p) => ({
|
||||
id: p.id,
|
||||
title: p.title,
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integ
|
|||
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
||||
import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';
|
||||
import { Organization, User } from '@prisma/client';
|
||||
import { ApiKeyDto } from '@gitroom/nestjs-libraries/dtos/integrations/api.key.dto';
|
||||
import { IntegrationFunctionDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.function.dto';
|
||||
import { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';
|
||||
import { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing';
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpException,
|
||||
Param,
|
||||
Post,
|
||||
Sse,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { McpService } from '@gitroom/nestjs-libraries/mcp/mcp.service';
|
||||
import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';
|
||||
|
||||
@ApiTags('Mcp')
|
||||
@Controller('/mcp')
|
||||
export class McpController {
|
||||
constructor(
|
||||
private _mcpService: McpService,
|
||||
private _organizationService: OrganizationService
|
||||
) {}
|
||||
|
||||
@Sse('/:api/sse')
|
||||
async sse(@Param('api') api: string) {
|
||||
const apiModel = await this._organizationService.getOrgByApiKey(api);
|
||||
if (!apiModel) {
|
||||
throw new HttpException('Invalid url', 400);
|
||||
}
|
||||
|
||||
return await this._mcpService.runServer(api, apiModel.id);
|
||||
}
|
||||
|
||||
@Post('/:api/messages')
|
||||
async post(@Param('api') api: string, @Body() body: any) {
|
||||
const apiModel = await this._organizationService.getOrgByApiKey(api);
|
||||
if (!apiModel) {
|
||||
throw new HttpException('Invalid url', 400);
|
||||
}
|
||||
|
||||
return this._mcpService.processPostBody(apiModel.id, body);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import { PublicApiModule } from '@gitroom/backend/public-api/public.api.module';
|
|||
import { ThrottlerBehindProxyGuard } from '@gitroom/nestjs-libraries/throttler/throttler.provider';
|
||||
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';
|
||||
import { SentryModule } from '@sentry/nestjs/setup';
|
||||
|
|
@ -24,7 +23,6 @@ import { ChatModule } from '@gitroom/nestjs-libraries/chat/chat.module';
|
|||
ApiModule,
|
||||
PublicApiModule,
|
||||
AgentModule,
|
||||
McpModule,
|
||||
ThirdPartyModule,
|
||||
VideoModule,
|
||||
ChatModule,
|
||||
|
|
@ -53,7 +51,6 @@ import { ChatModule } from '@gitroom/nestjs-libraries/chat/chat.module';
|
|||
ApiModule,
|
||||
PublicApiModule,
|
||||
AgentModule,
|
||||
McpModule,
|
||||
ThrottlerModule,
|
||||
ChatModule,
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { loadSwagger } from '@gitroom/helpers/swagger/load.swagger';
|
||||
import { json } from 'express';
|
||||
|
||||
process.env.TZ = 'UTC';
|
||||
|
||||
|
|
@ -13,12 +14,14 @@ initializeSentry('backend', true);
|
|||
import { SubscriptionExceptionFilter } from '@gitroom/backend/services/auth/permissions/subscription.exception';
|
||||
import { HttpExceptionFilter } from '@gitroom/nestjs-libraries/services/exception.filter';
|
||||
import { ConfigurationChecker } from '@gitroom/helpers/configuration/configuration.checker';
|
||||
import { startMcp } from '@gitroom/nestjs-libraries/chat/start.mcp';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
rawBody: true,
|
||||
cors: {
|
||||
...(!process.env.NOT_SECURED ? { credentials: true } : {}),
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
exposedHeaders: [
|
||||
'reload',
|
||||
'onboarding',
|
||||
|
|
@ -27,17 +30,24 @@ async function bootstrap() {
|
|||
],
|
||||
origin: [
|
||||
process.env.FRONTEND_URL,
|
||||
'http://localhost:6274',
|
||||
...(process.env.MAIN_URL ? [process.env.MAIN_URL] : []),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await startMcp(app);
|
||||
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
transform: true,
|
||||
})
|
||||
);
|
||||
|
||||
app.use('/copilot', (req: any, res: any, next: any) => {
|
||||
json({ limit: '50mb' })(req, res, next);
|
||||
});
|
||||
|
||||
app.use(cookieParser());
|
||||
app.useGlobalFilters(new SubscriptionExceptionFilter());
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
|
|
|
|||
|
|
@ -1,113 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { McpTool } from '@gitroom/nestjs-libraries/mcp/mcp.tool';
|
||||
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
||||
import { string, array, enum as eenum, object, boolean } from 'zod';
|
||||
import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import dayjs from 'dayjs';
|
||||
import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
|
||||
|
||||
@Injectable()
|
||||
export class MainMcp {
|
||||
constructor(
|
||||
private _integrationService: IntegrationService,
|
||||
private _postsService: PostsService,
|
||||
private _openAiService: OpenaiService
|
||||
) {}
|
||||
|
||||
@McpTool({ toolName: 'POSTIZ_GET_CONFIG_ID' })
|
||||
async preRun() {
|
||||
return [
|
||||
{
|
||||
type: 'text',
|
||||
text: `id: ${makeId(10)} Today date is ${dayjs.utc().format()}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@McpTool({ toolName: 'POSTIZ_PROVIDERS_LIST' })
|
||||
async listOfProviders(organization: string) {
|
||||
const list = (
|
||||
await this._integrationService.getIntegrationsList(organization)
|
||||
).map((org) => ({
|
||||
id: org.id,
|
||||
name: org.name,
|
||||
identifier: org.providerIdentifier,
|
||||
picture: org.picture,
|
||||
disabled: org.disabled,
|
||||
profile: org.profile,
|
||||
customer: org.customer
|
||||
? {
|
||||
id: org.customer.id,
|
||||
name: org.customer.name,
|
||||
}
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
return [{ type: 'text', text: JSON.stringify(list) }];
|
||||
}
|
||||
|
||||
@McpTool({
|
||||
toolName: 'POSTIZ_SCHEDULE_POST',
|
||||
zod: {
|
||||
type: eenum(['draft', 'schedule']),
|
||||
configId: string(),
|
||||
generatePictures: boolean(),
|
||||
date: string().describe('UTC TIME'),
|
||||
providerId: string().describe('Use POSTIZ_PROVIDERS_LIST to get the id'),
|
||||
posts: array(object({ text: string(), images: array(string()) })),
|
||||
},
|
||||
})
|
||||
async schedulePost(
|
||||
organization: string,
|
||||
obj: {
|
||||
type: 'draft' | 'schedule';
|
||||
generatePictures: boolean;
|
||||
date: string;
|
||||
providerId: string;
|
||||
posts: { text: string }[];
|
||||
}
|
||||
) {
|
||||
const create = await this._postsService.createPost(organization, {
|
||||
date: obj.date,
|
||||
type: obj.type,
|
||||
tags: [],
|
||||
shortLink: false,
|
||||
posts: [
|
||||
{
|
||||
group: makeId(10),
|
||||
value: await Promise.all(
|
||||
obj.posts.map(async (post) => ({
|
||||
content: post.text,
|
||||
id: makeId(10),
|
||||
image: !obj.generatePictures
|
||||
? []
|
||||
: [
|
||||
{
|
||||
id: makeId(10),
|
||||
path: await this._openAiService.generateImage(
|
||||
post.text,
|
||||
true
|
||||
),
|
||||
},
|
||||
],
|
||||
}))
|
||||
),
|
||||
settings: {
|
||||
__type: 'any' as any,
|
||||
},
|
||||
integration: {
|
||||
id: obj.providerId,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Post created successfully, check it here: ${process.env.FRONTEND_URL}/p/${create[0].postId}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import { Global, Module } from '@nestjs/common';
|
||||
import { MainMcp } from '@gitroom/backend/mcp/main.mcp';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [],
|
||||
providers: [MainMcp],
|
||||
get exports() {
|
||||
return [...this.providers];
|
||||
},
|
||||
})
|
||||
export class McpModule {}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Metadata } from 'next';
|
||||
import { Agent } from '@gitroom/frontend/components/agents/agent';
|
||||
import { AgentChat } from '@gitroom/frontend/components/agents/agent.chat';
|
||||
export const metadata: Metadata = {
|
||||
title: 'Postiz - Agent',
|
||||
description: '',
|
||||
};
|
||||
export default async function Page() {
|
||||
return (
|
||||
<AgentChat />
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { Metadata } from 'next';
|
||||
import { Agent } from '@gitroom/frontend/components/agents/agent';
|
||||
export const metadata: Metadata = {
|
||||
title: 'Postiz - Agent',
|
||||
description: '',
|
||||
};
|
||||
export default async function Layout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return <Agent>{children}</Agent>;
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { Metadata } from 'next';
|
||||
import { Agent } from '@gitroom/frontend/components/agents/agent';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Agent',
|
||||
title: 'Postiz - Agent',
|
||||
description: '',
|
||||
};
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<Agent />
|
||||
);
|
||||
return redirect('/agents/new');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -699,4 +699,8 @@ html[dir='rtl'] [dir='ltr'] {
|
|||
|
||||
.copilotKitMessage img {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.copilotKitMessage a {
|
||||
color: var(--new-btn-text) !important;
|
||||
}
|
||||
|
|
@ -0,0 +1,359 @@
|
|||
'use client';
|
||||
|
||||
import React, {
|
||||
FC,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { CopilotChat, CopilotKitCSSProperties } from '@copilotkit/react-ui';
|
||||
import {
|
||||
InputProps,
|
||||
UserMessageProps,
|
||||
} from '@copilotkit/react-ui/dist/components/chat/props';
|
||||
import { Input } from '@gitroom/frontend/components/agents/agent.input';
|
||||
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
|
||||
import {
|
||||
CopilotKit,
|
||||
useCopilotAction,
|
||||
useCopilotMessagesContext,
|
||||
} from '@copilotkit/react-core';
|
||||
import {
|
||||
MediaPortal,
|
||||
PropertiesContext,
|
||||
} from '@gitroom/frontend/components/agents/agent';
|
||||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { TextMessage } from '@copilotkit/runtime-client-gql';
|
||||
import { AddEditModal } from '@gitroom/frontend/components/new-launch/add.edit.modal';
|
||||
import dayjs from 'dayjs';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import { ExistingDataContextProvider } from '@gitroom/frontend/components/launches/helpers/use.existing.data';
|
||||
|
||||
export const AgentChat: FC = () => {
|
||||
const { backendUrl } = useVariables();
|
||||
const params = useParams<{ id: string }>();
|
||||
const { properties } = useContext(PropertiesContext);
|
||||
|
||||
return (
|
||||
<CopilotKit
|
||||
{...(params.id === 'new' ? {} : { threadId: params.id })}
|
||||
credentials="include"
|
||||
runtimeUrl={backendUrl + '/copilot/agent'}
|
||||
showDevConsole={false}
|
||||
agent="postiz"
|
||||
properties={{
|
||||
integrations: properties,
|
||||
}}
|
||||
>
|
||||
<Hooks />
|
||||
<LoadMessages id={params.id} />
|
||||
<div
|
||||
style={
|
||||
{
|
||||
'--copilot-kit-primary-color': 'var(--new-btn-text)',
|
||||
'--copilot-kit-background-color': 'var(--new-bg-color)',
|
||||
} as CopilotKitCSSProperties
|
||||
}
|
||||
className="trz agent bg-newBgColorInner flex flex-col gap-[15px] transition-all flex-1 items-center relative"
|
||||
>
|
||||
<div className="absolute left-0 w-full h-full pb-[20px]">
|
||||
<CopilotChat
|
||||
className="w-full h-full"
|
||||
labels={{
|
||||
title: 'Your Assistant',
|
||||
initial: `Hello, I am your Postiz agent 🙌🏻.
|
||||
|
||||
I can schedule a post or multiple posts to multiple channels and generate pictures and videos.
|
||||
|
||||
You can select the channels you want to use from the left menu.
|
||||
|
||||
You can see your previous conversations from the right menu.
|
||||
|
||||
You can also use me as an MCP Server, check Settings >> Public API
|
||||
`,
|
||||
}}
|
||||
UserMessage={Message}
|
||||
Input={NewInput}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CopilotKit>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadMessages: FC<{ id: string }> = ({ id }) => {
|
||||
const { setMessages } = useCopilotMessagesContext();
|
||||
const fetch = useFetch();
|
||||
|
||||
const loadMessages = useCallback(async (idToSet: string) => {
|
||||
const data = await (await fetch(`/copilot/${idToSet}/list`)).json();
|
||||
setMessages(
|
||||
data.uiMessages.map((p: any) => {
|
||||
return new TextMessage({
|
||||
content: p.content,
|
||||
role: p.role,
|
||||
});
|
||||
})
|
||||
);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (id === 'new') {
|
||||
setMessages([]);
|
||||
return;
|
||||
}
|
||||
loadMessages(id);
|
||||
}, [id]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const Message: FC<UserMessageProps> = (props) => {
|
||||
const convertContentToImagesAndVideo = useMemo(() => {
|
||||
return (props.message?.content || '')
|
||||
.replace(/Video: (http.*mp4\n)/g, (match, p1) => {
|
||||
return `<video controls class="h-[150px] w-[150px] rounded-[8px] mb-[10px]"><source src="${p1.trim()}" type="video/mp4">Your browser does not support the video tag.</video>`;
|
||||
})
|
||||
.replace(/Image: (http.*\n)/g, (match, p1) => {
|
||||
return `<img src="${p1.trim()}" class="h-[150px] w-[150px] max-w-full border border-newBgColorInner" />`;
|
||||
})
|
||||
.replace(/\[\-\-Media\-\-\](.*)\[\-\-Media\-\-\]/g, (match, p1) => {
|
||||
return `<div class="flex justify-center mt-[20px]">${p1}</div>`;
|
||||
})
|
||||
.replace(
|
||||
/(\[--integrations--\][\s\S]*?\[--integrations--\])/g,
|
||||
(match, p1) => {
|
||||
return ``;
|
||||
}
|
||||
);
|
||||
}, [props.message?.content]);
|
||||
return (
|
||||
<div
|
||||
className="copilotKitMessage copilotKitUserMessage min-w-[300px]"
|
||||
dangerouslySetInnerHTML={{ __html: convertContentToImagesAndVideo }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const NewInput: FC<InputProps> = (props) => {
|
||||
const [media, setMedia] = useState([] as { path: string; id: string }[]);
|
||||
const [value, setValue] = useState('');
|
||||
const { properties } = useContext(PropertiesContext);
|
||||
return (
|
||||
<>
|
||||
<MediaPortal
|
||||
value={value}
|
||||
media={media}
|
||||
setMedia={(e) => setMedia(e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
{...props}
|
||||
onChange={setValue}
|
||||
onSend={(text) => {
|
||||
const send = props.onSend(
|
||||
text +
|
||||
(media.length > 0
|
||||
? '\n[--Media--]' +
|
||||
media
|
||||
.map((m) =>
|
||||
m.path.indexOf('mp4') > -1
|
||||
? `Video: ${m.path}`
|
||||
: `Image: ${m.path}`
|
||||
)
|
||||
.join('\n') +
|
||||
'\n[--Media--]'
|
||||
: '') +
|
||||
`
|
||||
${
|
||||
properties.length
|
||||
? `[--integrations--]
|
||||
Use the following social media platforms: ${JSON.stringify(
|
||||
properties.map((p) => ({
|
||||
id: p.id,
|
||||
platform: p.identifier,
|
||||
profilePicture: p.picture,
|
||||
additionalSettings: p.additionalSettings,
|
||||
}))
|
||||
)}
|
||||
[--integrations--]`
|
||||
: ``
|
||||
}`
|
||||
);
|
||||
setValue('');
|
||||
setMedia([]);
|
||||
return send;
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Hooks: FC = () => {
|
||||
const modals = useModals();
|
||||
|
||||
useCopilotAction({
|
||||
name: 'manualPosting',
|
||||
description:
|
||||
'This tool should be triggered when the user wants to manually add the generated post',
|
||||
parameters: [
|
||||
{
|
||||
name: 'list',
|
||||
type: 'object[]',
|
||||
description:
|
||||
'list of posts to schedule to different social media (integration ids)',
|
||||
attributes: [
|
||||
{
|
||||
name: 'integrationId',
|
||||
type: 'string',
|
||||
description: 'The integration id',
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
type: 'string',
|
||||
description: 'UTC date of the scheduled post',
|
||||
},
|
||||
{
|
||||
name: 'settings',
|
||||
type: 'object',
|
||||
description: 'Settings for the integration [input:settings]',
|
||||
},
|
||||
{
|
||||
name: 'posts',
|
||||
type: 'object[]',
|
||||
description: 'list of posts / comments (one under another)',
|
||||
attributes: [
|
||||
{
|
||||
name: 'content',
|
||||
type: 'string',
|
||||
description: 'the content of the post',
|
||||
},
|
||||
{
|
||||
name: 'attachments',
|
||||
type: 'object[]',
|
||||
description: 'list of attachments',
|
||||
attributes: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
description: 'id of the attachment',
|
||||
},
|
||||
{
|
||||
name: 'path',
|
||||
type: 'string',
|
||||
description: 'url of the attachment',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
renderAndWaitForResponse: ({ args, status, respond }) => {
|
||||
if (status === 'executing') {
|
||||
return <OpenModal args={args} respond={respond} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
const OpenModal: FC<{
|
||||
respond: (value: any) => void;
|
||||
args: {
|
||||
list: {
|
||||
integrationId: string;
|
||||
date: string;
|
||||
settings?: Record<string, any>;
|
||||
posts: { content: string; attachments: { id: string; path: string }[] }[];
|
||||
}[];
|
||||
};
|
||||
}> = ({ args, respond }) => {
|
||||
const modals = useModals();
|
||||
const { properties } = useContext(PropertiesContext);
|
||||
const startModal = useCallback(async () => {
|
||||
for (const integration of args.list) {
|
||||
await new Promise((res) => {
|
||||
const group = makeId(10);
|
||||
modals.openModal({
|
||||
id: 'add-edit-modal',
|
||||
closeOnClickOutside: false,
|
||||
removeLayout: true,
|
||||
closeOnEscape: false,
|
||||
withCloseButton: false,
|
||||
askClose: true,
|
||||
size: '80%',
|
||||
title: ``,
|
||||
classNames: {
|
||||
modal: 'w-[100%] max-w-[1400px] text-textColor',
|
||||
},
|
||||
children: (
|
||||
<ExistingDataContextProvider
|
||||
value={{
|
||||
group,
|
||||
integration: integration.integrationId,
|
||||
integrationPicture:
|
||||
properties.find((p) => p.id === integration.integrationId)
|
||||
.picture || '',
|
||||
settings: integration.settings || {},
|
||||
posts: integration.posts.map((p) => ({
|
||||
approvedSubmitForOrder: 'NO',
|
||||
content: p.content,
|
||||
createdAt: new Date().toISOString(),
|
||||
state: 'DRAFT',
|
||||
id: makeId(10),
|
||||
settings: JSON.stringify(integration.settings || {}),
|
||||
group,
|
||||
integrationId: integration.integrationId,
|
||||
integration: properties.find(
|
||||
(p) => p.id === integration.integrationId
|
||||
),
|
||||
publishDate: dayjs.utc(integration.date).toISOString(),
|
||||
image: p.attachments.map((a) => ({
|
||||
id: a.id,
|
||||
path: a.path,
|
||||
})),
|
||||
})),
|
||||
}}
|
||||
>
|
||||
<AddEditModal
|
||||
date={dayjs.utc(integration.date)}
|
||||
allIntegrations={properties}
|
||||
integrations={properties.filter(
|
||||
(p) => p.id === integration.integrationId
|
||||
)}
|
||||
onlyValues={integration.posts.map((p) => ({
|
||||
content: p.content,
|
||||
id: makeId(10),
|
||||
settings: integration.settings || {},
|
||||
image: p.attachments.map((a) => ({
|
||||
id: a.id,
|
||||
path: a.path,
|
||||
})),
|
||||
}))}
|
||||
reopenModal={() => {}}
|
||||
mutate={() => res(true)}
|
||||
/>
|
||||
</ExistingDataContextProvider>
|
||||
),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
respond('User scheduled all the posts');
|
||||
}, [args, respond, properties]);
|
||||
|
||||
useEffect(() => {
|
||||
startModal();
|
||||
}, []);
|
||||
return (
|
||||
<div onClick={() => respond('continue')}>
|
||||
Opening manually ${JSON.stringify(args)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -6,11 +6,8 @@ import React, {
|
|||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
useContext,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
import { CopilotKit } from '@copilotkit/react-core';
|
||||
import { CopilotChat, CopilotKitCSSProperties } from '@copilotkit/react-ui';
|
||||
import clsx from 'clsx';
|
||||
import useCookie from 'react-use-cookie';
|
||||
import useSWR from 'swr';
|
||||
|
|
@ -21,12 +18,9 @@ import Image from 'next/image';
|
|||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { useWaitForClass } from '@gitroom/helpers/utils/use.wait.for.class';
|
||||
import { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component';
|
||||
import {
|
||||
InputProps,
|
||||
UserMessageProps,
|
||||
} from '@copilotkit/react-ui/dist/components/chat/props';
|
||||
import { Input } from '@gitroom/frontend/components/agents/agent.input';
|
||||
import { Integration } from '@prisma/client';
|
||||
import Link from 'next/link';
|
||||
import { useParams, usePathname, useRouter } from 'next/navigation';
|
||||
|
||||
export const MediaPortal: FC<{
|
||||
media: { path: string; id: string }[];
|
||||
|
|
@ -115,29 +109,6 @@ export const AgentList: FC<{ onChange: (arr: any[]) => void }> = ({
|
|||
)}
|
||||
>
|
||||
<div className="absolute top-0 start-0 w-full h-full p-[20px] overflow-auto scrollbar scrollbar-thumb-fifth scrollbar-track-newBgColor">
|
||||
<div className="mb-[15px] justify-center flex group-[.sidebar]:pb-[15px]">
|
||||
<button className="text-white whitespace-nowrap flex-1 pt-[12px] pb-[14px] ps-[16px] pe-[20px] group-[.sidebar]:p-0 min-h-[44px] max-h-[44px] rounded-md bg-btnPrimary flex justify-center items-center gap-[5px] outline-none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="21"
|
||||
height="20"
|
||||
viewBox="0 0 21 20"
|
||||
fill="none"
|
||||
className="min-w-[21px] min-h-[20px]"
|
||||
>
|
||||
<path
|
||||
d="M10.5001 4.16699V15.8337M4.66675 10.0003H16.3334"
|
||||
stroke="white"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div className="flex-1 text-start text-[16px] group-[.sidebar]:hidden">
|
||||
Start a new session
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<h2 className="group-[.sidebar]:hidden flex-1 text-[20px] font-[500] mb-[15px]">
|
||||
Select Channels
|
||||
|
|
@ -222,121 +193,79 @@ export const AgentList: FC<{ onChange: (arr: any[]) => void }> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const PropertiesContext = createContext({ properties: [] });
|
||||
export const Agent: FC = () => {
|
||||
const { backendUrl } = useVariables();
|
||||
export const PropertiesContext = createContext({ properties: [] });
|
||||
export const Agent: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [properties, setProperties] = useState([]);
|
||||
|
||||
return (
|
||||
<PropertiesContext.Provider value={{ properties }}>
|
||||
<CopilotKit
|
||||
credentials="include"
|
||||
runtimeUrl={backendUrl + '/copilot/agent'}
|
||||
showDevConsole={false}
|
||||
// publicApiKey="ck_pub_35c5e2cef8891a02b99bffed73c53d8d"
|
||||
agent="postiz"
|
||||
properties={{
|
||||
integrations: properties,
|
||||
}}
|
||||
>
|
||||
<AgentList onChange={setProperties} />
|
||||
<div
|
||||
style={
|
||||
{
|
||||
'--copilot-kit-primary-color': 'var(--new-btn-text)',
|
||||
'--copilot-kit-background-color': 'var(--new-bg-color)',
|
||||
} as CopilotKitCSSProperties
|
||||
}
|
||||
className="trz agent bg-newBgColorInner flex flex-col gap-[15px] transition-all flex-1 items-center relative"
|
||||
>
|
||||
<div className="absolute left-0 w-full h-full pb-[20px]">
|
||||
<CopilotChat
|
||||
className="w-full h-full"
|
||||
labels={{
|
||||
title: 'Your Assistant',
|
||||
initial: 'Hi! 👋 How can I assist you today?',
|
||||
}}
|
||||
UserMessage={Message}
|
||||
Input={NewInput}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CopilotKit>
|
||||
<AgentList onChange={setProperties} />
|
||||
<div className="bg-newBgColorInner flex flex-1">{children}</div>
|
||||
<Threads />
|
||||
</PropertiesContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const Message: FC<UserMessageProps> = (props) => {
|
||||
const convertContentToImagesAndVideo = useMemo(() => {
|
||||
return (props.message?.content || '')
|
||||
.replace(/Video: (http.*mp4\n)/g, (match, p1) => {
|
||||
return `<video controls class="h-[150px] w-[150px] rounded-[8px] mb-[10px]"><source src="${p1.trim()}" type="video/mp4">Your browser does not support the video tag.</video>`;
|
||||
})
|
||||
.replace(/Image: (http.*\n)/g, (match, p1) => {
|
||||
return `<img src="${p1.trim()}" class="h-[150px] w-[150px] max-w-full border border-newBgColorInner" />`;
|
||||
})
|
||||
.replace(/\[\-\-Media\-\-\](.*)\[\-\-Media\-\-\]/g, (match, p1) => {
|
||||
return `<div class="flex justify-center mt-[20px]">${p1}</div>`;
|
||||
})
|
||||
.replace(
|
||||
/(\[--integrations--\][\s\S]*?\[--integrations--\])/g,
|
||||
(match, p1) => {
|
||||
return ``;
|
||||
}
|
||||
);
|
||||
}, [props.message?.content]);
|
||||
const Threads: FC = () => {
|
||||
const fetch = useFetch();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const threads = useCallback(async () => {
|
||||
return (await fetch('/copilot/list')).json();
|
||||
}, []);
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
const { data } = useSWR('threads', threads);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="copilotKitMessage copilotKitUserMessage min-w-[300px]"
|
||||
dangerouslySetInnerHTML={{ __html: convertContentToImagesAndVideo }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
const NewInput: FC<InputProps> = (props) => {
|
||||
const [media, setMedia] = useState([] as { path: string; id: string }[]);
|
||||
const [value, setValue] = useState('');
|
||||
const { properties } = useContext(PropertiesContext);
|
||||
return (
|
||||
<>
|
||||
<MediaPortal
|
||||
value={value}
|
||||
media={media}
|
||||
setMedia={(e) => setMedia(e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
{...props}
|
||||
onChange={setValue}
|
||||
onSend={(text) => {
|
||||
const send = props.onSend(
|
||||
text +
|
||||
(media.length > 0
|
||||
? '\n[--Media--]' +
|
||||
media
|
||||
.map((m) =>
|
||||
m.path.indexOf('mp4') > -1
|
||||
? `Video: ${m.path}`
|
||||
: `Image: ${m.path}`
|
||||
)
|
||||
.join('\n') +
|
||||
'\n[--Media--]'
|
||||
: '') +
|
||||
`
|
||||
[--integrations--]
|
||||
Use the following social media platforms: ${JSON.stringify(
|
||||
properties.map((p) => ({
|
||||
id: p.id,
|
||||
platform: p.identifier,
|
||||
profilePicture: p.picture,
|
||||
additionalSettings: p.additionalSettings,
|
||||
}))
|
||||
className={clsx(
|
||||
'trz bg-newBgColorInner flex flex-col gap-[15px] transition-all relative',
|
||||
'w-[260px]'
|
||||
)}
|
||||
>
|
||||
<div className="absolute top-0 start-0 w-full h-full p-[20px] overflow-auto scrollbar scrollbar-thumb-fifth scrollbar-track-newBgColor">
|
||||
<div className="mb-[15px] justify-center flex group-[.sidebar]:pb-[15px]">
|
||||
<Link
|
||||
href={`/agents`}
|
||||
className="text-white whitespace-nowrap flex-1 pt-[12px] pb-[14px] ps-[16px] pe-[20px] group-[.sidebar]:p-0 min-h-[44px] max-h-[44px] rounded-md bg-btnPrimary flex justify-center items-center gap-[5px] outline-none"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="21"
|
||||
height="20"
|
||||
viewBox="0 0 21 20"
|
||||
fill="none"
|
||||
className="min-w-[21px] min-h-[20px]"
|
||||
>
|
||||
<path
|
||||
d="M10.5001 4.16699V15.8337M4.66675 10.0003H16.3334"
|
||||
stroke="white"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div className="flex-1 text-start text-[16px] group-[.sidebar]:hidden">
|
||||
Start a new chat
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-col gap-[1px]">
|
||||
{data?.threads?.map((p: any) => (
|
||||
<Link
|
||||
className={clsx(
|
||||
'overflow-ellipsis overflow-hidden whitespace-nowrap hover:bg-newBgColor px-[10px] py-[6px] rounded-[10px] cursor-pointer',
|
||||
p.id === id && 'bg-newBgColor'
|
||||
)}
|
||||
[--integrations--]`
|
||||
);
|
||||
setValue('');
|
||||
setMedia([]);
|
||||
return send;
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
href={`/agents/${p.id}`}
|
||||
key={p.id}
|
||||
>
|
||||
{p.title}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,24 +20,6 @@ export const useMenuItem = () => {
|
|||
const t = useT();
|
||||
|
||||
const firstMenu = [
|
||||
{
|
||||
name: 'Agent',
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="23"
|
||||
height="23"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M21.1963 9.07375C20.2913 6.95494 18.6824 5.21364 16.6416 4.14422C14.6009 3.0748 12.2534 2.74287 9.99616 3.20455C7.73891 3.66623 5.71031 4.8932 4.25334 6.67802C2.79637 8.46284 2.0004 10.696 2 13V21.25C2 21.7141 2.18437 22.1592 2.51256 22.4874C2.84075 22.8156 3.28587 23 3.75 23H10.8337C11.6141 24.7821 12.8964 26.2984 14.5241 27.3638C16.1519 28.4293 18.0546 28.9978 20 29H28.25C28.7141 29 29.1592 28.8156 29.4874 28.4874C29.8156 28.1592 30 27.7141 30 27.25V19C29.9995 16.5553 29.1036 14.1955 27.4814 12.3666C25.8593 10.5376 23.6234 9.36619 21.1963 9.07375ZM4 13C4 11.4177 4.46919 9.87103 5.34824 8.55544C6.22729 7.23984 7.47672 6.21446 8.93853 5.60896C10.4003 5.00346 12.0089 4.84504 13.5607 5.15372C15.1126 5.4624 16.538 6.22432 17.6569 7.34314C18.7757 8.46197 19.5376 9.88743 19.8463 11.4393C20.155 12.9911 19.9965 14.5997 19.391 16.0615C18.7855 17.5233 17.7602 18.7727 16.4446 19.6518C15.129 20.5308 13.5823 21 12 21H4V13ZM28 27H20C18.5854 26.9984 17.1964 26.6225 15.974 25.9106C14.7516 25.1986 13.7394 24.1759 13.04 22.9463C14.4096 22.8041 15.7351 22.3804 16.9333 21.7017C18.1314 21.023 19.1763 20.104 20.0024 19.0023C20.8284 17.9006 21.4179 16.6401 21.7337 15.2998C22.0495 13.9595 22.0848 12.5684 21.8375 11.2137C23.5916 11.6277 25.1545 12.6218 26.273 14.035C27.3915 15.4482 28 17.1977 28 19V27Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
path: '/agents',
|
||||
},
|
||||
{
|
||||
name: isGeneral ? t('calendar', 'Calendar') : t('launches', 'Launches'),
|
||||
icon: (
|
||||
|
|
@ -59,6 +41,24 @@ export const useMenuItem = () => {
|
|||
),
|
||||
path: '/launches',
|
||||
},
|
||||
{
|
||||
name: 'Agent',
|
||||
icon: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="23"
|
||||
height="23"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M21.1963 9.07375C20.2913 6.95494 18.6824 5.21364 16.6416 4.14422C14.6009 3.0748 12.2534 2.74287 9.99616 3.20455C7.73891 3.66623 5.71031 4.8932 4.25334 6.67802C2.79637 8.46284 2.0004 10.696 2 13V21.25C2 21.7141 2.18437 22.1592 2.51256 22.4874C2.84075 22.8156 3.28587 23 3.75 23H10.8337C11.6141 24.7821 12.8964 26.2984 14.5241 27.3638C16.1519 28.4293 18.0546 28.9978 20 29H28.25C28.7141 29 29.1592 28.8156 29.4874 28.4874C29.8156 28.1592 30 27.7141 30 27.25V19C29.9995 16.5553 29.1036 14.1955 27.4814 12.3666C25.8593 10.5376 23.6234 9.36619 21.1963 9.07375ZM4 13C4 11.4177 4.46919 9.87103 5.34824 8.55544C6.22729 7.23984 7.47672 6.21446 8.93853 5.60896C10.4003 5.00346 12.0089 4.84504 13.5607 5.15372C15.1126 5.4624 16.538 6.22432 17.6569 7.34314C18.7757 8.46197 19.5376 9.88743 19.8463 11.4393C20.155 12.9911 19.9965 14.5997 19.391 16.0615C18.7855 17.5233 17.7602 18.7727 16.4446 19.6518C15.129 20.5308 13.5823 21 12 21H4V13ZM28 27H20C18.5854 26.9984 17.1964 26.6225 15.974 25.9106C14.7516 25.1986 13.7394 24.1759 13.04 22.9463C14.4096 22.8041 15.7351 22.3804 16.9333 21.7017C18.1314 21.023 19.1763 20.104 20.0024 19.0023C20.8284 17.9006 21.4179 16.6401 21.7337 15.2998C22.0495 13.9595 22.0848 12.5684 21.8375 11.2137C23.5916 11.6277 25.1545 12.6218 26.273 14.035C27.3915 15.4482 28 17.1977 28 19V27Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
path: '/agents',
|
||||
},
|
||||
{
|
||||
name: t('analytics', 'Analytics'),
|
||||
icon: (
|
||||
|
|
@ -146,39 +146,6 @@ export const useMenuItem = () => {
|
|||
] satisfies MenuItemInterface[] as MenuItemInterface[];
|
||||
|
||||
const secondMenu = [
|
||||
// {
|
||||
// name: 'GrowChief',
|
||||
// icon: (
|
||||
// <svg
|
||||
// width="20"
|
||||
// height="21"
|
||||
// viewBox="0 0 50 28"
|
||||
// fill="none"
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// data-tooltip-id="tooltip"
|
||||
// data-tooltip-content="New! Automate your X and LinkedIn outreach with GrowChief"
|
||||
// >
|
||||
// <path
|
||||
// d="M24.8789 0.191772C39.9967 0.198463 49.621 14.0845 49.6514 14.1283C49.6514 14.1283 40.0206 27.8931 24.8789 27.8998C9.73703 27.9062 0.189453 14.1283 0.189453 14.1283C0.235704 14.0609 9.77381 0.185332 24.8789 0.191772Z"
|
||||
// fill="none"
|
||||
// stroke="currentColor"
|
||||
// strokeWidth="3"
|
||||
// />
|
||||
//
|
||||
// <circle
|
||||
// cx="24.9189"
|
||||
// cy="14.2621"
|
||||
// r="9.1328"
|
||||
// fill="none"
|
||||
// stroke="currentColor"
|
||||
// strokeWidth="3"
|
||||
// />
|
||||
// </svg>
|
||||
// ),
|
||||
// path: 'https://growchief.com',
|
||||
// role: ['ADMIN', 'SUPERADMIN', 'USER'],
|
||||
// requireBilling: true,
|
||||
// },
|
||||
{
|
||||
name: t('affiliate', 'Affiliate'),
|
||||
icon: (
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export const MenuItem: FC<{ label: string; icon: ReactNode; path: string }> = ({
|
|||
path,
|
||||
}) => {
|
||||
const currentPath = usePathname();
|
||||
const isActive = path.indexOf(currentPath) === 0;
|
||||
const isActive = currentPath.indexOf(path) === 0;
|
||||
|
||||
return (
|
||||
<Link
|
||||
|
|
|
|||
|
|
@ -85,20 +85,20 @@ export const PublicComponent = () => {
|
|||
<div className="text-customColor18 mt-[4px]">
|
||||
{t(
|
||||
'connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster',
|
||||
'Connect your MCP client to Postiz to schedule your posts faster!'
|
||||
'Connect Postiz MCP server to your client (Http streaming) to schedule your posts faster.'
|
||||
)}
|
||||
</div>
|
||||
<div className="my-[16px] mt-[16px] bg-sixth border-fifth items-center border rounded-[4px] p-[24px] flex gap-[24px]">
|
||||
<div className="flex items-center">
|
||||
{reveal2 ? (
|
||||
`${backendUrl}/mcp/` + user.publicApi + '/sse'
|
||||
`${backendUrl}/mcp/` + user.publicApi
|
||||
) : (
|
||||
<>
|
||||
<div className="blur-sm">
|
||||
{(`${backendUrl}/mcp/` + user.publicApi + '/sse').slice(0, -5)}
|
||||
{(`${backendUrl}/mcp/` + user.publicApi).slice(0, -5)}
|
||||
</div>
|
||||
<div>
|
||||
{(`${backendUrl}/mcp/` + user.publicApi + '/sse').slice(-5)}
|
||||
{(`${backendUrl}/mcp/` + user.publicApi).slice(-5)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ checksums:
|
|||
video_made_with_ai: c37747aaf8107d339d6238a0463f7096
|
||||
please_add_at_least: 90d3c0237b56e57c7a58d5decf6e9d3c
|
||||
send_invitation_via_email: 9275e0b85147a931421b3bf6c3083cb4
|
||||
global_settings: ba55734261d6bc26e792fda32de3e7ec
|
||||
copy_id: 831147124db35832872f8470c577e440
|
||||
team_members: 61333c4a765e10b2ad46774951725233
|
||||
invite_your_assistant_or_team_member_to_manage_your_account: dadd50655759ac32b9ed62e60f8acb1d
|
||||
|
|
@ -45,7 +46,7 @@ checksums:
|
|||
reveal: 3f7302cc2e097266e447b3e18f1b9ab7
|
||||
copy_key: 8f4f13acec7abf7c3aa6e680b6da99f8
|
||||
mcp: 62c2c8e6703c9e5224e98294a53e1884
|
||||
connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster: 55b92dd743d32bfe04b69c6ec9406e1f
|
||||
connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster: bb53d1093a6ab977f2eee0bbc7e0248c
|
||||
share_with_a_client: 8e27f69c841a9dda635f34fb1b38ad99
|
||||
post: 0beafa0cd18d45690b9b07104eb2c1b8
|
||||
comments: 502237cd1044491c48494aa60967a985
|
||||
|
|
|
|||
|
|
@ -1,4 +1,18 @@
|
|||
import type { ZodLikeSchema } from '@mastra/core/dist/types/zod-compat';
|
||||
import type {
|
||||
ToolExecutionContext,
|
||||
} from '@mastra/core/dist/tools/types';
|
||||
import { Tool } from '@mastra/core/dist/tools/tool';
|
||||
|
||||
export type ToolReturn = Tool<
|
||||
ZodLikeSchema,
|
||||
ZodLikeSchema,
|
||||
ZodLikeSchema,
|
||||
ZodLikeSchema,
|
||||
ToolExecutionContext<ZodLikeSchema, ZodLikeSchema, ZodLikeSchema>
|
||||
>;
|
||||
|
||||
export interface AgentToolInterface {
|
||||
name: string;
|
||||
run(): Promise<any>;
|
||||
}
|
||||
run(): ToolReturn;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
// context.ts
|
||||
import { AsyncLocalStorage } from 'node:async_hooks';
|
||||
|
||||
type Ctx = {
|
||||
requestId: string;
|
||||
auth: any; // replace with your org type if you have it, e.g. Organization
|
||||
};
|
||||
|
||||
const als = new AsyncLocalStorage<Ctx>();
|
||||
|
||||
export function runWithContext<T>(ctx: Ctx, fn: () => Promise<T> | T) {
|
||||
return als.run(ctx, fn);
|
||||
}
|
||||
|
||||
export function getContext(): Ctx | undefined {
|
||||
return als.getStore();
|
||||
}
|
||||
|
||||
export function getAuth<T = any>(): T | undefined {
|
||||
return als.getStore()?.auth as T | undefined;
|
||||
}
|
||||
|
||||
export function getRequestId(): string | undefined {
|
||||
return als.getStore()?.requestId;
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { ToolAction } from '@mastra/core/dist/tools/types';
|
||||
import { getAuth } from '@gitroom/nestjs-libraries/chat/async.storage';
|
||||
|
||||
export const checkAuth: ToolAction['execute'] = async (
|
||||
{ runtimeContext },
|
||||
options
|
||||
) => {
|
||||
const auth = getAuth();
|
||||
// @ts-ignore
|
||||
if (options?.extra?.authInfo || auth) {
|
||||
runtimeContext.set(
|
||||
// @ts-ignore
|
||||
'organization',
|
||||
// @ts-ignore
|
||||
JSON.stringify(options?.extra?.authInfo || auth)
|
||||
);
|
||||
// @ts-ignore
|
||||
runtimeContext.set('ui', 'false');
|
||||
}
|
||||
};
|
||||
|
|
@ -12,6 +12,11 @@ export const AgentState = object({
|
|||
proverbs: array(string()).default([]),
|
||||
});
|
||||
|
||||
const renderArray = (list: string[], show: boolean) => {
|
||||
if (!show) return '';
|
||||
return list.map((p) => `- ${p}`).join('\n');
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class LoadToolsService {
|
||||
constructor(private _moduleRef: ModuleRef) {}
|
||||
|
|
@ -39,7 +44,9 @@ export class LoadToolsService {
|
|||
const tools = await this.loadTools();
|
||||
return new Agent({
|
||||
name: 'postiz',
|
||||
instructions: () => {
|
||||
description: 'Agent that helps manage and schedule social media posts for users',
|
||||
instructions: ({ runtimeContext }) => {
|
||||
const ui: string = runtimeContext.get('ui' as never);
|
||||
return `
|
||||
Global information:
|
||||
- Date (UTC): ${dayjs().format('YYYY-MM-DD HH:mm:ss')}
|
||||
|
|
@ -47,9 +54,12 @@ export class LoadToolsService {
|
|||
You are an agent that helps manage and schedule social media posts for users, you can:
|
||||
- Schedule posts into the future, or now, adding texts, images and videos
|
||||
- Generate pictures for posts
|
||||
- Generate videos for posts
|
||||
- Generate text for posts
|
||||
- Show global analytics about socials
|
||||
- List integrations (channels)
|
||||
|
||||
- We schedule posts to different integration like facebook, instagram, etc. but to the user we don't say integrations we say channels as integration is the technical name
|
||||
- When scheduling a post, you must follow the social media rules and best practices.
|
||||
- When scheduling a post, you can pass an array for list of posts for a social media platform, But it has different behavior depending on the platform.
|
||||
- For platforms like Threads, Bluesky and X (Twitter), each post in the array will be a separate post in the thread.
|
||||
|
|
@ -64,9 +74,15 @@ export class LoadToolsService {
|
|||
- In every message I will send you the list of needed social medias (id and platform), if you already have the information use it, if not, use the integrationSchema tool to get it.
|
||||
- Make sure you always take the last information I give you about the socials, it might have changed.
|
||||
- Before scheduling a post, always make sure you ask the user confirmation by providing all the details of the post (text, images, videos, date, time, social media platform, account).
|
||||
- If the user confirm, ask if they would like to get a modal with populated content without scheduling the post yet or if they want to schedule it right away.
|
||||
- Between tools, we will reference things like: [output:name] and [input:name] to set the information right.
|
||||
- When outputting a date for the user, make sure it's human readable with time
|
||||
- The content of the post, HTML, Each line must be wrapped in <p> here is the possible tags: h1, h2, h3, u, strong, li, ul, p (you can\'t have u and strong together), don't use a "code" box
|
||||
${renderArray(
|
||||
[
|
||||
'If the user confirm, ask if they would like to get a modal with populated content without scheduling the post yet or if they want to schedule it right away.',
|
||||
],
|
||||
!!ui
|
||||
)}
|
||||
`;
|
||||
},
|
||||
model: openai('gpt-4.1'),
|
||||
|
|
@ -74,6 +90,9 @@ export class LoadToolsService {
|
|||
memory: new Memory({
|
||||
storage: pStore,
|
||||
options: {
|
||||
threads: {
|
||||
generateTitle: true,
|
||||
},
|
||||
workingMemory: {
|
||||
enabled: true,
|
||||
schema: AgentState,
|
||||
|
|
|
|||
|
|
@ -6,16 +6,21 @@ import { LoadToolsService } from '@gitroom/nestjs-libraries/chat/load.tools.serv
|
|||
|
||||
@Injectable()
|
||||
export class MastraService {
|
||||
static mastra: Mastra;
|
||||
constructor(private _loadToolsService: LoadToolsService) {}
|
||||
async mastra() {
|
||||
return new Mastra({
|
||||
storage: pStore,
|
||||
agents: {
|
||||
postiz: await this._loadToolsService.agent(),
|
||||
},
|
||||
logger: new ConsoleLogger({
|
||||
level: 'info',
|
||||
}),
|
||||
});
|
||||
MastraService.mastra =
|
||||
MastraService.mastra ||
|
||||
new Mastra({
|
||||
storage: pStore,
|
||||
agents: {
|
||||
postiz: await this._loadToolsService.agent(),
|
||||
},
|
||||
logger: new ConsoleLogger({
|
||||
level: 'debug',
|
||||
}),
|
||||
});
|
||||
|
||||
return MastraService.mastra;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
import { INestApplication } from '@nestjs/common';
|
||||
import { Request, Response } from 'express';
|
||||
import { MastraService } from '@gitroom/nestjs-libraries/chat/mastra.service';
|
||||
import { MCPServer } from '@mastra/mcp';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';
|
||||
import { runWithContext } from './async.storage';
|
||||
export const startMcp = async (app: INestApplication) => {
|
||||
const mastraService = app.get(MastraService, { strict: false });
|
||||
const organizationService = app.get(OrganizationService, { strict: false });
|
||||
|
||||
const mastra = await mastraService.mastra();
|
||||
const agent = mastra.getAgent('postiz');
|
||||
const tools = await agent.getTools();
|
||||
|
||||
const server = new MCPServer({
|
||||
name: 'Postiz MCP',
|
||||
version: '1.0.0',
|
||||
tools,
|
||||
agents: { postiz: agent },
|
||||
});
|
||||
|
||||
app.use(
|
||||
'/mcp/:id',
|
||||
async (req: Request, res: Response) => {
|
||||
// @ts-ignore
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', '*');
|
||||
res.setHeader('Access-Control-Allow-Headers', '*');
|
||||
res.setHeader('Access-Control-Expose-Headers', '*');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.sendStatus(200);
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
req.auth = await organizationService.getOrgByApiKey(req.params.id);
|
||||
// @ts-ignore
|
||||
if (!req.auth) {
|
||||
throw new HttpException('Invalid API Key', 400);
|
||||
}
|
||||
|
||||
const url = new URL(
|
||||
`/mcp/${req.params.id}`,
|
||||
process.env.NEXT_PUBLIC_BACKEND_URL
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
await runWithContext({ requestId: req.params.id, auth: req.auth }, async () => {
|
||||
await server.startHTTP({
|
||||
url,
|
||||
httpPath: url.pathname,
|
||||
options: {
|
||||
sessionIdGenerator: () => {
|
||||
return randomUUID();
|
||||
},
|
||||
},
|
||||
req,
|
||||
res,
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { AgentToolInterface } from '@gitroom/nestjs-libraries/chat/agent.tool.interface';
|
||||
import { createTool } from '@mastra/core/tools';
|
||||
import { z } from 'zod';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';
|
||||
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
||||
import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';
|
||||
|
||||
@Injectable()
|
||||
export class GenerateImageTool implements AgentToolInterface {
|
||||
private storage = UploadFactory.createStorage();
|
||||
|
||||
constructor(private _mediaService: MediaService) {}
|
||||
name = 'generateImageTool';
|
||||
|
||||
run() {
|
||||
return createTool({
|
||||
id: 'generateImageTool',
|
||||
description: `Generate image to use in a post,
|
||||
in case the user specified a platform that requires attachment and attachment was not provided,
|
||||
ask if they want to generate a picture of a video.
|
||||
`,
|
||||
inputSchema: z.object({
|
||||
prompt: z.string(),
|
||||
}),
|
||||
outputSchema: z.object({
|
||||
id: z.string(),
|
||||
path: z.string(),
|
||||
}),
|
||||
execute: async (args, options) => {
|
||||
const { context, runtimeContext } = args;
|
||||
checkAuth(args, options);
|
||||
// @ts-ignore
|
||||
const org = JSON.parse(runtimeContext.get('organization') as string);
|
||||
const image = await this._mediaService.generateImage(
|
||||
context.prompt,
|
||||
org
|
||||
);
|
||||
|
||||
const file = await this.storage.uploadSimple(
|
||||
'data:image/png;base64,' + image
|
||||
);
|
||||
|
||||
return this._mediaService.saveFile(org.id, file.split('/').pop(), file);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import { AgentToolInterface, ToolReturn } from '@gitroom/nestjs-libraries/chat/agent.tool.interface';
|
||||
import { createTool } from '@mastra/core/tools';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
|
||||
import { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';
|
||||
import z from 'zod';
|
||||
import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';
|
||||
|
||||
@Injectable()
|
||||
export class GenerateVideoOptionsTool implements AgentToolInterface {
|
||||
constructor(private _videoManagerService: VideoManager) {}
|
||||
name = 'generateVideoOptions';
|
||||
|
||||
run() {
|
||||
return createTool({
|
||||
id: 'generateVideoOptions',
|
||||
description: `All the options to generate videos, some tools might require another call to generateVideoFunction`,
|
||||
outputSchema: z.object({
|
||||
video: z.array(
|
||||
z.object({
|
||||
type: z.string(),
|
||||
output: z.string(),
|
||||
tools: z.array(
|
||||
z.object({
|
||||
functionName: z.string(),
|
||||
output: z.string(),
|
||||
})
|
||||
),
|
||||
customParams: z.any(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
execute: async (args, options) => {
|
||||
const { context, runtimeContext } = args;
|
||||
checkAuth(args, options);
|
||||
const videos = this._videoManagerService.getAllVideos();
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
video: videos.map((p) => {
|
||||
return {
|
||||
type: p.identifier,
|
||||
output: 'vertical|horizontal',
|
||||
tools: p.tools,
|
||||
customParams: validationMetadatasToSchemas()[p.dto.name],
|
||||
};
|
||||
}),
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
return {
|
||||
video: videos.map((p) => {
|
||||
return {
|
||||
type: p.identifier,
|
||||
output: 'vertical|horizontal',
|
||||
tools: p.tools,
|
||||
customParams: validationMetadatasToSchemas()[p.dto.name],
|
||||
};
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import { AgentToolInterface } from '@gitroom/nestjs-libraries/chat/agent.tool.interface';
|
||||
import { createTool } from '@mastra/core/tools';
|
||||
import { z } from 'zod';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
IntegrationManager,
|
||||
socialIntegrationList,
|
||||
} from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
||||
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
|
||||
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
||||
import { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||
import { timer } from '@gitroom/helpers/utils/timer';
|
||||
import { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service';
|
||||
import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';
|
||||
import { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';
|
||||
import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';
|
||||
|
||||
@Injectable()
|
||||
export class GenerateVideoTool implements AgentToolInterface {
|
||||
constructor(
|
||||
private _mediaService: MediaService,
|
||||
private _videoManager: VideoManager
|
||||
) {}
|
||||
name = 'generateVideoTool';
|
||||
|
||||
run() {
|
||||
return createTool({
|
||||
id: 'generateVideoTool',
|
||||
description: `Generate video to use in a post,
|
||||
in case the user specified a platform that requires attachment and attachment was not provided,
|
||||
ask if they want to generate a picture of a video.
|
||||
In many cases 'videoFunctionTool' will need to be called first, to get things like voice id
|
||||
Here are the type of video that can be generated:
|
||||
${this._videoManager
|
||||
.getAllVideos()
|
||||
.map((p) => "-" + p.title)
|
||||
.join('\n')}
|
||||
`,
|
||||
inputSchema: z.object({
|
||||
identifier: z.string(),
|
||||
output: z.enum(['vertical', 'horizontal']),
|
||||
customParams: z.array(
|
||||
z.object({
|
||||
key: z.string().describe('Name of the settings key to pass'),
|
||||
value: z.any().describe('Value of the key'),
|
||||
})
|
||||
),
|
||||
}),
|
||||
outputSchema: z.object({
|
||||
url: z.string(),
|
||||
}),
|
||||
execute: async (args, options) => {
|
||||
const { context, runtimeContext } = args;
|
||||
checkAuth(args, options);
|
||||
// @ts-ignore
|
||||
const org = JSON.parse(runtimeContext.get('organization') as string);
|
||||
const value = await this._mediaService.generateVideo(org, {
|
||||
type: context.identifier,
|
||||
output: context.output,
|
||||
customParams: context.customParams.reduce(
|
||||
(all, current) => ({
|
||||
...all,
|
||||
[current.key]: current.value,
|
||||
}),
|
||||
{}
|
||||
),
|
||||
});
|
||||
|
||||
return {
|
||||
url: value.path,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import {
|
||||
AgentToolInterface,
|
||||
ToolReturn,
|
||||
} from '@gitroom/nestjs-libraries/chat/agent.tool.interface';
|
||||
import { createTool } from '@mastra/core/tools';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
||||
import z from 'zod';
|
||||
import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';
|
||||
import { getAuth } from '@gitroom/nestjs-libraries/chat/async.storage';
|
||||
|
||||
@Injectable()
|
||||
export class IntegrationListTool implements AgentToolInterface {
|
||||
constructor(private _integrationService: IntegrationService) {}
|
||||
name = 'integrationList';
|
||||
|
||||
run() {
|
||||
return createTool({
|
||||
id: 'integrationList',
|
||||
description: `This tool list available integrations to schedule posts to`,
|
||||
outputSchema: z.object({
|
||||
output: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
picture: z.string(),
|
||||
platform: z.string(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
execute: async (args, options) => {
|
||||
console.log(getAuth());
|
||||
console.log(options);
|
||||
const { context, runtimeContext } = args;
|
||||
checkAuth(args, options);
|
||||
const organizationId = JSON.parse(
|
||||
// @ts-ignore
|
||||
runtimeContext.get('organization') as string
|
||||
).id;
|
||||
|
||||
return {
|
||||
output: (
|
||||
await this._integrationService.getIntegrationsList(organizationId)
|
||||
).map((p) => ({
|
||||
name: p.name,
|
||||
id: p.id,
|
||||
disabled: p.disabled,
|
||||
picture: p.picture || '/no-picture.jpg',
|
||||
platform: p.providerIdentifier,
|
||||
display: p.profile,
|
||||
type: p.type,
|
||||
})),
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
|||
import { AllProvidersSettings } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/all.providers.settings';
|
||||
import { validate } from 'class-validator';
|
||||
import { Integration } from '@prisma/client';
|
||||
import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';
|
||||
|
||||
@Injectable()
|
||||
export class IntegrationSchedulePostTool implements AgentToolInterface {
|
||||
|
|
@ -18,7 +19,7 @@ export class IntegrationSchedulePostTool implements AgentToolInterface {
|
|||
) {}
|
||||
name = 'integrationSchedulePostTool';
|
||||
|
||||
async run(): Promise<any> {
|
||||
run() {
|
||||
return createTool({
|
||||
id: 'schedulePostTool',
|
||||
description: `
|
||||
|
|
@ -56,7 +57,11 @@ If the tools return errors, you would need to rerun it with the right parameters
|
|||
postsAndComments: z
|
||||
.array(
|
||||
z.object({
|
||||
content: z.string().describe('The content of the post'),
|
||||
content: z
|
||||
.string()
|
||||
.describe(
|
||||
"The content of the post, HTML, Each line must be wrapped in <p> here is the possible tags: h1, h2, h3, u, strong, li, ul, p (you can't have u and strong together)"
|
||||
),
|
||||
attachments: z
|
||||
.array(z.string())
|
||||
.describe('The image of the post (URLS)'),
|
||||
|
|
@ -97,10 +102,14 @@ If the tools return errors, you would need to rerun it with the right parameters
|
|||
)
|
||||
.or(z.object({ errors: z.string() })),
|
||||
}),
|
||||
execute: async ({ runtimeContext, context }) => {
|
||||
execute: async (args, options) => {
|
||||
const { context, runtimeContext } = args;
|
||||
checkAuth(args, options);
|
||||
console.log(JSON.stringify(context, null, 2));
|
||||
// @ts-ignore
|
||||
const organizationId = runtimeContext.get('organization') as string;
|
||||
const organizationId = JSON.parse(
|
||||
// @ts-ignore
|
||||
runtimeContext.get('organization') as string
|
||||
).id;
|
||||
const finalOutput = [];
|
||||
|
||||
const integrations = {} as Record<string, Integration>;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
|
|||
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
||||
import { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||
import { timer } from '@gitroom/helpers/utils/timer';
|
||||
import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';
|
||||
|
||||
@Injectable()
|
||||
export class IntegrationTriggerTool implements AgentToolInterface {
|
||||
|
|
@ -19,7 +20,7 @@ export class IntegrationTriggerTool implements AgentToolInterface {
|
|||
) {}
|
||||
name = 'triggerTool';
|
||||
|
||||
async run(): Promise<any> {
|
||||
run() {
|
||||
return createTool({
|
||||
id: 'triggerTool',
|
||||
description: `After using the integrationSchema, we sometimes miss details we can\'t ask from the user, like ids.
|
||||
|
|
@ -39,12 +40,16 @@ export class IntegrationTriggerTool implements AgentToolInterface {
|
|||
),
|
||||
}),
|
||||
outputSchema: z.object({
|
||||
output: z.array(z.object()),
|
||||
output: z.array(z.record(z.string(), z.any())),
|
||||
}),
|
||||
execute: async ({ runtimeContext, context }) => {
|
||||
execute: async (args, options) => {
|
||||
const { context, runtimeContext } = args;
|
||||
checkAuth(args, options);
|
||||
console.log('triggerTool', context);
|
||||
// @ts-ignore
|
||||
const organizationId = runtimeContext.get('organization') as string;
|
||||
const organizationId = JSON.parse(
|
||||
// @ts-ignore
|
||||
runtimeContext.get('organization') as string
|
||||
).id;
|
||||
|
||||
const getIntegration =
|
||||
await this._integrationService.getIntegrationById(
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@ import {
|
|||
socialIntegrationList,
|
||||
} from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
||||
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
|
||||
import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';
|
||||
|
||||
@Injectable()
|
||||
export class IntegrationValidationTool implements AgentToolInterface {
|
||||
constructor(private _integrationManager: IntegrationManager) {}
|
||||
name = 'integrationSchema';
|
||||
|
||||
async run(): Promise<any> {
|
||||
run() {
|
||||
return createTool({
|
||||
id: 'integrationSchema',
|
||||
description: `Everytime we want to schedule a social media post, we need to understand the schema of the integration.
|
||||
|
|
@ -71,7 +72,9 @@ export class IntegrationValidationTool implements AgentToolInterface {
|
|||
),
|
||||
}),
|
||||
}),
|
||||
execute: async ({ context }) => {
|
||||
execute: async (args, options) => {
|
||||
const { context, runtimeContext } = args;
|
||||
checkAuth(args, options);
|
||||
const integration = socialIntegrationList.find(
|
||||
(p) => p.identifier === context.platform
|
||||
)!;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
import { IntegrationValidationTool } from '@gitroom/nestjs-libraries/chat/tools/integration.validation.tool';
|
||||
import { IntegrationTriggerTool } from '@gitroom/nestjs-libraries/chat/tools/integration.trigger.tool';
|
||||
import { IntegrationSchedulePostTool } from './integration.schedule.post';
|
||||
import { GenerateVideoOptionsTool } from '@gitroom/nestjs-libraries/chat/tools/generate.video.options.tool';
|
||||
import { VideoFunctionTool } from '@gitroom/nestjs-libraries/chat/tools/video.function.tool';
|
||||
import { GenerateVideoTool } from '@gitroom/nestjs-libraries/chat/tools/generate.video.tool';
|
||||
import { GenerateImageTool } from '@gitroom/nestjs-libraries/chat/tools/generate.image.tool';
|
||||
import { IntegrationListTool } from '@gitroom/nestjs-libraries/chat/tools/integration.list.tool';
|
||||
|
||||
export const toolList = [
|
||||
IntegrationListTool,
|
||||
IntegrationValidationTool,
|
||||
IntegrationTriggerTool,
|
||||
IntegrationSchedulePostTool,
|
||||
GenerateVideoOptionsTool,
|
||||
VideoFunctionTool,
|
||||
GenerateVideoTool,
|
||||
GenerateImageTool,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
import { AgentToolInterface } from '@gitroom/nestjs-libraries/chat/agent.tool.interface';
|
||||
import { createTool } from '@mastra/core/tools';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';
|
||||
import z from 'zod';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';
|
||||
|
||||
@Injectable()
|
||||
export class VideoFunctionTool implements AgentToolInterface {
|
||||
constructor(
|
||||
private _videoManagerService: VideoManager,
|
||||
private _moduleRef: ModuleRef
|
||||
) {}
|
||||
name = 'videoFunctionTool';
|
||||
|
||||
run() {
|
||||
return createTool({
|
||||
id: 'videoFunctionTool',
|
||||
description: `Sometimes when we want to generate videos we might need to get some additional information like voice_id, etc`,
|
||||
inputSchema: z.object({
|
||||
identifier: z.string(),
|
||||
functionName: z.string(),
|
||||
}),
|
||||
execute: async (args, options) => {
|
||||
const { context, runtimeContext } = args;
|
||||
checkAuth(args, options);
|
||||
const videos = this._videoManagerService.getAllVideos();
|
||||
const findVideo = videos.find(
|
||||
(p) =>
|
||||
p.identifier === context.identifier &&
|
||||
p.tools.some((p) => p.functionName === context.functionName)
|
||||
);
|
||||
|
||||
if (!findVideo) {
|
||||
return { error: 'Function not found' };
|
||||
}
|
||||
|
||||
const func = await this._moduleRef
|
||||
// @ts-ignore
|
||||
.get(findVideo.target, { strict: false })
|
||||
[context.functionName]();
|
||||
return func;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ export class MediaService {
|
|||
org: Organization,
|
||||
generatePromptFirst?: boolean
|
||||
) {
|
||||
return await this._subscriptionService.useCredit(
|
||||
const generating = await this._subscriptionService.useCredit(
|
||||
org,
|
||||
'ai_images',
|
||||
async () => {
|
||||
|
|
@ -48,6 +48,8 @@ export class MediaService {
|
|||
return this._openAi.generateImage(prompt, !!generatePromptFirst);
|
||||
}
|
||||
);
|
||||
|
||||
return generating;
|
||||
}
|
||||
|
||||
saveFile(org: string, fileName: string, filePath: string) {
|
||||
|
|
@ -84,6 +86,7 @@ export class MediaService {
|
|||
org,
|
||||
'ai_videos'
|
||||
);
|
||||
|
||||
if (totalCredits.credits <= 0) {
|
||||
throw new SubscriptionException({
|
||||
action: AuthorizationActions.Create,
|
||||
|
|
@ -100,7 +103,9 @@ export class MediaService {
|
|||
throw new HttpException('This video is not available in trial mode', 406);
|
||||
}
|
||||
|
||||
console.log(body.customParams);
|
||||
await video.instance.processAndValidate(body.customParams);
|
||||
console.log('no err');
|
||||
|
||||
return await this._subscriptionService.useCredit(
|
||||
org,
|
||||
|
|
@ -125,8 +130,14 @@ export class MediaService {
|
|||
|
||||
// @ts-ignore
|
||||
const functionToCall = video.instance[functionName];
|
||||
if (typeof functionToCall !== 'function' || this._videoManager.checkAvailableVideoFunction(functionToCall)) {
|
||||
throw new HttpException(`Function ${functionName} not found on video instance`, 400);
|
||||
if (
|
||||
typeof functionToCall !== 'function' ||
|
||||
this._videoManager.checkAvailableVideoFunction(functionToCall)
|
||||
) {
|
||||
throw new HttpException(
|
||||
`Function ${functionName} not found on video instance`,
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
return functionToCall(body);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import { IsDefined, IsString, MinLength } from 'class-validator';
|
||||
import { JSONSchema } from 'class-validator-jsonschema';
|
||||
|
||||
export class DiscordDto {
|
||||
@MinLength(1)
|
||||
@IsDefined()
|
||||
@IsString()
|
||||
@JSONSchema({
|
||||
description: 'Channel must be an id',
|
||||
})
|
||||
channel: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { IsOptional, IsString, MinLength } from 'class-validator';
|
||||
import { JSONSchema } from 'class-validator-jsonschema';
|
||||
|
||||
export class ListmonkDto {
|
||||
@IsString()
|
||||
|
|
@ -9,9 +10,15 @@ export class ListmonkDto {
|
|||
preview: string;
|
||||
|
||||
@IsString()
|
||||
@JSONSchema({
|
||||
description: 'List must be an id',
|
||||
})
|
||||
list: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@JSONSchema({
|
||||
description: 'Template must be an id',
|
||||
})
|
||||
template: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
IsDefined, IsOptional, IsString, IsUrl, MaxLength, MinLength, ValidateIf
|
||||
} from 'class-validator';
|
||||
import { JSONSchema } from 'class-validator-jsonschema';
|
||||
|
||||
export class PinterestSettingsDto {
|
||||
@IsString()
|
||||
|
|
@ -25,6 +26,9 @@ export class PinterestSettingsDto {
|
|||
})
|
||||
@MinLength(1, {
|
||||
message: 'Board is required',
|
||||
})
|
||||
@JSONSchema({
|
||||
description: 'board must be an id',
|
||||
})
|
||||
board: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import { IsDefined, IsString, MinLength } from 'class-validator';
|
||||
import { JSONSchema } from 'class-validator-jsonschema';
|
||||
|
||||
export class SlackDto {
|
||||
@MinLength(1)
|
||||
@IsDefined()
|
||||
@IsString()
|
||||
@JSONSchema({
|
||||
description: 'Channel must be an id',
|
||||
})
|
||||
channel: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import EventEmitter from 'events';
|
||||
import { finalize, fromEvent, startWith } from 'rxjs';
|
||||
import { McpTransport } from '@gitroom/nestjs-libraries/mcp/mcp.transport';
|
||||
import { JSONRPCMessageSchema } from '@gitroom/nestjs-libraries/mcp/mcp.types';
|
||||
import { McpSettings } from '@gitroom/nestjs-libraries/mcp/mcp.settings';
|
||||
import { MainMcp } from '@gitroom/backend/mcp/main.mcp';
|
||||
|
||||
@Injectable()
|
||||
export class McpService {
|
||||
static event = new EventEmitter();
|
||||
constructor(private _mainMcp: MainMcp) {}
|
||||
|
||||
async runServer(apiKey: string, organization: string) {
|
||||
const server = McpSettings.load(organization, this._mainMcp).server();
|
||||
const transport = new McpTransport(organization);
|
||||
|
||||
const observer = fromEvent(
|
||||
McpService.event,
|
||||
`organization-${organization}`
|
||||
).pipe(
|
||||
startWith({
|
||||
type: 'endpoint',
|
||||
data:
|
||||
process.env.NEXT_PUBLIC_BACKEND_URL + '/mcp/' + apiKey + '/messages',
|
||||
}),
|
||||
finalize(() => {
|
||||
transport.close();
|
||||
})
|
||||
);
|
||||
|
||||
await server.connect(transport);
|
||||
|
||||
return observer;
|
||||
}
|
||||
|
||||
async processPostBody(organization: string, body: object) {
|
||||
const server = McpSettings.load(organization, this._mainMcp).server();
|
||||
const message = JSONRPCMessageSchema.parse(body);
|
||||
const transport = new McpTransport(organization);
|
||||
await server.connect(transport);
|
||||
transport.handlePostMessage(message);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { MainMcp } from '@gitroom/backend/mcp/main.mcp';
|
||||
import { socialIntegrationList } from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
||||
import * as Sentry from '@sentry/nestjs';
|
||||
export class McpSettings {
|
||||
private _server: McpServer;
|
||||
createServer(organization: string, service: MainMcp) {
|
||||
this._server = Sentry.wrapMcpServerWithSentry(new McpServer(
|
||||
{
|
||||
name: 'Postiz',
|
||||
version: '2.0.0',
|
||||
},
|
||||
{
|
||||
instructions: `Postiz is a service to schedule social media posts for ${socialIntegrationList
|
||||
.map((p) => p.name)
|
||||
.join(
|
||||
', '
|
||||
)} to schedule you need to have the providerId (you can get it from POSTIZ_PROVIDERS_LIST), user need to specify the schedule date (or now), text, you also can send base64 images and text for the comments. When you get POSTIZ_PROVIDERS_LIST, always display all the options to the user`,
|
||||
}
|
||||
));
|
||||
|
||||
for (const usePrompt of Reflect.getMetadata(
|
||||
'MCP_PROMPT',
|
||||
MainMcp.prototype
|
||||
) || []) {
|
||||
const list = [
|
||||
usePrompt.data.promptName,
|
||||
usePrompt.data.zod,
|
||||
async (...args: any[]) => {
|
||||
return {
|
||||
// @ts-ignore
|
||||
messages: await service[usePrompt.func as string](
|
||||
organization,
|
||||
...args
|
||||
),
|
||||
};
|
||||
},
|
||||
].filter((f) => f);
|
||||
this._server.prompt(...(list as [any, any, any]));
|
||||
}
|
||||
|
||||
for (const usePrompt of Reflect.getMetadata(
|
||||
'MCP_TOOL',
|
||||
MainMcp.prototype
|
||||
) || []) {
|
||||
const list: any[] = [
|
||||
usePrompt.data.toolName,
|
||||
usePrompt.data.zod,
|
||||
async (...args: any[]) => {
|
||||
return {
|
||||
// @ts-ignore
|
||||
content: await service[usePrompt.func as string](
|
||||
organization,
|
||||
...args
|
||||
),
|
||||
};
|
||||
},
|
||||
].filter((f) => f);
|
||||
|
||||
this._server.tool(...(list as [any, any, any]));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
server() {
|
||||
return this._server;
|
||||
}
|
||||
|
||||
static load(organization: string, service: MainMcp): McpSettings {
|
||||
return new McpSettings().createServer(organization, service);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import { ZodRawShape } from 'zod';
|
||||
|
||||
export function McpTool(params: { toolName: string; zod?: ZodRawShape }) {
|
||||
return function (
|
||||
target: any,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: PropertyDescriptor
|
||||
) {
|
||||
const existingMetadata = Reflect.getMetadata('MCP_TOOL', target) || [];
|
||||
|
||||
// Add the metadata information for this method
|
||||
existingMetadata.push({ data: params, func: propertyKey });
|
||||
|
||||
// Define metadata on the class prototype (so it can be retrieved from the class)
|
||||
Reflect.defineMetadata('MCP_TOOL', existingMetadata, target);
|
||||
};
|
||||
}
|
||||
|
||||
export function McpPrompt(params: { promptName: string; zod?: ZodRawShape }) {
|
||||
return function (
|
||||
target: any,
|
||||
propertyKey: string | symbol,
|
||||
descriptor: PropertyDescriptor
|
||||
) {
|
||||
const existingMetadata = Reflect.getMetadata('MCP_PROMPT', target) || [];
|
||||
|
||||
// Add the metadata information for this method
|
||||
existingMetadata.push({ data: params, func: propertyKey });
|
||||
|
||||
// Define metadata on the class prototype (so it can be retrieved from the class)
|
||||
Reflect.defineMetadata('MCP_PROMPT', existingMetadata, target);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||
import { McpService } from '@gitroom/nestjs-libraries/mcp/mcp.service';
|
||||
import {
|
||||
JSONRPCMessage,
|
||||
JSONRPCMessageSchema,
|
||||
} from '@gitroom/nestjs-libraries/mcp/mcp.types';
|
||||
|
||||
export class McpTransport implements Transport {
|
||||
constructor(private _organization: string) {}
|
||||
|
||||
onclose?: () => void;
|
||||
onerror?: (error: Error) => void;
|
||||
onmessage?: (message: JSONRPCMessage) => void;
|
||||
|
||||
async start() {}
|
||||
|
||||
async send(message: JSONRPCMessage): Promise<void> {
|
||||
McpService.event.emit(`organization-${this._organization}`, {
|
||||
type: 'message',
|
||||
data: JSON.stringify(message),
|
||||
});
|
||||
}
|
||||
|
||||
async close() {
|
||||
McpService.event.removeAllListeners(`organization-${this._organization}`);
|
||||
}
|
||||
|
||||
handlePostMessage(message: any) {
|
||||
let parsedMessage: JSONRPCMessage;
|
||||
|
||||
try {
|
||||
parsedMessage = JSONRPCMessageSchema.parse(message);
|
||||
} catch (error) {
|
||||
this.onerror?.(error as Error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.onmessage?.(parsedMessage);
|
||||
}
|
||||
|
||||
get sessionId() {
|
||||
return this._organization;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -227,34 +227,45 @@ 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 and also have a back dark gradient on top, image prompt should not contain text in the picture, generate between 3-5 slides maximum`;
|
||||
return (
|
||||
(
|
||||
await openai.chat.completions.parse({
|
||||
model: 'gpt-4.1',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: message,
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: text,
|
||||
},
|
||||
],
|
||||
response_format: zodResponseFormat(
|
||||
z.object({
|
||||
slides: z.array(
|
||||
for (let i = 0; i < 3; i++) {
|
||||
try {
|
||||
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 and also have a back dark gradient on top, image prompt should not contain text in the picture, generate between 3-5 slides maximum`;
|
||||
const parse =
|
||||
(
|
||||
await openai.chat.completions.parse({
|
||||
model: 'gpt-4.1',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: message,
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: text,
|
||||
},
|
||||
],
|
||||
response_format: zodResponseFormat(
|
||||
z.object({
|
||||
imagePrompt: z.string(),
|
||||
voiceText: z.string(),
|
||||
})
|
||||
slides: z
|
||||
.array(
|
||||
z.object({
|
||||
imagePrompt: z.string(),
|
||||
voiceText: z.string(),
|
||||
})
|
||||
)
|
||||
.describe('an array of slides'),
|
||||
}),
|
||||
'slides'
|
||||
),
|
||||
}),
|
||||
'slides'
|
||||
),
|
||||
})
|
||||
).choices[0].message.parsed?.slides || []
|
||||
);
|
||||
})
|
||||
).choices[0].message.parsed?.slides || [];
|
||||
|
||||
return parse;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
|
||||
import {
|
||||
ExposeVideoFunction, URL, Video, VideoAbstract
|
||||
ExposeVideoFunction,
|
||||
URL,
|
||||
Video,
|
||||
VideoAbstract,
|
||||
} from '@gitroom/nestjs-libraries/videos/video.interface';
|
||||
import { chunk } from 'lodash';
|
||||
import Transloadit from 'transloadit';
|
||||
|
|
@ -12,6 +15,7 @@ import { stringifySync } from 'subtitle';
|
|||
import pLimit from 'p-limit';
|
||||
import { FalService } from '@gitroom/nestjs-libraries/openai/fal.service';
|
||||
import { IsString } from 'class-validator';
|
||||
import { JSONSchema } from 'class-validator-jsonschema';
|
||||
const limit = pLimit(2);
|
||||
|
||||
const transloadit = new Transloadit({
|
||||
|
|
@ -24,10 +28,16 @@ async function getAudioDuration(buffer: Buffer): Promise<number> {
|
|||
return metadata.format.duration || 0;
|
||||
}
|
||||
|
||||
class Params {
|
||||
class ImagesSlidesParams {
|
||||
@JSONSchema({
|
||||
description: 'Elevenlabs voice id, use a special tool to get it, this is a required filed',
|
||||
})
|
||||
@IsString()
|
||||
voice: string;
|
||||
|
||||
@JSONSchema({
|
||||
description: 'Simple string of the prompt, not a json',
|
||||
})
|
||||
@IsString()
|
||||
prompt: string;
|
||||
}
|
||||
|
|
@ -35,8 +45,10 @@ class Params {
|
|||
@Video({
|
||||
identifier: 'image-text-slides',
|
||||
title: 'Image Text Slides',
|
||||
description: 'Generate videos slides from images and text',
|
||||
description: 'Generate videos slides from images and text, Don\'t break down the slides, provide only the first slide information',
|
||||
placement: 'text-to-image',
|
||||
tools: [{ functionName: 'loadVoices', output: 'voice id' }],
|
||||
dto: ImagesSlidesParams,
|
||||
trial: true,
|
||||
available:
|
||||
!!process.env.ELEVENSLABS_API_KEY &&
|
||||
|
|
@ -45,8 +57,8 @@ class Params {
|
|||
!!process.env.OPENAI_API_KEY &&
|
||||
!!process.env.FAL_KEY,
|
||||
})
|
||||
export class ImagesSlides extends VideoAbstract<Params> {
|
||||
override dto = Params;
|
||||
export class ImagesSlides extends VideoAbstract<ImagesSlidesParams> {
|
||||
override dto = ImagesSlidesParams;
|
||||
private storage = UploadFactory.createStorage();
|
||||
constructor(
|
||||
private _openaiService: OpenaiService,
|
||||
|
|
@ -57,7 +69,7 @@ export class ImagesSlides extends VideoAbstract<Params> {
|
|||
|
||||
async process(
|
||||
output: 'vertical' | 'horizontal',
|
||||
customParams: Params
|
||||
customParams: ImagesSlidesParams
|
||||
): Promise<URL> {
|
||||
const list = await this._openaiService.generateSlidesFromText(
|
||||
customParams.prompt
|
||||
|
|
@ -153,6 +165,8 @@ export class ImagesSlides extends VideoAbstract<Params> {
|
|||
{ format: 'SRT' }
|
||||
);
|
||||
|
||||
console.log(split);
|
||||
|
||||
const { results } = await transloadit.createAssembly({
|
||||
uploads: {
|
||||
'subtitles.srt': srt,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class Image {
|
|||
@IsString()
|
||||
path: string;
|
||||
}
|
||||
class Params {
|
||||
class Veo3Params {
|
||||
@IsString()
|
||||
prompt: string;
|
||||
|
||||
|
|
@ -30,14 +30,16 @@ class Params {
|
|||
title: 'Veo3 (Audio + Video)',
|
||||
description: 'Generate videos with the most advanced video model.',
|
||||
placement: 'text-to-image',
|
||||
dto: Veo3Params,
|
||||
tools: [],
|
||||
trial: false,
|
||||
available: !!process.env.KIEAI_API_KEY,
|
||||
})
|
||||
export class Veo3 extends VideoAbstract<Params> {
|
||||
override dto = Params;
|
||||
export class Veo3 extends VideoAbstract<Veo3Params> {
|
||||
override dto = Veo3Params;
|
||||
async process(
|
||||
output: 'vertical' | 'horizontal',
|
||||
customParams: Params
|
||||
customParams: Veo3Params
|
||||
): Promise<URL> {
|
||||
const value = await (
|
||||
await fetch('https://api.kie.ai/api/v1/veo/generate', {
|
||||
|
|
|
|||
|
|
@ -30,18 +30,24 @@ export interface VideoParams {
|
|||
identifier: string;
|
||||
title: string;
|
||||
description: string;
|
||||
dto: any;
|
||||
placement: 'text-to-image' | 'image-to-video' | 'video-to-video';
|
||||
tools: { functionName: string; output: string }[];
|
||||
available: boolean;
|
||||
trial: boolean;
|
||||
}
|
||||
|
||||
export function ExposeVideoFunction() {
|
||||
export function ExposeVideoFunction(description?: string) {
|
||||
return function (
|
||||
target: any,
|
||||
propertyKey: string,
|
||||
descriptor: PropertyDescriptor
|
||||
) {
|
||||
Reflect.defineMetadata('video-function', 'true', descriptor.value);
|
||||
Reflect.defineMetadata(
|
||||
'video-function',
|
||||
description || 'true',
|
||||
descriptor.value
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,16 +10,28 @@ import {
|
|||
export class VideoManager {
|
||||
constructor(private _moduleRef: ModuleRef) {}
|
||||
|
||||
getAllVideos(): any[] {
|
||||
return (Reflect.getMetadata('video', VideoAbstract) || []).filter((f: any) => f.available).map(
|
||||
(p: any) => ({
|
||||
getAllVideos(): {
|
||||
identifier: string;
|
||||
title: string;
|
||||
dto: any;
|
||||
description: string;
|
||||
target: VideoAbstract<any>,
|
||||
tools: { functionName: string; output: string }[];
|
||||
placement: string;
|
||||
trial: boolean;
|
||||
}[] {
|
||||
return (Reflect.getMetadata('video', VideoAbstract) || [])
|
||||
.filter((f: any) => f.available)
|
||||
.map((p: any) => ({
|
||||
target: p.target,
|
||||
identifier: p.identifier,
|
||||
title: p.title,
|
||||
tools: p.tools,
|
||||
dto: p.dto,
|
||||
description: p.description,
|
||||
placement: p.placement,
|
||||
trial: p.trial,
|
||||
})
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
checkAvailableVideoFunction(method: any) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "فيديو تم إنشاؤه بالذكاء الاصطناعي",
|
||||
"please_add_at_least": "يرجى إضافة 20 حرفًا على الأقل",
|
||||
"send_invitation_via_email": "إرسال دعوة عبر البريد الإلكتروني؟",
|
||||
"global_settings": "الإعدادات العامة",
|
||||
"copy_id": "نسخ معرف القناة",
|
||||
"team_members": "أعضاء الفريق",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "ادعُ مساعدك أو أحد أعضاء فريقك لإدارة حسابك",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "إظهار",
|
||||
"copy_key": "نسخ المفتاح",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "قم بربط عميل MCP الخاص بك بـ Postiz لجدولة منشوراتك بشكل أسرع!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "قم بتوصيل خادم Postiz MCP بعميلك (بث HTTP) لجدولة منشوراتك بشكل أسرع!",
|
||||
"share_with_a_client": "مشاركة مع عميل",
|
||||
"post": "منشور",
|
||||
"comments": "تعليقات",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "এআই দিয়ে তৈরি ভিডিও",
|
||||
"please_add_at_least": "অনুগ্রহ করে অন্তত ২০টি অক্ষর যোগ করুন",
|
||||
"send_invitation_via_email": "ইমেইলের মাধ্যমে আমন্ত্রণ পাঠান?",
|
||||
"global_settings": "গ্লোবাল সেটিংস",
|
||||
"copy_id": "চ্যানেল আইডি কপি করুন",
|
||||
"team_members": "দলের সদস্যরা",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "আপনার অ্যাকাউন্ট পরিচালনা করতে আপনার সহায়ক বা দলের সদস্যকে আমন্ত্রণ জানান",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "প্রকাশ করুন",
|
||||
"copy_key": "কী কপি করুন",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "আপনার পোস্টগুলি দ্রুত সময়সূচী করতে আপনার MCP ক্লায়েন্টকে Postiz-এর সাথে সংযুক্ত করুন!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "আপনার ক্লায়েন্টকে (Http স্ট্রিমিং) Postiz MCP সার্ভারের সাথে সংযুক্ত করুন, যাতে আরও দ্রুত আপনার পোস্টগুলো নির্ধারণ করতে পারেন!",
|
||||
"share_with_a_client": "একটি ক্লায়েন্টের সাথে শেয়ার করুন",
|
||||
"post": "পোস্ট",
|
||||
"comments": "মন্তব্য",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "Video mit KI erstellt",
|
||||
"please_add_at_least": "Bitte füge mindestens 20 Zeichen hinzu",
|
||||
"send_invitation_via_email": "Einladung per E-Mail senden?",
|
||||
"global_settings": "Globale Einstellungen",
|
||||
"copy_id": "Kanal-ID kopieren",
|
||||
"team_members": "Teammitglieder",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "Laden Sie Ihren Assistenten oder Ihr Teammitglied ein, Ihr Konto zu verwalten",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "Anzeigen",
|
||||
"copy_key": "Schlüssel kopieren",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Verbinden Sie Ihren MCP-Client mit Postiz, um Ihre Beiträge schneller zu planen!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Verbinden Sie den Postiz MCP-Server mit Ihrem Client (HTTP-Streaming), um Ihre Beiträge schneller zu planen!",
|
||||
"share_with_a_client": "Mit einem Kunden teilen",
|
||||
"post": "Beitrag",
|
||||
"comments": "Kommentare",
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
"reveal": "Reveal",
|
||||
"copy_key": "Copy Key",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Connect your MCP client to Postiz to schedule your posts faster!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Connect Postiz MCP server to your client (Http streaming) to schedule your posts faster!",
|
||||
"share_with_a_client": "Share with a client",
|
||||
"post": "Post",
|
||||
"comments": "Comments",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "Video hecho con IA",
|
||||
"please_add_at_least": "Por favor, añade al menos 20 caracteres",
|
||||
"send_invitation_via_email": "¿Enviar invitación por correo electrónico?",
|
||||
"global_settings": "Configuración global",
|
||||
"copy_id": "Copiar ID del canal",
|
||||
"team_members": "Miembros del equipo",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "Invita a tu asistente o miembro del equipo para que gestione tu cuenta",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "Revelar",
|
||||
"copy_key": "Copiar clave",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "¡Conecta tu cliente MCP a Postiz para programar tus publicaciones más rápido!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "¡Conecta el servidor MCP de Postiz a tu cliente (transmisión HTTP) para programar tus publicaciones más rápido!",
|
||||
"share_with_a_client": "Compartir con un cliente",
|
||||
"post": "Publicar",
|
||||
"comments": "Comentarios",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "Vidéo réalisée avec l'IA",
|
||||
"please_add_at_least": "Veuillez ajouter au moins 20 caractères",
|
||||
"send_invitation_via_email": "Envoyer l'invitation par e-mail ?",
|
||||
"global_settings": "Paramètres globaux",
|
||||
"copy_id": "Copier l'identifiant de la chaîne",
|
||||
"team_members": "Membres de l'équipe",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "Invitez votre assistant ou un membre de votre équipe à gérer votre compte",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "Révéler",
|
||||
"copy_key": "Copier la clé",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Connectez votre client MCP à Postiz pour planifier vos publications plus rapidement !",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Connectez le serveur MCP de Postiz à votre client (streaming HTTP) pour programmer vos publications plus rapidement !",
|
||||
"share_with_a_client": "Partager avec un client",
|
||||
"post": "Publication",
|
||||
"comments": "Commentaires",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "וידאו שנוצר באמצעות בינה מלאכותית",
|
||||
"please_add_at_least": "אנא הוסף לפחות 20 תווים",
|
||||
"send_invitation_via_email": "לשלוח הזמנה בדוא\"ל?",
|
||||
"global_settings": "הגדרות כלליות",
|
||||
"copy_id": "העתק מזהה ערוץ",
|
||||
"team_members": "חברי צוות",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "הזמן את העוזר או חבר הצוות שלך לנהל את החשבון שלך",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "הצג",
|
||||
"copy_key": "העתק מפתח",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "חבר את לקוח ה-MCP שלך ל-Postiz כדי לתזמן את הפוסטים שלך מהר יותר!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "חבר את שרת ה-MCP של Postiz ללקוח שלך (הזרמת Http) כדי לתזמן את הפוסטים שלך מהר יותר!",
|
||||
"share_with_a_client": "שתף עם לקוח",
|
||||
"post": "פוסט",
|
||||
"comments": "תגובות",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "Video realizzato con l'IA",
|
||||
"please_add_at_least": "Per favore aggiungi almeno 20 caratteri",
|
||||
"send_invitation_via_email": "Inviare l'invito via email?",
|
||||
"global_settings": "Impostazioni globali",
|
||||
"copy_id": "Copia ID canale",
|
||||
"team_members": "Membri del team",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "Invita il tuo assistente o un membro del team a gestire il tuo account",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "Mostra",
|
||||
"copy_key": "Copia chiave",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Collega il tuo client MCP a Postiz per programmare i tuoi post più velocemente!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Collega il server MCP di Postiz al tuo client (streaming Http) per programmare i tuoi post più velocemente!",
|
||||
"share_with_a_client": "Condividi con un cliente",
|
||||
"post": "Post",
|
||||
"comments": "Commenti",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "AIで作成された動画",
|
||||
"please_add_at_least": "少なくとも20文字を追加してください",
|
||||
"send_invitation_via_email": "メールで招待を送信しますか?",
|
||||
"global_settings": "グローバル設定",
|
||||
"copy_id": "チャンネルIDをコピー",
|
||||
"team_members": "チームメンバー",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "アシスタントやチームメンバーを招待してアカウントを管理してもらいましょう",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "表示",
|
||||
"copy_key": "キーをコピー",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "MCPクライアントをPostizに接続して、投稿をより早くスケジューリングしましょう!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "投稿をより迅速にスケジュールするために、Postiz MCPサーバーをクライアント(HTTPストリーミング)に接続しましょう!",
|
||||
"share_with_a_client": "クライアントと共有",
|
||||
"post": "投稿",
|
||||
"comments": "コメント",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "AI로 제작된 영상",
|
||||
"please_add_at_least": "최소 20자 이상 입력해 주세요",
|
||||
"send_invitation_via_email": "이메일로 초대장을 보내시겠습니까?",
|
||||
"global_settings": "글로벌 설정",
|
||||
"copy_id": "채널 ID 복사",
|
||||
"team_members": "팀원",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "계정을 관리할 수 있도록 어시스턴트나 팀원을 초대하세요.",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "표시",
|
||||
"copy_key": "키 복사",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "MCP 클라이언트를 Postiz에 연결하여 게시물을 더 빠르게 예약하세요!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "게시물을 더 빠르게 예약하려면 Postiz MCP 서버를 클라이언트에 연결하세요(Http 스트리밍).",
|
||||
"share_with_a_client": "클라이언트와 공유",
|
||||
"post": "게시물",
|
||||
"comments": "댓글",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "Vídeo feito com IA",
|
||||
"please_add_at_least": "Por favor, adicione pelo menos 20 caracteres",
|
||||
"send_invitation_via_email": "Enviar convite por e-mail?",
|
||||
"global_settings": "Configurações Globais",
|
||||
"copy_id": "Copiar ID do canal",
|
||||
"team_members": "Membros da equipe",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "Convide seu assistente ou membro da equipe para gerenciar sua conta",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "Revelar",
|
||||
"copy_key": "Copiar chave",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Conecte seu cliente MCP ao Postiz para agendar seus posts mais rápido!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Conecte o servidor MCP do Postiz ao seu cliente (transmissão HTTP) para agendar suas postagens mais rapidamente!",
|
||||
"share_with_a_client": "Compartilhar com um cliente",
|
||||
"post": "Postar",
|
||||
"comments": "Comentários",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "Видео создано с помощью ИИ",
|
||||
"please_add_at_least": "Пожалуйста, добавьте не менее 20 символов",
|
||||
"send_invitation_via_email": "Отправить приглашение по электронной почте?",
|
||||
"global_settings": "Глобальные настройки",
|
||||
"copy_id": "Скопировать ID канала",
|
||||
"team_members": "Члены команды",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "Пригласите помощника или члена команды для управления вашим аккаунтом",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "Показать",
|
||||
"copy_key": "Скопировать ключ",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Подключите ваш MCP-клиент к Postiz, чтобы быстрее планировать публикации!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Подключите сервер Postiz MCP к вашему клиенту (HTTP-поток), чтобы быстрее планировать ваши публикации!",
|
||||
"share_with_a_client": "Поделиться с клиентом",
|
||||
"post": "Пост",
|
||||
"comments": "Комментарии",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "Video yapay zeka ile oluşturuldu",
|
||||
"please_add_at_least": "Lütfen en az 20 karakter ekleyin",
|
||||
"send_invitation_via_email": "Davetiyeyi e-posta ile gönderilsin mi?",
|
||||
"global_settings": "Genel Ayarlar",
|
||||
"copy_id": "Kanal Kimliğini Kopyala",
|
||||
"team_members": "Takım Üyeleri",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "Hesabınızı yönetmesi için asistanınızı veya takım üyenizi davet edin",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "Göster",
|
||||
"copy_key": "Anahtarı Kopyala",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Gönderilerinizi daha hızlı planlamak için MCP istemcinizi Postiz'e bağlayın!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Gönderilerinizi daha hızlı planlamak için Postiz MCP sunucusunu istemcinize (Http akışı) bağlayın!",
|
||||
"share_with_a_client": "Bir müşteriyle paylaş",
|
||||
"post": "Gönderi",
|
||||
"comments": "Yorumlar",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "Video được tạo bằng AI",
|
||||
"please_add_at_least": "Vui lòng thêm ít nhất 20 ký tự",
|
||||
"send_invitation_via_email": "Gửi lời mời qua email?",
|
||||
"global_settings": "Cài đặt toàn cục",
|
||||
"copy_id": "Sao chép ID kênh",
|
||||
"team_members": "Thành viên nhóm",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "Mời trợ lý hoặc thành viên nhóm của bạn để quản lý tài khoản của bạn",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "Hiển thị",
|
||||
"copy_key": "Sao chép khóa",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Kết nối khách hàng MCP của bạn với Postiz để lên lịch bài đăng nhanh hơn!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "Kết nối máy chủ MCP của Postiz với máy khách của bạn (Http streaming) để lên lịch bài đăng nhanh hơn!",
|
||||
"share_with_a_client": "Chia sẻ với khách hàng",
|
||||
"post": "Bài đăng",
|
||||
"comments": "Bình luận",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"video_made_with_ai": "视频由AI制作",
|
||||
"please_add_at_least": "请至少添加20个字符",
|
||||
"send_invitation_via_email": "通过电子邮件发送邀请?",
|
||||
"global_settings": "全局设置",
|
||||
"copy_id": "复制频道ID",
|
||||
"team_members": "团队成员",
|
||||
"invite_your_assistant_or_team_member_to_manage_your_account": "邀请你的助理或团队成员来管理你的账户",
|
||||
|
|
@ -41,7 +42,7 @@
|
|||
"reveal": "显示",
|
||||
"copy_key": "复制密钥",
|
||||
"mcp": "MCP",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "将你的MCP客户端连接到Postiz以更快地安排你的帖子!",
|
||||
"connect_your_mcp_client_to_postiz_to_schedule_your_posts_faster": "将 Postiz MCP 服务器连接到您的客户端(Http 流式传输),以更快地安排您的帖子!",
|
||||
"share_with_a_client": "与客户分享",
|
||||
"post": "帖子",
|
||||
"comments": "评论",
|
||||
|
|
|
|||
22
package.json
22
package.json
|
|
@ -43,16 +43,16 @@
|
|||
"test": "jest --coverage --detectOpenHandles --reporters=default --reporters=jest-junit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ag-ui/mastra": "0.1.0-next.1",
|
||||
"@ai-sdk/openai": "^2.0.38",
|
||||
"@ag-ui/mastra": "0.2.0",
|
||||
"@ai-sdk/openai": "^2.0.52",
|
||||
"@atproto/api": "^0.15.15",
|
||||
"@aws-sdk/client-s3": "^3.787.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.787.0",
|
||||
"@casl/ability": "^6.5.0",
|
||||
"@copilotkit/react-core": "^1.10.4",
|
||||
"@copilotkit/react-textarea": "^1.10.4",
|
||||
"@copilotkit/react-ui": "^1.10.4",
|
||||
"@copilotkit/runtime": "^1.10.4",
|
||||
"@copilotkit/react-core": "1.10.6",
|
||||
"@copilotkit/react-textarea": "1.10.6",
|
||||
"@copilotkit/react-ui": "1.10.6",
|
||||
"@copilotkit/runtime": "1.10.6",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@langchain/community": "^0.3.40",
|
||||
"@langchain/core": "^0.3.44",
|
||||
|
|
@ -62,9 +62,9 @@
|
|||
"@mantine/dates": "^5.10.5",
|
||||
"@mantine/hooks": "^5.10.5",
|
||||
"@mantine/modals": "^5.10.5",
|
||||
"@mastra/core": "^0.18.0",
|
||||
"@mastra/memory": "^0.15.3",
|
||||
"@mastra/pg": "^0.16.1",
|
||||
"@mastra/core": "^0.20.2",
|
||||
"@mastra/memory": "^0.15.6",
|
||||
"@mastra/pg": "^0.17.2",
|
||||
"@modelcontextprotocol/sdk": "^1.9.0",
|
||||
"@nestjs/cli": "10.0.2",
|
||||
"@nestjs/common": "^10.0.2",
|
||||
|
|
@ -180,7 +180,7 @@
|
|||
"node-telegram-bot-api": "^0.66.0",
|
||||
"nodemailer": "^6.9.15",
|
||||
"nostr-tools": "^2.10.4",
|
||||
"openai": "^5.11.0",
|
||||
"openai": "^6.2.0",
|
||||
"p-limit": "^3.1.0",
|
||||
"parse5": "^6.0.1",
|
||||
"polotno": "^2.10.5",
|
||||
|
|
@ -236,7 +236,7 @@
|
|||
"ws": "^8.18.0",
|
||||
"yargs": "^17.7.2",
|
||||
"yup": "^1.4.0",
|
||||
"zod": "^4.1.11",
|
||||
"zod": "^3.25.76",
|
||||
"zustand": "^5.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
790
pnpm-lock.yaml
790
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue