fix(minor-bugs): grafana, zep, video generation, templates fixes (#2336)

* make creator profile required

* fix grafana tag dropdown / outputs mismatch

* fix grafana annotations to make dashboard id required

* fix fal ai

* fix fal ai

* fix zep
This commit is contained in:
Vikhyath Mondreti
2025-12-12 17:02:36 -08:00
committed by GitHub
parent 49d31c80f5
commit 0415eb47fe
19 changed files with 254 additions and 165 deletions

View File

@@ -807,18 +807,31 @@ async function generateWithFalAI(
// Build request body based on model requirements
const requestBody: any = { prompt }
// Format duration based on model requirements
const formattedDuration = formatDuration(model, duration)
if (formattedDuration !== undefined) {
requestBody.duration = formattedDuration
}
// Models that support duration and aspect_ratio parameters
const supportsStandardParams = [
'kling-2.5-turbo-pro',
'kling-2.1-pro',
'minimax-hailuo-2.3-pro',
'minimax-hailuo-2.3-standard',
]
if (aspectRatio) {
requestBody.aspect_ratio = aspectRatio
}
// Models that only need prompt (minimal params)
const minimalParamModels = ['ltxv-0.9.8', 'wan-2.1', 'veo-3.1', 'sora-2']
if (resolution) {
requestBody.resolution = resolution
if (supportsStandardParams.includes(model)) {
// Kling and MiniMax models support duration and aspect_ratio
const formattedDuration = formatDuration(model, duration)
if (formattedDuration !== undefined) {
requestBody.duration = formattedDuration
}
if (aspectRatio) {
requestBody.aspect_ratio = aspectRatio
}
if (resolution) {
requestBody.resolution = resolution
}
}
// MiniMax models support prompt optimizer

View File

@@ -40,7 +40,7 @@ const CreateTemplateSchema = z.object({
about: z.string().optional(), // Markdown long description
})
.optional(),
creatorId: z.string().optional(), // Creator profile ID
creatorId: z.string().min(1, 'Creator profile is required'),
tags: z.array(z.string()).max(10, 'Maximum 10 tags allowed').optional().default([]),
})
@@ -204,50 +204,47 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
}
// Validate creator profile if provided
if (data.creatorId) {
// Verify the creator profile exists and user has access
const creatorProfile = await db
// Validate creator profile - required for all templates
const creatorProfile = await db
.select()
.from(templateCreators)
.where(eq(templateCreators.id, data.creatorId))
.limit(1)
if (creatorProfile.length === 0) {
logger.warn(`[${requestId}] Creator profile not found: ${data.creatorId}`)
return NextResponse.json({ error: 'Creator profile not found' }, { status: 404 })
}
const creator = creatorProfile[0]
// Verify user has permission to use this creator profile
if (creator.referenceType === 'user') {
if (creator.referenceId !== session.user.id) {
logger.warn(`[${requestId}] User cannot use creator profile: ${data.creatorId}`)
return NextResponse.json(
{ error: 'You do not have permission to use this creator profile' },
{ status: 403 }
)
}
} else if (creator.referenceType === 'organization') {
// Verify user is a member of the organization
const membership = await db
.select()
.from(templateCreators)
.where(eq(templateCreators.id, data.creatorId))
.from(member)
.where(
and(eq(member.userId, session.user.id), eq(member.organizationId, creator.referenceId))
)
.limit(1)
if (creatorProfile.length === 0) {
logger.warn(`[${requestId}] Creator profile not found: ${data.creatorId}`)
return NextResponse.json({ error: 'Creator profile not found' }, { status: 404 })
}
const creator = creatorProfile[0]
// Verify user has permission to use this creator profile
if (creator.referenceType === 'user') {
if (creator.referenceId !== session.user.id) {
logger.warn(`[${requestId}] User cannot use creator profile: ${data.creatorId}`)
return NextResponse.json(
{ error: 'You do not have permission to use this creator profile' },
{ status: 403 }
)
}
} else if (creator.referenceType === 'organization') {
// Verify user is a member of the organization
const membership = await db
.select()
.from(member)
.where(
and(eq(member.userId, session.user.id), eq(member.organizationId, creator.referenceId))
)
.limit(1)
if (membership.length === 0) {
logger.warn(
`[${requestId}] User not a member of organization for creator: ${data.creatorId}`
)
return NextResponse.json(
{ error: 'You must be a member of the organization to use its creator profile' },
{ status: 403 }
)
}
if (membership.length === 0) {
logger.warn(
`[${requestId}] User not a member of organization for creator: ${data.creatorId}`
)
return NextResponse.json(
{ error: 'You must be a member of the organization to use its creator profile' },
{ status: 403 }
)
}
}
@@ -307,7 +304,7 @@ export async function POST(request: NextRequest) {
workflowId: data.workflowId,
name: data.name,
details: data.details || null,
creatorId: data.creatorId || null,
creatorId: data.creatorId,
views: 0,
stars: 0,
status: 'pending' as const, // All new templates start as pending

View File

@@ -89,7 +89,10 @@ export function TemplateDeploy({
const isSubmitting = createMutation.isPending || updateMutation.isPending
const isFormValid =
formData.name.trim().length > 0 && formData.name.length <= 100 && formData.tagline.length <= 200
formData.name.trim().length > 0 &&
formData.name.length <= 100 &&
formData.tagline.length <= 200 &&
formData.creatorId.length > 0
const updateField = <K extends keyof TemplateFormData>(field: K, value: TemplateFormData[K]) => {
setFormData((prev) => ({ ...prev, [field]: value }))
@@ -201,7 +204,7 @@ export function TemplateDeploy({
tagline: formData.tagline.trim(),
about: formData.about.trim(),
},
creatorId: formData.creatorId || undefined,
creatorId: formData.creatorId,
tags: formData.tags,
}
@@ -285,7 +288,7 @@ export function TemplateDeploy({
<div>
<Label className='mb-[6.5px] block pl-[2px] font-medium text-[13px] text-[var(--text-primary)]'>
Name
Name <span className='text-[var(--text-error)]'>*</span>
</Label>
<Input
placeholder='Deep Research Agent'
@@ -323,29 +326,34 @@ export function TemplateDeploy({
<div>
<Label className='mb-[6.5px] block pl-[2px] font-medium text-[13px] text-[var(--text-primary)]'>
Creator
Creator <span className='text-[var(--text-error)]'>*</span>
</Label>
{creatorOptions.length === 0 && !loadingCreators ? (
<Button
type='button'
variant='primary'
onClick={() => {
try {
const event = new CustomEvent('open-settings', {
detail: { tab: 'template-profile' },
})
window.dispatchEvent(event)
logger.info('Opened Settings modal at template-profile section')
} catch (error) {
logger.error('Failed to open Settings modal for template profile', {
error,
})
}
}}
className='gap-[8px]'
>
<span>Create Template Profile</span>
</Button>
<div className='space-y-[8px]'>
<p className='text-[12px] text-[var(--text-tertiary)]'>
A creator profile is required to publish templates.
</p>
<Button
type='button'
variant='primary'
onClick={() => {
try {
const event = new CustomEvent('open-settings', {
detail: { tab: 'template-profile' },
})
window.dispatchEvent(event)
logger.info('Opened Settings modal at template-profile section')
} catch (error) {
logger.error('Failed to open Settings modal for template profile', {
error,
})
}
}}
className='gap-[8px]'
>
<span>Create Template Profile</span>
</Button>
</div>
) : (
<Combobox
options={creatorOptions.map((option) => ({

View File

@@ -298,7 +298,8 @@ export const GrafanaBlock: BlockConfig<GrafanaResponse> = {
id: 'annotationDashboardUid',
title: 'Dashboard UID',
type: 'short-input',
placeholder: 'Optional - attach to specific dashboard',
placeholder: 'Enter dashboard UID',
required: true,
condition: {
field: 'operation',
value: ['grafana_create_annotation', 'grafana_list_annotations'],

View File

@@ -169,6 +169,29 @@ export const VideoGeneratorBlock: BlockConfig<VideoBlockResponse> = {
required: false,
},
// Duration selection - Fal.ai (only for Kling and MiniMax models)
{
id: 'duration',
title: 'Duration (seconds)',
type: 'dropdown',
condition: {
field: 'model',
value: [
'kling-2.5-turbo-pro',
'kling-2.1-pro',
'minimax-hailuo-2.3-pro',
'minimax-hailuo-2.3-standard',
],
},
options: [
{ label: '5', id: '5' },
{ label: '8', id: '8' },
{ label: '10', id: '10' },
],
value: () => '5',
required: false,
},
// Aspect ratio selection - Veo (only 16:9 and 9:16)
{
id: 'aspectRatio',
@@ -213,6 +236,28 @@ export const VideoGeneratorBlock: BlockConfig<VideoBlockResponse> = {
required: false,
},
// Aspect ratio selection - Fal.ai (only for Kling and MiniMax models)
{
id: 'aspectRatio',
title: 'Aspect Ratio',
type: 'dropdown',
condition: {
field: 'model',
value: [
'kling-2.5-turbo-pro',
'kling-2.1-pro',
'minimax-hailuo-2.3-pro',
'minimax-hailuo-2.3-standard',
],
},
options: [
{ label: '16:9', id: '16:9' },
{ label: '9:16', id: '9:16' },
],
value: () => '16:9',
required: false,
},
// Note: MiniMax aspect ratio is fixed at 16:9 (not configurable)
// Note: Runway Gen-4 Turbo outputs at 720p natively (no resolution selector needed)

View File

@@ -276,26 +276,26 @@ export const ZepBlock: BlockConfig<ZepResponse> = {
metadata: { type: 'json', description: 'User metadata' },
},
outputs: {
// Thread operations
threadId: { type: 'string', description: 'Thread identifier' },
userId: { type: 'string', description: 'User identifier' },
uuid: { type: 'string', description: 'Internal UUID' },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Update timestamp' },
threads: { type: 'json', description: 'Array of threads' },
deleted: { type: 'boolean', description: 'Deletion status' },
// Message operations
messages: { type: 'json', description: 'Message data' },
messageIds: { type: 'json', description: 'Message identifiers' },
context: { type: 'string', description: 'User context string' },
facts: { type: 'json', description: 'Extracted facts' },
entities: { type: 'json', description: 'Extracted entities' },
summary: { type: 'string', description: 'Conversation summary' },
batchId: { type: 'string', description: 'Batch operation ID' },
messageIds: { type: 'json', description: 'Array of added message UUIDs' },
added: { type: 'boolean', description: 'Whether messages were added successfully' },
// Context operations
context: { type: 'string', description: 'User context string (summary or basic mode)' },
// User operations
userId: { type: 'string', description: 'User identifier' },
email: { type: 'string', description: 'User email' },
firstName: { type: 'string', description: 'User first name' },
lastName: { type: 'string', description: 'User last name' },
metadata: { type: 'json', description: 'User metadata' },
responseCount: { type: 'number', description: 'Number of items in response' },
totalCount: { type: 'number', description: 'Total number of items available' },
rowCount: { type: 'number', description: 'Number of rows in response' },
// Counts
totalCount: { type: 'number', description: 'Total number of items returned' },
},
}

View File

@@ -46,10 +46,9 @@ export const createAnnotationTool: ToolConfig<
},
dashboardUid: {
type: 'string',
required: false,
required: true,
visibility: 'user-or-llm',
description:
'UID of the dashboard to add the annotation to (optional for global annotations)',
description: 'UID of the dashboard to add the annotation to',
},
panelId: {
type: 'number',

View File

@@ -108,5 +108,45 @@ export const createFolderTool: ToolConfig<GrafanaCreateFolderParams, GrafanaCrea
type: 'string',
description: 'The URL path to the folder',
},
hasAcl: {
type: 'boolean',
description: 'Whether the folder has custom ACL permissions',
},
canSave: {
type: 'boolean',
description: 'Whether the current user can save the folder',
},
canEdit: {
type: 'boolean',
description: 'Whether the current user can edit the folder',
},
canAdmin: {
type: 'boolean',
description: 'Whether the current user has admin rights on the folder',
},
canDelete: {
type: 'boolean',
description: 'Whether the current user can delete the folder',
},
createdBy: {
type: 'string',
description: 'Username of who created the folder',
},
created: {
type: 'string',
description: 'Timestamp when the folder was created',
},
updatedBy: {
type: 'string',
description: 'Username of who last updated the folder',
},
updated: {
type: 'string',
description: 'Timestamp when the folder was last updated',
},
version: {
type: 'number',
description: 'Version number of the folder',
},
},
}

View File

@@ -46,9 +46,9 @@ export const listAnnotationsTool: ToolConfig<
},
dashboardUid: {
type: 'string',
required: false,
required: true,
visibility: 'user-or-llm',
description: 'Filter by dashboard UID',
description: 'Dashboard UID to query annotations from',
},
panelId: {
type: 'number',

View File

@@ -103,6 +103,19 @@ export const listFoldersTool: ToolConfig<GrafanaListFoldersParams, GrafanaListFo
uid: { type: 'string', description: 'Folder UID' },
title: { type: 'string', description: 'Folder title' },
url: { type: 'string', description: 'Folder URL path' },
hasAcl: { type: 'boolean', description: 'Whether the folder has custom ACL permissions' },
canSave: { type: 'boolean', description: 'Whether the current user can save the folder' },
canEdit: { type: 'boolean', description: 'Whether the current user can edit the folder' },
canAdmin: { type: 'boolean', description: 'Whether the current user has admin rights' },
canDelete: {
type: 'boolean',
description: 'Whether the current user can delete the folder',
},
createdBy: { type: 'string', description: 'Username of who created the folder' },
created: { type: 'string', description: 'Timestamp when the folder was created' },
updatedBy: { type: 'string', description: 'Username of who last updated the folder' },
updated: { type: 'string', description: 'Timestamp when the folder was last updated' },
version: { type: 'number', description: 'Version number of the folder' },
},
},
},

View File

@@ -65,46 +65,47 @@ export const zepAddMessagesTool: ToolConfig<any, ZepResponse> = {
transformResponse: async (response, params) => {
const threadId = params.threadId
const text = await response.text()
if (!response.ok) {
throw new Error(`Zep API error (${response.status}): ${text || response.statusText}`)
const error = await response.text()
throw new Error(`Zep API error (${response.status}): ${error || response.statusText}`)
}
const text = await response.text()
if (!text || text.trim() === '') {
return {
success: true,
output: {
threadId,
added: true,
messageIds: [],
},
}
}
const data = JSON.parse(text.replace(/^\uFEFF/, '').trim())
const data = JSON.parse(text)
return {
success: true,
output: {
threadId,
context: data.context,
added: true,
messageIds: data.message_uuids || [],
},
}
},
outputs: {
context: {
threadId: {
type: 'string',
description: 'Updated context after adding messages',
description: 'The thread ID',
},
added: {
type: 'boolean',
description: 'Whether messages were added successfully',
},
messageIds: {
type: 'array',
description: 'Array of added message UUIDs',
},
threadId: {
type: 'string',
description: 'The thread ID',
},
},
}

View File

@@ -80,12 +80,12 @@ export const zepAddUserTool: ToolConfig<any, ZepResponse> = {
},
transformResponse: async (response) => {
const text = await response.text()
if (!response.ok) {
throw new Error(`Zep API error (${response.status}): ${text || response.statusText}`)
const error = await response.text()
throw new Error(`Zep API error (${response.status}): ${error || response.statusText}`)
}
const text = await response.text()
if (!text || text.trim() === '') {
return {
success: true,
@@ -93,7 +93,7 @@ export const zepAddUserTool: ToolConfig<any, ZepResponse> = {
}
}
const data = JSON.parse(text.replace(/^\uFEFF/, '').trim())
const data = JSON.parse(text)
return {
success: true,

View File

@@ -43,12 +43,12 @@ export const zepCreateThreadTool: ToolConfig<any, ZepResponse> = {
},
transformResponse: async (response) => {
const text = await response.text()
if (!response.ok) {
throw new Error(`Zep API error (${response.status}): ${text || response.statusText}`)
const error = await response.text()
throw new Error(`Zep API error (${response.status}): ${error || response.statusText}`)
}
const text = await response.text()
if (!text || text.trim() === '') {
return {
success: true,
@@ -56,7 +56,7 @@ export const zepCreateThreadTool: ToolConfig<any, ZepResponse> = {
}
}
const data = JSON.parse(text.replace(/^\uFEFF/, '').trim())
const data = JSON.parse(text)
return {
success: true,

View File

@@ -53,21 +53,17 @@ export const zepGetContextTool: ToolConfig<any, ZepResponse> = {
},
transformResponse: async (response) => {
const text = await response.text()
if (!response.ok) {
throw new Error(`Zep API error (${response.status}): ${text || response.statusText}`)
const error = await response.text()
throw new Error(`Zep API error (${response.status}): ${error || response.statusText}`)
}
const data = JSON.parse(text.replace(/^\uFEFF/, '').trim())
const data = await response.json()
return {
success: true,
output: {
context: data.context || data,
facts: data.facts || [],
entities: data.entities || [],
summary: data.summary,
context: data.context,
},
}
},
@@ -75,19 +71,7 @@ export const zepGetContextTool: ToolConfig<any, ZepResponse> = {
outputs: {
context: {
type: 'string',
description: 'The context string (summary or basic)',
},
facts: {
type: 'array',
description: 'Extracted facts',
},
entities: {
type: 'array',
description: 'Extracted entities',
},
summary: {
type: 'string',
description: 'Conversation summary',
description: 'The context string (summary or basic mode)',
},
},
}

View File

@@ -59,13 +59,12 @@ export const zepGetMessagesTool: ToolConfig<any, ZepResponse> = {
},
transformResponse: async (response) => {
const text = await response.text()
if (!response.ok) {
throw new Error(`Zep API error (${response.status}): ${text || response.statusText}`)
const error = await response.text()
throw new Error(`Zep API error (${response.status}): ${error || response.statusText}`)
}
const data = JSON.parse(text.replace(/^\uFEFF/, '').trim())
const data = await response.json()
return {
success: true,

View File

@@ -61,13 +61,12 @@ export const zepGetThreadsTool: ToolConfig<any, ZepResponse> = {
},
transformResponse: async (response) => {
const text = await response.text()
if (!response.ok) {
throw new Error(`Zep API error (${response.status}): ${text || response.statusText}`)
const error = await response.text()
throw new Error(`Zep API error (${response.status}): ${error || response.statusText}`)
}
const data = JSON.parse(text.replace(/^\uFEFF/, '').trim())
const data = await response.json()
return {
success: true,

View File

@@ -33,13 +33,12 @@ export const zepGetUserTool: ToolConfig<any, ZepResponse> = {
},
transformResponse: async (response) => {
const text = await response.text()
if (!response.ok) {
throw new Error(`Zep API error (${response.status}): ${text || response.statusText}`)
const error = await response.text()
throw new Error(`Zep API error (${response.status}): ${error || response.statusText}`)
}
const data = JSON.parse(text.replace(/^\uFEFF/, '').trim())
const data = await response.json()
return {
success: true,

View File

@@ -43,19 +43,19 @@ export const zepGetUserThreadsTool: ToolConfig<any, ZepResponse> = {
},
transformResponse: async (response) => {
const text = await response.text()
if (!response.ok) {
throw new Error(`Zep API error (${response.status}): ${text || response.statusText}`)
const error = await response.text()
throw new Error(`Zep API error (${response.status}): ${error || response.statusText}`)
}
const data = JSON.parse(text.replace(/^\uFEFF/, '').trim())
const data = await response.json()
const threads = data.threads || data || []
return {
success: true,
output: {
threads: data.threads || data || [],
userId: data.user_id,
threads,
totalCount: threads.length,
},
}
},
@@ -65,9 +65,9 @@ export const zepGetUserThreadsTool: ToolConfig<any, ZepResponse> = {
type: 'array',
description: 'Array of thread objects for this user',
},
userId: {
type: 'string',
description: 'The user ID',
totalCount: {
type: 'number',
description: 'Total number of threads returned',
},
},
}

View File

@@ -5,7 +5,6 @@ export interface ZepResponse extends ToolResponse {
output: {
// Thread operations
threadId?: string
userId?: string
uuid?: string
createdAt?: string
updatedAt?: string
@@ -17,26 +16,18 @@ export interface ZepResponse extends ToolResponse {
messages?: any[]
messageIds?: string[]
added?: boolean
batchId?: string
// Context operations
context?: string
facts?: any[]
entities?: any[]
summary?: string
// User operations
userId?: string
email?: string
firstName?: string
lastName?: string
metadata?: any
// Pagination
responseCount?: number
// Counts
totalCount?: number
rowCount?: number
// Search results (if needed in future)
searchResults?: any[]
}
}