diff --git a/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts b/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts index 94cb46881b..12deef65a3 100644 --- a/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts +++ b/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts @@ -35,6 +35,7 @@ import { import { VideoDto } from '@gitroom/nestjs-libraries/dtos/videos/video.dto'; import { VideoFunctionDto } from '@gitroom/nestjs-libraries/dtos/videos/video.function.dto'; import { UploadDto } from '@gitroom/nestjs-libraries/dtos/media/upload.dto'; +import { GetMediaDto } from '@gitroom/nestjs-libraries/dtos/media/get.media.dto'; import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service'; import { GetNotificationsDto } from '@gitroom/nestjs-libraries/dtos/notifications/get.notifications.dto'; import { Readable } from 'stream'; @@ -289,6 +290,15 @@ export class PublicIntegrationsController { return { connected: true }; } + @Get('/media') + getMedia( + @GetOrgFromRequest() org: Organization, + @Query() query: GetMediaDto + ) { + Sentry.metrics.count('public_api-request', 1); + return this._mediaService.getMedia(org.id, query.page, query.search); + } + @Get('/groups') async listGroups(@GetOrgFromRequest() org: Organization) { Sentry.metrics.count('public_api-request', 1); diff --git a/libraries/nestjs-libraries/src/chat/load.tools.service.ts b/libraries/nestjs-libraries/src/chat/load.tools.service.ts index a0194b6245..4f085a3350 100644 --- a/libraries/nestjs-libraries/src/chat/load.tools.service.ts +++ b/libraries/nestjs-libraries/src/chat/load.tools.service.ts @@ -78,6 +78,7 @@ 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). + - To reuse media that was already uploaded, use mediaListTool (search by filename) and pass the returned path as the attachment, instead of asking the user for a URL or uploading it again. - To find or inspect existing posts, use postsListTool with a UTC start and end date - it returns every post scheduled in that window. To cover "all my upcoming posts", pass a wide window starting now. - To change the provider settings of an existing post that was not published yet (scheduled or draft), first find it with postsListTool, then use postSettingsTool with the post's id. It only updates the settings - the content and the publish date stay as they are - and only the keys you pass are changed (get them with the integrationSchema tool). Show the user which post and which settings will change and get their confirmation first. - Never open the "modal with populated content" to edit an existing post - that modal only CREATES a new post, so using it to edit would duplicate the post. It is only for brand new posts. diff --git a/libraries/nestjs-libraries/src/chat/tools/media.list.tool.ts b/libraries/nestjs-libraries/src/chat/tools/media.list.tool.ts new file mode 100644 index 0000000000..34501cc5b9 --- /dev/null +++ b/libraries/nestjs-libraries/src/chat/tools/media.list.tool.ts @@ -0,0 +1,77 @@ +import { AgentToolInterface } from '@gitroom/nestjs-libraries/chat/agent.tool.interface'; +import { createTool } from '@mastra/core/tools'; +import { Injectable } from '@nestjs/common'; +import { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/media.service'; +import z from 'zod'; +import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context'; + +@Injectable() +export class MediaListTool implements AgentToolInterface { + constructor(private _mediaService: MediaService) {} + name = 'mediaListTool'; + + run() { + return createTool({ + id: 'mediaListTool', + description: `List the media (images and videos) already uploaded to the media library, newest first, 18 per page, optionally searched by original filename. +Use the returned path as an attachment URL in integrationSchedulePostTool instead of uploading the file again.`, + inputSchema: z.object({ + search: z + .string() + .optional() + .describe('Filter by original filename (case-insensitive contains)'), + page: z.number().optional().describe('Page number, starting at 1'), + }), + mcp: { + annotations: { + title: 'List Media', + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: false, + }, + }, + outputSchema: z.object({ + pages: z.number(), + output: z.array( + z.object({ + id: z.string(), + name: z.string(), + originalName: z.string().nullable(), + path: z.string(), + type: z.string(), + fileSize: z.number(), + createdAt: z.string(), + thumbnail: z.string().nullable(), + }) + ), + }), + execute: async (inputData, context) => { + checkAuth(inputData, context); + const organizationId = JSON.parse( + (context?.requestContext as any)?.get('organization') as string + ).id; + + const { pages, results } = await this._mediaService.getMedia( + organizationId, + inputData.page || 1, + inputData.search + ); + + return { + pages, + output: results.map((p) => ({ + id: p.id, + name: p.name, + originalName: p.originalName, + path: p.path, + type: p.type, + fileSize: p.fileSize, + createdAt: p.createdAt.toISOString(), + thumbnail: p.thumbnail, + })), + }; + }, + }); + } +} diff --git a/libraries/nestjs-libraries/src/chat/tools/tool.list.ts b/libraries/nestjs-libraries/src/chat/tools/tool.list.ts index ef386474f2..0586efb164 100644 --- a/libraries/nestjs-libraries/src/chat/tools/tool.list.ts +++ b/libraries/nestjs-libraries/src/chat/tools/tool.list.ts @@ -8,6 +8,7 @@ import { GenerateImageTool } from '@gitroom/nestjs-libraries/chat/tools/generate import { IntegrationListTool } from '@gitroom/nestjs-libraries/chat/tools/integration.list.tool'; import { GroupListTool } from '@gitroom/nestjs-libraries/chat/tools/group.list.tool'; import { UploadFromUrlTool } from '@gitroom/nestjs-libraries/chat/tools/upload.from.url.tool'; +import { MediaListTool } from '@gitroom/nestjs-libraries/chat/tools/media.list.tool'; import { PostsListTool } from '@gitroom/nestjs-libraries/chat/tools/posts.list.tool'; import { PostSettingsTool } from '@gitroom/nestjs-libraries/chat/tools/post.settings.tool'; @@ -24,4 +25,5 @@ export const toolList = [ GenerateVideoTool, GenerateImageTool, UploadFromUrlTool, + MediaListTool, ]; diff --git a/libraries/nestjs-libraries/src/database/prisma/media/media.repository.ts b/libraries/nestjs-libraries/src/database/prisma/media/media.repository.ts index 208def20f6..354e988fc5 100644 --- a/libraries/nestjs-libraries/src/database/prisma/media/media.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/media/media.repository.ts @@ -110,6 +110,9 @@ export class MediaRepository { thumbnail: true, alt: true, thumbnailTimestamp: true, + type: true, + fileSize: true, + createdAt: true, }, skip: pageNum * 18, take: 18, diff --git a/libraries/nestjs-libraries/src/dtos/media/get.media.dto.ts b/libraries/nestjs-libraries/src/dtos/media/get.media.dto.ts new file mode 100644 index 0000000000..6b20a30b70 --- /dev/null +++ b/libraries/nestjs-libraries/src/dtos/media/get.media.dto.ts @@ -0,0 +1,14 @@ +import { IsOptional, IsNumber, IsString, Min } from 'class-validator'; +import { Transform } from 'class-transformer'; + +export class GetMediaDto { + @IsOptional() + @IsNumber() + @Min(1) + @Transform(({ value }) => parseInt(value, 10)) + page?: number = 1; + + @IsOptional() + @IsString() + search?: string; +}