mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(constants): modify consts to match new api routes, run format
This commit is contained in:
@@ -10,7 +10,7 @@ const logger = createLogger('MarketplaceViewAPI')
|
||||
/**
|
||||
* POST handler for incrementing the view count when a workflow card is clicked
|
||||
* This endpoint is called from the WorkflowCard component's onClick handler
|
||||
*
|
||||
*
|
||||
* The ID parameter is the marketplace entry ID, not the workflow ID
|
||||
*/
|
||||
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
@@ -37,8 +37,8 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
||||
// Increment the view count for this workflow
|
||||
await db
|
||||
.update(schema.marketplace)
|
||||
.set({
|
||||
views: sql`${schema.marketplace.views} + 1`
|
||||
.set({
|
||||
views: sql`${schema.marketplace.views} + 1`,
|
||||
})
|
||||
.where(eq(schema.marketplace.id, id))
|
||||
|
||||
@@ -54,4 +54,4 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
|
||||
)
|
||||
return createErrorResponse('Failed to track view', 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { db } from '@/db'
|
||||
import { marketplace, user, workflow } from '@/db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
// Create a logger for this module
|
||||
const logger = createLogger('MarketplacePublishAPI')
|
||||
@@ -40,7 +40,8 @@ export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Parse request body
|
||||
const body = await request.json()
|
||||
const { workflowId, name, description, category, authorName, workflowState } = PublishRequestSchema.parse(body)
|
||||
const { workflowId, name, description, category, authorName, workflowState } =
|
||||
PublishRequestSchema.parse(body)
|
||||
|
||||
// Check if the workflow belongs to the user
|
||||
const userWorkflow = await db
|
||||
@@ -50,7 +51,9 @@ export async function POST(request: NextRequest) {
|
||||
.limit(1)
|
||||
|
||||
if (!userWorkflow.length || userWorkflow[0].id !== workflowId) {
|
||||
logger.warn(`[${requestId}] User ${userId} attempted to publish workflow they don't own: ${workflowId}`)
|
||||
logger.warn(
|
||||
`[${requestId}] User ${userId} attempted to publish workflow they don't own: ${workflowId}`
|
||||
)
|
||||
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
@@ -148,4 +151,4 @@ export async function POST(request: NextRequest) {
|
||||
logger.error(`[${requestId}] Marketplace publish error`, error)
|
||||
return NextResponse.json({ error: error.message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { desc, eq, sql } from 'drizzle-orm'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
|
||||
import { CATEGORIES } from '@/app/w/marketplace/constants/categories'
|
||||
import { db } from '@/db'
|
||||
import * as schema from '@/db/schema'
|
||||
import { CATEGORIES } from '@/app/w/marketplace/constants/categories'
|
||||
|
||||
const logger = createLogger('MarketplaceWorkflowsAPI')
|
||||
|
||||
@@ -13,14 +13,14 @@ export const revalidate = 60
|
||||
|
||||
/**
|
||||
* Consolidated API endpoint for marketplace workflows
|
||||
*
|
||||
*
|
||||
* Supports:
|
||||
* - Getting featured/popular/recent workflows
|
||||
* - Getting workflows by category
|
||||
* - Getting workflow state
|
||||
* - Getting workflow details
|
||||
* - Incrementing view counts
|
||||
*
|
||||
*
|
||||
* Query parameters:
|
||||
* - section: 'popular', 'recent', 'byCategory', or specific category name
|
||||
* - limit: Maximum number of items to return per section (default: 6)
|
||||
@@ -30,7 +30,7 @@ export const revalidate = 60
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
|
||||
|
||||
try {
|
||||
// Parse query parameters
|
||||
const url = new URL(request.url)
|
||||
@@ -41,11 +41,11 @@ export async function GET(request: NextRequest) {
|
||||
const includeState = url.searchParams.get('includeState') === 'true'
|
||||
const workflowId = url.searchParams.get('workflowId')
|
||||
const marketplaceId = url.searchParams.get('marketplaceId')
|
||||
|
||||
|
||||
// Handle single workflow request first (by workflow ID)
|
||||
if (workflowId) {
|
||||
let marketplaceEntry;
|
||||
|
||||
let marketplaceEntry
|
||||
|
||||
if (includeState) {
|
||||
// Query with state included
|
||||
marketplaceEntry = await db
|
||||
@@ -66,7 +66,7 @@ export async function GET(request: NextRequest) {
|
||||
.from(schema.marketplace)
|
||||
.where(eq(schema.marketplace.workflowId, workflowId))
|
||||
.limit(1)
|
||||
.then((rows) => rows[0]);
|
||||
.then((rows) => rows[0])
|
||||
} else {
|
||||
// Query without state
|
||||
marketplaceEntry = await db
|
||||
@@ -86,7 +86,7 @@ export async function GET(request: NextRequest) {
|
||||
.from(schema.marketplace)
|
||||
.where(eq(schema.marketplace.workflowId, workflowId))
|
||||
.limit(1)
|
||||
.then((rows) => rows[0]);
|
||||
.then((rows) => rows[0])
|
||||
}
|
||||
|
||||
if (!marketplaceEntry) {
|
||||
@@ -95,22 +95,23 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
// Transform response if state was requested
|
||||
const responseData = includeState && 'state' in marketplaceEntry
|
||||
? {
|
||||
...marketplaceEntry,
|
||||
workflowState: marketplaceEntry.state,
|
||||
state: undefined,
|
||||
}
|
||||
: marketplaceEntry;
|
||||
const responseData =
|
||||
includeState && 'state' in marketplaceEntry
|
||||
? {
|
||||
...marketplaceEntry,
|
||||
workflowState: marketplaceEntry.state,
|
||||
state: undefined,
|
||||
}
|
||||
: marketplaceEntry
|
||||
|
||||
logger.info(`[${requestId}] Retrieved marketplace data for workflow: ${workflowId}`)
|
||||
return createSuccessResponse(responseData)
|
||||
}
|
||||
|
||||
|
||||
// Handle single marketplace entry request (by marketplace ID)
|
||||
if (marketplaceId) {
|
||||
let marketplaceEntry;
|
||||
|
||||
let marketplaceEntry
|
||||
|
||||
if (includeState) {
|
||||
// Query with state included
|
||||
marketplaceEntry = await db
|
||||
@@ -131,7 +132,7 @@ export async function GET(request: NextRequest) {
|
||||
.from(schema.marketplace)
|
||||
.where(eq(schema.marketplace.id, marketplaceId))
|
||||
.limit(1)
|
||||
.then((rows) => rows[0]);
|
||||
.then((rows) => rows[0])
|
||||
} else {
|
||||
// Query without state
|
||||
marketplaceEntry = await db
|
||||
@@ -151,7 +152,7 @@ export async function GET(request: NextRequest) {
|
||||
.from(schema.marketplace)
|
||||
.where(eq(schema.marketplace.id, marketplaceId))
|
||||
.limit(1)
|
||||
.then((rows) => rows[0]);
|
||||
.then((rows) => rows[0])
|
||||
}
|
||||
|
||||
if (!marketplaceEntry) {
|
||||
@@ -160,18 +161,19 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
// Transform response if state was requested
|
||||
const responseData = includeState && 'state' in marketplaceEntry
|
||||
? {
|
||||
...marketplaceEntry,
|
||||
workflowState: marketplaceEntry.state,
|
||||
state: undefined,
|
||||
}
|
||||
: marketplaceEntry;
|
||||
const responseData =
|
||||
includeState && 'state' in marketplaceEntry
|
||||
? {
|
||||
...marketplaceEntry,
|
||||
workflowState: marketplaceEntry.state,
|
||||
state: undefined,
|
||||
}
|
||||
: marketplaceEntry
|
||||
|
||||
logger.info(`[${requestId}] Retrieved marketplace entry: ${marketplaceId}`)
|
||||
return createSuccessResponse(responseData)
|
||||
}
|
||||
|
||||
|
||||
// Handle featured/collection requests
|
||||
const result: {
|
||||
popular: any[]
|
||||
@@ -180,7 +182,7 @@ export async function GET(request: NextRequest) {
|
||||
} = {
|
||||
popular: [],
|
||||
recent: [],
|
||||
byCategory: {}
|
||||
byCategory: {},
|
||||
}
|
||||
|
||||
// Define common fields to select
|
||||
@@ -198,13 +200,13 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
// Add state if requested
|
||||
const selectFields = includeState
|
||||
const selectFields = includeState
|
||||
? { ...baseFields, state: schema.marketplace.state }
|
||||
: baseFields;
|
||||
: baseFields
|
||||
|
||||
// Determine which sections to fetch
|
||||
const sections = sectionParam ? sectionParam.split(',') : ['popular', 'recent', 'byCategory']
|
||||
|
||||
|
||||
// Get popular items if requested
|
||||
if (sections.includes('popular')) {
|
||||
result.popular = await db
|
||||
@@ -213,7 +215,7 @@ export async function GET(request: NextRequest) {
|
||||
.orderBy(desc(schema.marketplace.stars), desc(schema.marketplace.views))
|
||||
.limit(limit)
|
||||
}
|
||||
|
||||
|
||||
// Get recent items if requested
|
||||
if (sections.includes('recent')) {
|
||||
result.recent = await db
|
||||
@@ -222,33 +224,37 @@ export async function GET(request: NextRequest) {
|
||||
.orderBy(desc(schema.marketplace.createdAt))
|
||||
.limit(limit)
|
||||
}
|
||||
|
||||
|
||||
// Get categories if requested
|
||||
if (sections.includes('byCategory') || categoryParam || sections.some(s => CATEGORIES.some(c => c.value === s))) {
|
||||
if (
|
||||
sections.includes('byCategory') ||
|
||||
categoryParam ||
|
||||
sections.some((s) => CATEGORIES.some((c) => c.value === s))
|
||||
) {
|
||||
// Identify all requested categories
|
||||
const requestedCategories = new Set<string>();
|
||||
|
||||
const requestedCategories = new Set<string>()
|
||||
|
||||
// Add explicitly requested category
|
||||
if (categoryParam) {
|
||||
requestedCategories.add(categoryParam);
|
||||
requestedCategories.add(categoryParam)
|
||||
}
|
||||
|
||||
|
||||
// Add categories from sections parameter
|
||||
sections.forEach(section => {
|
||||
if (CATEGORIES.some(c => c.value === section)) {
|
||||
requestedCategories.add(section);
|
||||
sections.forEach((section) => {
|
||||
if (CATEGORIES.some((c) => c.value === section)) {
|
||||
requestedCategories.add(section)
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
// Include byCategory section contents if requested
|
||||
if (sections.includes('byCategory')) {
|
||||
CATEGORIES.forEach(c => requestedCategories.add(c.value));
|
||||
CATEGORIES.forEach((c) => requestedCategories.add(c.value))
|
||||
}
|
||||
|
||||
|
||||
// Log what we're fetching
|
||||
const categoriesToFetch = Array.from(requestedCategories);
|
||||
logger.info(`[${requestId}] Fetching specific categories: ${categoriesToFetch.join(', ')}`);
|
||||
|
||||
const categoriesToFetch = Array.from(requestedCategories)
|
||||
logger.info(`[${requestId}] Fetching specific categories: ${categoriesToFetch.join(', ')}`)
|
||||
|
||||
// Process each requested category
|
||||
await Promise.all(
|
||||
categoriesToFetch.map(async (categoryValue) => {
|
||||
@@ -257,40 +263,44 @@ export async function GET(request: NextRequest) {
|
||||
.from(schema.marketplace)
|
||||
.where(eq(schema.marketplace.category, categoryValue))
|
||||
.orderBy(desc(schema.marketplace.stars), desc(schema.marketplace.views))
|
||||
.limit(limit);
|
||||
|
||||
.limit(limit)
|
||||
|
||||
// Always add the category to the result, even if empty
|
||||
result.byCategory[categoryValue] = categoryItems;
|
||||
logger.info(`[${requestId}] Category ${categoryValue}: found ${categoryItems.length} items`);
|
||||
result.byCategory[categoryValue] = categoryItems
|
||||
logger.info(
|
||||
`[${requestId}] Category ${categoryValue}: found ${categoryItems.length} items`
|
||||
)
|
||||
})
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// Transform the data if state was included to match the expected format
|
||||
if (includeState) {
|
||||
const transformSection = (section: any[]) => {
|
||||
return section.map(item =>
|
||||
'state' in item ? {
|
||||
...item,
|
||||
workflowState: item.state,
|
||||
state: undefined
|
||||
} : item
|
||||
);
|
||||
};
|
||||
return section.map((item) =>
|
||||
'state' in item
|
||||
? {
|
||||
...item,
|
||||
workflowState: item.state,
|
||||
state: undefined,
|
||||
}
|
||||
: item
|
||||
)
|
||||
}
|
||||
|
||||
if (result.popular.length > 0) {
|
||||
result.popular = transformSection(result.popular);
|
||||
result.popular = transformSection(result.popular)
|
||||
}
|
||||
|
||||
|
||||
if (result.recent.length > 0) {
|
||||
result.recent = transformSection(result.recent);
|
||||
result.recent = transformSection(result.recent)
|
||||
}
|
||||
|
||||
Object.keys(result.byCategory).forEach(category => {
|
||||
|
||||
Object.keys(result.byCategory).forEach((category) => {
|
||||
if (result.byCategory[category].length > 0) {
|
||||
result.byCategory[category] = transformSection(result.byCategory[category]);
|
||||
result.byCategory[category] = transformSection(result.byCategory[category])
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Fetched marketplace items${includeState ? ' with state' : ''}`)
|
||||
@@ -303,17 +313,17 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
/**
|
||||
* POST handler for incrementing view counts
|
||||
*
|
||||
*
|
||||
* Request body:
|
||||
* - id: Marketplace entry ID to increment view count for
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
|
||||
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { id } = body
|
||||
|
||||
|
||||
if (!id) {
|
||||
return createErrorResponse('Marketplace ID is required', 400)
|
||||
}
|
||||
@@ -336,8 +346,8 @@ export async function POST(request: NextRequest) {
|
||||
// Increment the view count
|
||||
await db
|
||||
.update(schema.marketplace)
|
||||
.set({
|
||||
views: sql`${schema.marketplace.views} + 1`
|
||||
.set({
|
||||
views: sql`${schema.marketplace.views} + 1`,
|
||||
})
|
||||
.where(eq(schema.marketplace.id, id))
|
||||
|
||||
@@ -350,4 +360,4 @@ export async function POST(request: NextRequest) {
|
||||
logger.error(`[${requestId}] Error incrementing view count`, error)
|
||||
return createErrorResponse(`Failed to track view: ${error.message}`, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { apiKey } from '@/db/schema'
|
||||
import { db } from '@/db'
|
||||
import { apiKey } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('ApiKeyRoute')
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
case 'slack': {
|
||||
const signingSecret = providerConfig.signingSecret
|
||||
|
||||
|
||||
if (!signingSecret) {
|
||||
logger.warn(`[${requestId}] Slack webhook missing signing secret: ${webhookId}`)
|
||||
return NextResponse.json(
|
||||
@@ -234,7 +234,7 @@ export async function GET(request: NextRequest) {
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
logger.info(`[${requestId}] Slack webhook test successful: ${webhookId}`)
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
@@ -254,23 +254,23 @@ export async function GET(request: NextRequest) {
|
||||
curlCommand: [
|
||||
`curl -X POST "${webhookUrl}"`,
|
||||
`-H "Content-Type: application/json"`,
|
||||
`-H "X-Slack-Request-Timestamp: $(date +%s)"`,
|
||||
`-H "X-Slack-Request-Timestamp: $(date +%s)"`,
|
||||
`-H "X-Slack-Signature: v0=$(date +%s)"`,
|
||||
`-d '{"type":"event_callback","event":{"type":"message","channel":"C0123456789","user":"U0123456789","text":"Hello from Slack!","ts":"1234567890.123456"},"team_id":"T0123456789"}'`
|
||||
`-d '{"type":"event_callback","event":{"type":"message","channel":"C0123456789","user":"U0123456789","text":"Hello from Slack!","ts":"1234567890.123456"},"team_id":"T0123456789"}'`,
|
||||
].join(' \\\n'),
|
||||
samplePayload: {
|
||||
"type": "event_callback",
|
||||
"token": "XXYYZZ",
|
||||
"team_id": "T123ABC",
|
||||
"event": {
|
||||
"type": "message",
|
||||
"user": "U123ABC",
|
||||
"text": "Hello from Slack!",
|
||||
"ts": "1234567890.1234"
|
||||
type: 'event_callback',
|
||||
token: 'XXYYZZ',
|
||||
team_id: 'T123ABC',
|
||||
event: {
|
||||
type: 'message',
|
||||
user: 'U123ABC',
|
||||
text: 'Hello from Slack!',
|
||||
ts: '1234567890.1234',
|
||||
},
|
||||
"event_id": "Ev123ABC"
|
||||
}
|
||||
}
|
||||
event_id: 'Ev123ABC',
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { db } from '@/db'
|
||||
import { environment, userStats, webhook, workflow } from '@/db/schema'
|
||||
import { Executor } from '@/executor'
|
||||
import { Serializer } from '@/serializer'
|
||||
import {validateSlackSignature} from '../../utils'
|
||||
import { validateSlackSignature } from '../../utils'
|
||||
|
||||
const logger = createLogger('WebhookTriggerAPI')
|
||||
|
||||
@@ -118,12 +118,12 @@ export async function POST(
|
||||
|
||||
try {
|
||||
const path = (await params).path
|
||||
|
||||
|
||||
// Clone the request to get both the raw body for Slack signature verification
|
||||
// and the parsed JSON body for processing
|
||||
const requestClone = request.clone()
|
||||
rawBody = await requestClone.text()
|
||||
|
||||
|
||||
// Parse the request body
|
||||
const body = JSON.parse(rawBody || '{}')
|
||||
logger.info(`[${requestId}] Webhook POST request received for path: ${path}`)
|
||||
@@ -168,16 +168,16 @@ export async function POST(
|
||||
// Validate Slack signature if this is a Slack webhook
|
||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||
const signingSecret = providerConfig.signingSecret
|
||||
|
||||
|
||||
if (signingSecret) {
|
||||
const slackSignature = request.headers.get('x-slack-signature')
|
||||
const slackTimestamp = request.headers.get('x-slack-request-timestamp')
|
||||
|
||||
|
||||
if (!slackSignature || !slackTimestamp || !rawBody) {
|
||||
logger.warn(`[${requestId}] Missing Slack signature headers`, {
|
||||
hasSignature: !!slackSignature,
|
||||
hasTimestamp: !!slackTimestamp,
|
||||
hasBody: !!rawBody
|
||||
hasBody: !!rawBody,
|
||||
})
|
||||
return NextResponse.json({ error: 'Invalid Slack request' }, { status: 400 })
|
||||
}
|
||||
@@ -189,14 +189,14 @@ export async function POST(
|
||||
slackTimestamp,
|
||||
rawBody
|
||||
)
|
||||
|
||||
|
||||
if (!isValid) {
|
||||
logger.warn(`[${requestId}] Invalid Slack signature`)
|
||||
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
|
||||
}
|
||||
|
||||
|
||||
logger.info(`[${requestId}] Slack signature validated successfully`)
|
||||
|
||||
|
||||
// Handle Slack URL verification challenge during POST
|
||||
if (body.type === 'url_verification' && body.challenge) {
|
||||
logger.info(`[${requestId}] Responding to Slack URL verification challenge`)
|
||||
@@ -216,13 +216,19 @@ export async function POST(
|
||||
if (messageId) {
|
||||
await markMessageAsProcessed(messageId)
|
||||
}
|
||||
|
||||
|
||||
// Mark this request as processed to prevent duplicates
|
||||
await markMessageAsProcessed(requestHash, 60 * 60 * 24)
|
||||
|
||||
// Process the webhook for Slack
|
||||
return await processWebhook(foundWebhook, foundWorkflow, body, request, executionId, requestId)
|
||||
|
||||
// Process the webhook for Slack
|
||||
return await processWebhook(
|
||||
foundWebhook,
|
||||
foundWorkflow,
|
||||
body,
|
||||
request,
|
||||
executionId,
|
||||
requestId
|
||||
)
|
||||
} else if (foundWebhook.provider === 'whatsapp') {
|
||||
// Extract WhatsApp specific data
|
||||
const data = body?.entry?.[0]?.changes?.[0]?.value
|
||||
|
||||
@@ -8,63 +8,59 @@
|
||||
*/
|
||||
|
||||
export async function validateSlackSignature(
|
||||
signingSecret: string,
|
||||
signature: string,
|
||||
timestamp: string,
|
||||
body: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
// Basic validation first
|
||||
if (!signingSecret || !signature || !timestamp || !body) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the timestamp is too old (> 5 minutes)
|
||||
const currentTime = Math.floor(Date.now() / 1000)
|
||||
if (Math.abs(currentTime - parseInt(timestamp)) > 300) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compute the signature
|
||||
const encoder = new TextEncoder()
|
||||
const baseString = `v0:${timestamp}:${body}`
|
||||
|
||||
// Create the HMAC with the signing secret
|
||||
const key = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
encoder.encode(signingSecret),
|
||||
{ name: 'HMAC', hash: 'SHA-256' },
|
||||
false,
|
||||
['sign']
|
||||
)
|
||||
|
||||
const signatureBytes = await crypto.subtle.sign(
|
||||
'HMAC',
|
||||
key,
|
||||
encoder.encode(baseString)
|
||||
)
|
||||
|
||||
// Convert the signature to hex
|
||||
const signatureHex = Array.from(new Uint8Array(signatureBytes))
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
|
||||
// Prepare the expected signature format
|
||||
const computedSignature = `v0=${signatureHex}`
|
||||
|
||||
// Constant-time comparison to prevent timing attacks
|
||||
if (computedSignature.length !== signature.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
let result = 0
|
||||
for (let i = 0; i < computedSignature.length; i++) {
|
||||
result |= computedSignature.charCodeAt(i) ^ signature.charCodeAt(i)
|
||||
}
|
||||
|
||||
return result === 0
|
||||
} catch (error) {
|
||||
console.error('Error validating Slack signature:', error)
|
||||
signingSecret: string,
|
||||
signature: string,
|
||||
timestamp: string,
|
||||
body: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
// Basic validation first
|
||||
if (!signingSecret || !signature || !timestamp || !body) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the timestamp is too old (> 5 minutes)
|
||||
const currentTime = Math.floor(Date.now() / 1000)
|
||||
if (Math.abs(currentTime - parseInt(timestamp)) > 300) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compute the signature
|
||||
const encoder = new TextEncoder()
|
||||
const baseString = `v0:${timestamp}:${body}`
|
||||
|
||||
// Create the HMAC with the signing secret
|
||||
const key = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
encoder.encode(signingSecret),
|
||||
{ name: 'HMAC', hash: 'SHA-256' },
|
||||
false,
|
||||
['sign']
|
||||
)
|
||||
|
||||
const signatureBytes = await crypto.subtle.sign('HMAC', key, encoder.encode(baseString))
|
||||
|
||||
// Convert the signature to hex
|
||||
const signatureHex = Array.from(new Uint8Array(signatureBytes))
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
|
||||
// Prepare the expected signature format
|
||||
const computedSignature = `v0=${signatureHex}`
|
||||
|
||||
// Constant-time comparison to prevent timing attacks
|
||||
if (computedSignature.length !== signature.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
let result = 0
|
||||
for (let i = 0; i < computedSignature.length; i++) {
|
||||
result |= computedSignature.charCodeAt(i) ^ signature.charCodeAt(i)
|
||||
}
|
||||
|
||||
return result === 0
|
||||
} catch (error) {
|
||||
console.error('Error validating Slack signature:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||
state: schema.marketplace.state,
|
||||
name: schema.marketplace.name,
|
||||
description: schema.marketplace.description,
|
||||
authorId: schema.marketplace.authorId,
|
||||
authorId: schema.marketplace.authorId,
|
||||
authorName: schema.marketplace.authorName,
|
||||
})
|
||||
.from(schema.marketplace)
|
||||
@@ -65,4 +65,4 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||
logger.error(`[${requestId}] Error getting public workflow: ${(await params).id}`, error)
|
||||
return createErrorResponse('Failed to get public workflow', 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -33,7 +33,7 @@
|
||||
--ring: 222.2 84% 4.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
|
||||
|
||||
/* Scrollbar Custom Properties */
|
||||
--scrollbar-track: 0 0% 85%;
|
||||
--scrollbar-thumb: 0 0% 65%;
|
||||
@@ -69,7 +69,7 @@
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
|
||||
|
||||
/* Dark Mode Scrollbar Custom Properties */
|
||||
--scrollbar-track: 217.2 32.6% 17.5%;
|
||||
--scrollbar-thumb: 217.2 32.6% 30%;
|
||||
@@ -87,27 +87,27 @@
|
||||
@apply bg-background text-foreground;
|
||||
overscroll-behavior-x: none;
|
||||
}
|
||||
|
||||
|
||||
/* Global Scrollbar Styling */
|
||||
::-webkit-scrollbar {
|
||||
width: var(--scrollbar-size);
|
||||
height: var(--scrollbar-size);
|
||||
}
|
||||
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: hsl(var(--scrollbar-track));
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: hsl(var(--scrollbar-thumb));
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: hsl(var(--scrollbar-thumb-hover));
|
||||
}
|
||||
|
||||
|
||||
/* For Firefox */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
@@ -138,13 +138,13 @@
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
|
||||
/* Custom Scrollbar Utility Classes */
|
||||
.scrollbar-none {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
|
||||
.scrollbar-none::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
+18
-9
@@ -13,18 +13,17 @@ interface SlackConfigProps {
|
||||
test?: any
|
||||
} | null
|
||||
copied: string | null
|
||||
copyToClipboard: (text: string, type: string) => void
|
||||
copyToClipboard: (text: string, type: string) => void
|
||||
testWebhook: () => Promise<void>
|
||||
|
||||
}
|
||||
|
||||
export function SlackConfig({
|
||||
signingSecret,
|
||||
setSigningSecret,
|
||||
isLoadingToken,
|
||||
testResult,
|
||||
copied,
|
||||
copyToClipboard,
|
||||
signingSecret,
|
||||
setSigningSecret,
|
||||
isLoadingToken,
|
||||
testResult,
|
||||
copied,
|
||||
copyToClipboard,
|
||||
}: SlackConfigProps) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -57,7 +56,17 @@ export function SlackConfig({
|
||||
<div className="bg-gray-50 dark:bg-gray-800 p-3 rounded-md mt-3 border border-gray-200 dark:border-gray-700">
|
||||
<h4 className="font-medium">Setup Instructions</h4>
|
||||
<ol className="list-decimal list-inside space-y-1 text-sm">
|
||||
<li>Go to your <a href="https://api.slack.com/apps" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">Slack Apps page</a></li>
|
||||
<li>
|
||||
Go to your{' '}
|
||||
<a
|
||||
href="https://api.slack.com/apps"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-500 hover:underline"
|
||||
>
|
||||
Slack Apps page
|
||||
</a>
|
||||
</li>
|
||||
<li>Create a new app or select an existing one</li>
|
||||
<li>Navigate to "Event Subscriptions" in the left sidebar</li>
|
||||
<li>Enable events and add the Webhook URL above as the Request URL</li>
|
||||
|
||||
+5
-1
@@ -28,7 +28,11 @@ export function WebhookDialogFooter({
|
||||
onClose,
|
||||
}: WebhookDialogFooterProps) {
|
||||
const showTestButton =
|
||||
webhookId && (webhookProvider === 'whatsapp' || webhookProvider === 'generic' || webhookProvider === "slack") && onTest
|
||||
webhookId &&
|
||||
(webhookProvider === 'whatsapp' ||
|
||||
webhookProvider === 'generic' ||
|
||||
webhookProvider === 'slack') &&
|
||||
onTest
|
||||
|
||||
return (
|
||||
<DialogFooter className="flex justify-between sticky bottom-0 py-3 bg-background border-t z-10 mt-auto w-full">
|
||||
|
||||
+14
-15
@@ -5,6 +5,7 @@ import { ProviderConfig, WEBHOOK_PROVIDERS } from '../webhook-config'
|
||||
import { DiscordConfig } from './providers/discord-config'
|
||||
import { GenericConfig } from './providers/generic-config'
|
||||
import { GithubConfig } from './providers/github-config'
|
||||
import { SlackConfig } from './providers/slack-config'
|
||||
import { StripeConfig } from './providers/stripe-config'
|
||||
import { WhatsAppConfig } from './providers/whatsapp-config'
|
||||
import { DeleteConfirmDialog } from './ui/confirmation'
|
||||
@@ -12,7 +13,6 @@ import { UnsavedChangesDialog } from './ui/confirmation'
|
||||
import { WebhookDialogFooter } from './ui/webhook-footer'
|
||||
import { WebhookDialogHeader } from './ui/webhook-header'
|
||||
import { WebhookUrlField } from './ui/webhook-url'
|
||||
import { SlackConfig } from './providers/slack-config'
|
||||
|
||||
const logger = createLogger('WebhookModal')
|
||||
|
||||
@@ -213,8 +213,7 @@ export function WebhookModal({
|
||||
secretHeaderName !== originalValues.secretHeaderName ||
|
||||
requireAuth !== originalValues.requireAuth ||
|
||||
allowedIps !== originalValues.allowedIps)) ||
|
||||
(webhookProvider === 'slack' &&
|
||||
slackSigningSecret !== originalValues.slackSigningSecret)
|
||||
(webhookProvider === 'slack' && slackSigningSecret !== originalValues.slackSigningSecret)
|
||||
|
||||
setHasUnsavedChanges(hasChanges)
|
||||
}, [
|
||||
@@ -448,18 +447,18 @@ export function WebhookModal({
|
||||
copyToClipboard={copyToClipboard}
|
||||
/>
|
||||
)
|
||||
case 'slack':
|
||||
return (
|
||||
<SlackConfig
|
||||
signingSecret={slackSigningSecret}
|
||||
setSigningSecret={setSlackSigningSecret}
|
||||
isLoadingToken={isLoadingToken}
|
||||
testResult={testResult}
|
||||
copied={copied}
|
||||
copyToClipboard={copyToClipboard}
|
||||
testWebhook={testWebhook}
|
||||
/>
|
||||
)
|
||||
case 'slack':
|
||||
return (
|
||||
<SlackConfig
|
||||
signingSecret={slackSigningSecret}
|
||||
setSigningSecret={setSlackSigningSecret}
|
||||
isLoadingToken={isLoadingToken}
|
||||
testResult={testResult}
|
||||
copied={copied}
|
||||
copyToClipboard={copyToClipboard}
|
||||
testWebhook={testWebhook}
|
||||
/>
|
||||
)
|
||||
case 'generic':
|
||||
default:
|
||||
return (
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { CheckCircle2, ExternalLink } from 'lucide-react'
|
||||
import { DiscordIcon, GithubIcon, StripeIcon, WhatsAppIcon, SlackIcon } from '@/components/icons'
|
||||
import { DiscordIcon, GithubIcon, SlackIcon, StripeIcon, WhatsAppIcon } from '@/components/icons'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { useSubBlockValue } from '../../hooks/use-sub-block-value'
|
||||
@@ -156,7 +156,7 @@ export const WEBHOOK_PROVIDERS: { [key: string]: WebhookProvider } = {
|
||||
icon: (props) => <SlackIcon {...props} />,
|
||||
configFields: {
|
||||
signingSecret: {
|
||||
type: 'string',
|
||||
type: 'string',
|
||||
label: 'Signing Secret',
|
||||
placeholder: 'Enter your Slack app signing secret',
|
||||
description: 'The signing secret from your Slack app to validate request authenticity.',
|
||||
|
||||
@@ -67,7 +67,7 @@ function findAllPathNodes(edges: any[], targetNodeId: string): string[] {
|
||||
const visited = new Set<string>()
|
||||
const queue: [string, number][] = [[targetNodeId, 0]] // [nodeId, distance]
|
||||
const pathNodes = new Set<string>()
|
||||
|
||||
|
||||
// Build a reverse adjacency list for faster traversal
|
||||
const reverseAdjList: Record<string, string[]> = {}
|
||||
for (const edge of edges) {
|
||||
@@ -76,11 +76,11 @@ function findAllPathNodes(edges: any[], targetNodeId: string): string[] {
|
||||
}
|
||||
reverseAdjList[edge.target].push(edge.source)
|
||||
}
|
||||
|
||||
|
||||
// BFS to find all ancestors and their shortest distance from target
|
||||
while (queue.length > 0) {
|
||||
const [currentNodeId, distance] = queue.shift()!
|
||||
|
||||
|
||||
if (visited.has(currentNodeId)) {
|
||||
// If we've seen this node before, update its distance if this path is shorter
|
||||
const currentDistance = nodeDistances.get(currentNodeId) || Infinity
|
||||
@@ -89,24 +89,24 @@ function findAllPathNodes(edges: any[], targetNodeId: string): string[] {
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
visited.add(currentNodeId)
|
||||
nodeDistances.set(currentNodeId, distance)
|
||||
|
||||
|
||||
// Don't add the target node itself to the results
|
||||
if (currentNodeId !== targetNodeId) {
|
||||
pathNodes.add(currentNodeId)
|
||||
}
|
||||
|
||||
|
||||
// Get all incoming edges from the reverse adjacency list
|
||||
const incomingNodeIds = reverseAdjList[currentNodeId] || []
|
||||
|
||||
|
||||
// Add all source nodes to the queue with incremented distance
|
||||
for (const sourceId of incomingNodeIds) {
|
||||
queue.push([sourceId, distance + 1])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return Array.from(pathNodes)
|
||||
}
|
||||
|
||||
@@ -121,46 +121,46 @@ export function useBlockConnections(blockId: string) {
|
||||
|
||||
// Find all blocks along paths leading to this block
|
||||
const allPathNodeIds = findAllPathNodes(edges, blockId)
|
||||
|
||||
|
||||
// Map each path node to a ConnectedBlock structure
|
||||
const allPathConnections = allPathNodeIds.map(sourceId => {
|
||||
const sourceBlock = blocks[sourceId]
|
||||
if (!sourceBlock) return null
|
||||
|
||||
// Get the response format from the subblock store
|
||||
const responseFormatValue = useSubBlockStore
|
||||
.getState()
|
||||
.getValue(sourceId, 'responseFormat')
|
||||
const allPathConnections = allPathNodeIds
|
||||
.map((sourceId) => {
|
||||
const sourceBlock = blocks[sourceId]
|
||||
if (!sourceBlock) return null
|
||||
|
||||
let responseFormat
|
||||
// Get the response format from the subblock store
|
||||
const responseFormatValue = useSubBlockStore.getState().getValue(sourceId, 'responseFormat')
|
||||
|
||||
try {
|
||||
responseFormat =
|
||||
typeof responseFormatValue === 'string' && responseFormatValue
|
||||
? JSON.parse(responseFormatValue)
|
||||
: responseFormatValue // Handle case where it's already an object
|
||||
} catch (e) {
|
||||
logger.error('Failed to parse response format:', { e })
|
||||
responseFormat = undefined
|
||||
}
|
||||
let responseFormat
|
||||
|
||||
// Get the default output type from the block's outputs
|
||||
const defaultOutputs: Field[] = Object.entries(sourceBlock.outputs || {}).map(([key]) => ({
|
||||
name: key,
|
||||
type: 'string',
|
||||
}))
|
||||
try {
|
||||
responseFormat =
|
||||
typeof responseFormatValue === 'string' && responseFormatValue
|
||||
? JSON.parse(responseFormatValue)
|
||||
: responseFormatValue // Handle case where it's already an object
|
||||
} catch (e) {
|
||||
logger.error('Failed to parse response format:', { e })
|
||||
responseFormat = undefined
|
||||
}
|
||||
|
||||
// Extract fields from the response format using our helper function
|
||||
const outputFields = responseFormat ? extractFieldsFromSchema(responseFormat) : defaultOutputs
|
||||
// Get the default output type from the block's outputs
|
||||
const defaultOutputs: Field[] = Object.entries(sourceBlock.outputs || {}).map(([key]) => ({
|
||||
name: key,
|
||||
type: 'string',
|
||||
}))
|
||||
|
||||
return {
|
||||
id: sourceBlock.id,
|
||||
type: sourceBlock.type,
|
||||
outputType: outputFields.map((field: Field) => field.name),
|
||||
name: sourceBlock.name,
|
||||
responseFormat,
|
||||
}
|
||||
}).filter(Boolean) as ConnectedBlock[]
|
||||
// Extract fields from the response format using our helper function
|
||||
const outputFields = responseFormat ? extractFieldsFromSchema(responseFormat) : defaultOutputs
|
||||
|
||||
return {
|
||||
id: sourceBlock.id,
|
||||
type: sourceBlock.type,
|
||||
outputType: outputFields.map((field: Field) => field.name),
|
||||
name: sourceBlock.name,
|
||||
responseFormat,
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as ConnectedBlock[]
|
||||
|
||||
// Keep the original incoming connections for compatibility
|
||||
const directIncomingConnections = edges
|
||||
|
||||
@@ -2,16 +2,16 @@ import { useCallback, useState } from 'react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { buildTraceSpans } from '@/lib/logs/trace-spans'
|
||||
import { useConsoleStore } from '@/stores/panel/console/store'
|
||||
import { useExecutionStore } from '@/stores/execution/store'
|
||||
import { useNotificationStore } from '@/stores/notifications/store'
|
||||
import { useConsoleStore } from '@/stores/panel/console/store'
|
||||
import { usePanelStore } from '@/stores/panel/store'
|
||||
import { useVariablesStore } from '@/stores/panel/variables/store'
|
||||
import { useEnvironmentStore } from '@/stores/settings/environment/store'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
import { usePanelStore } from '@/stores/panel/store'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { mergeSubblockState } from '@/stores/workflows/utils'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import { useVariablesStore } from '@/stores/panel/variables/store'
|
||||
import { Executor } from '@/executor'
|
||||
import { ExecutionResult } from '@/executor/types'
|
||||
import { Serializer } from '@/serializer'
|
||||
@@ -86,7 +86,7 @@ export function useWorkflowExecution() {
|
||||
if (!isPanelOpen) {
|
||||
togglePanel()
|
||||
}
|
||||
|
||||
|
||||
// Set active tab to console
|
||||
setActiveTab('console')
|
||||
|
||||
@@ -136,7 +136,12 @@ export function useWorkflowExecution() {
|
||||
const workflow = new Serializer().serializeWorkflow(mergedStates, edges, loops)
|
||||
|
||||
// Create executor and store in global state
|
||||
const newExecutor = new Executor(workflow, currentBlockStates, envVarValues, workflowVariables)
|
||||
const newExecutor = new Executor(
|
||||
workflow,
|
||||
currentBlockStates,
|
||||
envVarValues,
|
||||
workflowVariables
|
||||
)
|
||||
setExecutor(newExecutor)
|
||||
|
||||
// Execute workflow
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import {
|
||||
Atom,
|
||||
BotMessageSquare,
|
||||
Brain,
|
||||
Code,
|
||||
Database,
|
||||
LineChart,
|
||||
MailIcon,
|
||||
Store
|
||||
} from 'lucide-react'
|
||||
import { ReactNode } from 'react'
|
||||
import {
|
||||
Atom,
|
||||
BotMessageSquare,
|
||||
Brain,
|
||||
Code,
|
||||
Database,
|
||||
LineChart,
|
||||
MailIcon,
|
||||
Store,
|
||||
} from 'lucide-react'
|
||||
|
||||
export interface Category {
|
||||
value: string
|
||||
@@ -18,59 +18,59 @@ export interface Category {
|
||||
}
|
||||
|
||||
export const CATEGORIES: Category[] = [
|
||||
{
|
||||
value: 'data',
|
||||
label: 'Data Analysis',
|
||||
{
|
||||
value: 'data',
|
||||
label: 'Data Analysis',
|
||||
icon: <Database className="h-4 w-4 mr-2" />,
|
||||
color: '#0ea5e9' // sky-500
|
||||
color: '#0ea5e9', // sky-500
|
||||
},
|
||||
{
|
||||
value: 'marketing',
|
||||
label: 'Marketing',
|
||||
{
|
||||
value: 'marketing',
|
||||
label: 'Marketing',
|
||||
icon: <MailIcon className="h-4 w-4 mr-2" />,
|
||||
color: '#f43f5e' // rose-500
|
||||
color: '#f43f5e', // rose-500
|
||||
},
|
||||
{
|
||||
value: 'sales',
|
||||
label: 'Sales',
|
||||
{
|
||||
value: 'sales',
|
||||
label: 'Sales',
|
||||
icon: <Store className="h-4 w-4 mr-2" />,
|
||||
color: '#10b981' // emerald-500
|
||||
color: '#10b981', // emerald-500
|
||||
},
|
||||
{
|
||||
value: 'customer_service',
|
||||
label: 'Customer Service',
|
||||
icon: <BotMessageSquare className="h-4 w-4 mr-2" />,
|
||||
color: '#8b5cf6' // violet-500
|
||||
color: '#8b5cf6', // violet-500
|
||||
},
|
||||
{
|
||||
value: 'research',
|
||||
label: 'Research',
|
||||
{
|
||||
value: 'research',
|
||||
label: 'Research',
|
||||
icon: <Atom className="h-4 w-4 mr-2" />,
|
||||
color: '#f59e0b' // amber-500
|
||||
color: '#f59e0b', // amber-500
|
||||
},
|
||||
{
|
||||
value: 'finance',
|
||||
label: 'Finance',
|
||||
{
|
||||
value: 'finance',
|
||||
label: 'Finance',
|
||||
icon: <LineChart className="h-4 w-4 mr-2" />,
|
||||
color: '#14b8a6' // teal-500
|
||||
color: '#14b8a6', // teal-500
|
||||
},
|
||||
{
|
||||
value: 'programming',
|
||||
label: 'Programming',
|
||||
{
|
||||
value: 'programming',
|
||||
label: 'Programming',
|
||||
icon: <Code className="h-4 w-4 mr-2" />,
|
||||
color: '#6366f1' // indigo-500
|
||||
color: '#6366f1', // indigo-500
|
||||
},
|
||||
{
|
||||
value: 'other',
|
||||
label: 'Other',
|
||||
{
|
||||
value: 'other',
|
||||
label: 'Other',
|
||||
icon: <Brain className="h-4 w-4 mr-2" />,
|
||||
color: '#7F2FFF' // Brand purple
|
||||
color: '#7F2FFF', // Brand purple
|
||||
},
|
||||
]
|
||||
|
||||
// Helper functions to get category information
|
||||
export const getCategoryByValue = (value: string): Category => {
|
||||
return CATEGORIES.find(cat => cat.value === value) || CATEGORIES[CATEGORIES.length - 1]
|
||||
return CATEGORIES.find((cat) => cat.value === value) || CATEGORIES[CATEGORIES.length - 1]
|
||||
}
|
||||
|
||||
export const getCategoryLabel = (value: string): string => {
|
||||
@@ -83,4 +83,4 @@ export const getCategoryIcon = (value: string): ReactNode => {
|
||||
|
||||
export const getCategoryColor = (value: string): string => {
|
||||
return getCategoryByValue(value).color
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1716,7 +1716,6 @@ export function EyeIcon(props: SVGProps<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export function ConfluenceIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
@@ -1729,10 +1728,10 @@ export function ConfluenceIcon(props: SVGProps<SVGSVGElement>) {
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill="#1868DB"
|
||||
d="M20.602 20.234c-6.584-3.183-8.507-3.66-11.281-3.66-3.255 0-6.03 1.355-8.507 5.16l-.407.622c-.333.513-.407.696-.407.915s.111.403.518.659l4.18 2.598c.221.146.406.22.591.22.222 0 .37-.11.592-.44l.666-1.024c1.035-1.574 1.96-2.086 3.144-2.086 1.035 0 2.256.293 3.772 1.025l4.365 2.049c.444.22.925.11 1.146-.403l2.072-4.537c.222-.512.074-.842-.444-1.098M1.406 12.22c6.583 3.184 8.507 3.66 11.28 3.66 3.256 0 6.03-1.354 8.508-5.16l.407-.622c.332-.512.406-.695.406-.915s-.11-.402-.518-.658L17.31 5.927c-.222-.147-.407-.22-.592-.22-.222 0-.37.11-.592.44l-.665 1.024c-1.036 1.573-1.96 2.086-3.144 2.086-1.036 0-2.257-.293-3.773-1.025L4.18 6.183c-.444-.22-.925-.11-1.147.402L.962 11.123c-.222.512-.074.841.444 1.098"
|
||||
<path
|
||||
fill="#1868DB"
|
||||
d="M20.602 20.234c-6.584-3.183-8.507-3.66-11.281-3.66-3.255 0-6.03 1.355-8.507 5.16l-.407.622c-.333.513-.407.696-.407.915s.111.403.518.659l4.18 2.598c.221.146.406.22.591.22.222 0 .37-.11.592-.44l.666-1.024c1.035-1.574 1.96-2.086 3.144-2.086 1.035 0 2.256.293 3.772 1.025l4.365 2.049c.444.22.925.11 1.146-.403l2.072-4.537c.222-.512.074-.842-.444-1.098M1.406 12.22c6.583 3.184 8.507 3.66 11.28 3.66 3.256 0 6.03-1.354 8.508-5.16l.407-.622c.332-.512.406-.695.406-.915s-.11-.402-.518-.658L17.31 5.927c-.222-.147-.407-.22-.592-.22-.222 0-.37.11-.592.44l-.665 1.024c-1.036 1.573-1.96 2.086-3.144 2.086-1.036 0-2.257-.293-3.773-1.025L4.18 6.183c-.444-.22-.925-.11-1.147.402L.962 11.123c-.222.512-.074.841.444 1.098"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,6 +42,12 @@ import { cn } from '@/lib/utils'
|
||||
// This file is not typed correctly from shadcn, so we're disabling the type checker
|
||||
// @ts-nocheck
|
||||
|
||||
// This file is not typed correctly from shadcn, so we're disabling the type checker
|
||||
// @ts-nocheck
|
||||
|
||||
// This file is not typed correctly from shadcn, so we're disabling the type checker
|
||||
// @ts-nocheck
|
||||
|
||||
const Command = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive> & {
|
||||
|
||||
+35
-45
@@ -1,36 +1,33 @@
|
||||
"use client"
|
||||
'use client'
|
||||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import * as React from 'react'
|
||||
import * as LabelPrimitive from '@radix-ui/react-label'
|
||||
import { Slot } from '@radix-ui/react-slot'
|
||||
import {
|
||||
Controller,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
type ControllerProps,
|
||||
type FieldPath,
|
||||
type FieldValues,
|
||||
} from "react-hook-form"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Label } from "@/components/ui/label"
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
} from 'react-hook-form'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const Form = FormProvider
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName
|
||||
}
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue
|
||||
)
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue)
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
@@ -49,7 +46,7 @@ const useFormField = () => {
|
||||
const fieldState = getFieldState(fieldContext.name, formState)
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>")
|
||||
throw new Error('useFormField should be used within <FormField>')
|
||||
}
|
||||
|
||||
const { id } = itemContext
|
||||
@@ -68,23 +65,20 @@ type FormItemContextValue = {
|
||||
id: string
|
||||
}
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue
|
||||
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue)
|
||||
|
||||
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => {
|
||||
const id = React.useId()
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn('space-y-2', className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
const FormItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const id = React.useId()
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
)
|
||||
})
|
||||
FormItem.displayName = "FormItem"
|
||||
FormItem.displayName = 'FormItem'
|
||||
|
||||
const FormLabel = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
@@ -95,13 +89,13 @@ const FormLabel = React.forwardRef<
|
||||
return (
|
||||
<Label
|
||||
ref={ref}
|
||||
className={cn(error && "text-destructive", className)}
|
||||
className={cn(error && 'text-destructive', className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormLabel.displayName = "FormLabel"
|
||||
FormLabel.displayName = 'FormLabel'
|
||||
|
||||
const FormControl = React.forwardRef<
|
||||
React.ElementRef<typeof Slot>,
|
||||
@@ -113,17 +107,13 @@ const FormControl = React.forwardRef<
|
||||
<Slot
|
||||
ref={ref}
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error
|
||||
? `${formDescriptionId}`
|
||||
: `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormControl.displayName = "FormControl"
|
||||
FormControl.displayName = 'FormControl'
|
||||
|
||||
const FormDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
@@ -135,19 +125,19 @@ const FormDescription = React.forwardRef<
|
||||
<p
|
||||
ref={ref}
|
||||
id={formDescriptionId}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
className={cn('text-sm text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormDescription.displayName = "FormDescription"
|
||||
FormDescription.displayName = 'FormDescription'
|
||||
|
||||
const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { error, formMessageId } = useFormField()
|
||||
const body = error ? String(error?.message ?? "") : children
|
||||
const body = error ? String(error?.message ?? '') : children
|
||||
|
||||
if (!body) {
|
||||
return null
|
||||
@@ -157,14 +147,14 @@ const FormMessage = React.forwardRef<
|
||||
<p
|
||||
ref={ref}
|
||||
id={formMessageId}
|
||||
className={cn("text-sm font-medium text-destructive", className)}
|
||||
className={cn('text-sm font-medium text-destructive', className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
)
|
||||
})
|
||||
FormMessage.displayName = "FormMessage"
|
||||
FormMessage.displayName = 'FormMessage'
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
|
||||
@@ -93,12 +93,8 @@
|
||||
"name": "account_user_id_user_id_fk",
|
||||
"tableFrom": "account",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -170,12 +166,8 @@
|
||||
"name": "api_key_user_id_user_id_fk",
|
||||
"tableFrom": "api_key",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -185,9 +177,7 @@
|
||||
"api_key_key_unique": {
|
||||
"name": "api_key_key_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"key"
|
||||
]
|
||||
"columns": ["key"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -230,12 +220,8 @@
|
||||
"name": "environment_user_id_user_id_fk",
|
||||
"tableFrom": "environment",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -245,9 +231,7 @@
|
||||
"environment_user_id_unique": {
|
||||
"name": "environment_user_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id"
|
||||
]
|
||||
"columns": ["user_id"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -341,12 +325,8 @@
|
||||
"name": "marketplace_workflow_id_workflow_id_fk",
|
||||
"tableFrom": "marketplace",
|
||||
"tableTo": "workflow",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["workflow_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
@@ -354,12 +334,8 @@
|
||||
"name": "marketplace_author_id_user_id_fk",
|
||||
"tableFrom": "marketplace",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"author_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["author_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -428,12 +404,8 @@
|
||||
"name": "marketplace_star_marketplace_id_marketplace_id_fk",
|
||||
"tableFrom": "marketplace_star",
|
||||
"tableTo": "marketplace",
|
||||
"columnsFrom": [
|
||||
"marketplace_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["marketplace_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
@@ -441,12 +413,8 @@
|
||||
"name": "marketplace_star_user_id_user_id_fk",
|
||||
"tableFrom": "marketplace_star",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -516,12 +484,8 @@
|
||||
"name": "session_user_id_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -531,9 +495,7 @@
|
||||
"session_token_unique": {
|
||||
"name": "session_token_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"token"
|
||||
]
|
||||
"columns": ["token"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -576,12 +538,8 @@
|
||||
"name": "settings_user_id_user_id_fk",
|
||||
"tableFrom": "settings",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -591,9 +549,7 @@
|
||||
"settings_user_id_unique": {
|
||||
"name": "settings_user_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id"
|
||||
]
|
||||
"columns": ["user_id"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -654,9 +610,7 @@
|
||||
"user_email_unique": {
|
||||
"name": "user_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
"columns": ["email"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -757,9 +711,7 @@
|
||||
"waitlist_email_unique": {
|
||||
"name": "waitlist_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
"columns": ["email"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -844,12 +796,8 @@
|
||||
"name": "webhook_workflow_id_workflow_id_fk",
|
||||
"tableFrom": "webhook",
|
||||
"tableTo": "workflow",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["workflow_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -952,12 +900,8 @@
|
||||
"name": "workflow_user_id_user_id_fk",
|
||||
"tableFrom": "workflow",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -1034,12 +978,8 @@
|
||||
"name": "workflow_logs_workflow_id_workflow_id_fk",
|
||||
"tableFrom": "workflow_logs",
|
||||
"tableTo": "workflow",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["workflow_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -1111,12 +1051,8 @@
|
||||
"name": "workflow_schedule_workflow_id_workflow_id_fk",
|
||||
"tableFrom": "workflow_schedule",
|
||||
"tableTo": "workflow",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["workflow_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -1126,9 +1062,7 @@
|
||||
"workflow_schedule_workflow_id_unique": {
|
||||
"name": "workflow_schedule_workflow_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"workflow_id"
|
||||
]
|
||||
"columns": ["workflow_id"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -1147,4 +1081,4 @@
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,12 +93,8 @@
|
||||
"name": "account_user_id_user_id_fk",
|
||||
"tableFrom": "account",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -170,12 +166,8 @@
|
||||
"name": "api_key_user_id_user_id_fk",
|
||||
"tableFrom": "api_key",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -185,9 +177,7 @@
|
||||
"api_key_key_unique": {
|
||||
"name": "api_key_key_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"key"
|
||||
]
|
||||
"columns": ["key"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -230,12 +220,8 @@
|
||||
"name": "environment_user_id_user_id_fk",
|
||||
"tableFrom": "environment",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -245,9 +231,7 @@
|
||||
"environment_user_id_unique": {
|
||||
"name": "environment_user_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id"
|
||||
]
|
||||
"columns": ["user_id"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -341,12 +325,8 @@
|
||||
"name": "marketplace_workflow_id_workflow_id_fk",
|
||||
"tableFrom": "marketplace",
|
||||
"tableTo": "workflow",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["workflow_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
@@ -354,12 +334,8 @@
|
||||
"name": "marketplace_author_id_user_id_fk",
|
||||
"tableFrom": "marketplace",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"author_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["author_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -428,12 +404,8 @@
|
||||
"name": "marketplace_star_marketplace_id_marketplace_id_fk",
|
||||
"tableFrom": "marketplace_star",
|
||||
"tableTo": "marketplace",
|
||||
"columnsFrom": [
|
||||
"marketplace_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["marketplace_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
@@ -441,12 +413,8 @@
|
||||
"name": "marketplace_star_user_id_user_id_fk",
|
||||
"tableFrom": "marketplace_star",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -516,12 +484,8 @@
|
||||
"name": "session_user_id_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -531,9 +495,7 @@
|
||||
"session_token_unique": {
|
||||
"name": "session_token_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"token"
|
||||
]
|
||||
"columns": ["token"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -576,12 +538,8 @@
|
||||
"name": "settings_user_id_user_id_fk",
|
||||
"tableFrom": "settings",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -591,9 +549,7 @@
|
||||
"settings_user_id_unique": {
|
||||
"name": "settings_user_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id"
|
||||
]
|
||||
"columns": ["user_id"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -654,9 +610,7 @@
|
||||
"user_email_unique": {
|
||||
"name": "user_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
"columns": ["email"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -757,9 +711,7 @@
|
||||
"waitlist_email_unique": {
|
||||
"name": "waitlist_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
"columns": ["email"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -844,12 +796,8 @@
|
||||
"name": "webhook_workflow_id_workflow_id_fk",
|
||||
"tableFrom": "webhook",
|
||||
"tableTo": "workflow",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["workflow_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -959,12 +907,8 @@
|
||||
"name": "workflow_user_id_user_id_fk",
|
||||
"tableFrom": "workflow",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -1041,12 +985,8 @@
|
||||
"name": "workflow_logs_workflow_id_workflow_id_fk",
|
||||
"tableFrom": "workflow_logs",
|
||||
"tableTo": "workflow",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["workflow_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -1118,12 +1058,8 @@
|
||||
"name": "workflow_schedule_workflow_id_workflow_id_fk",
|
||||
"tableFrom": "workflow_schedule",
|
||||
"tableTo": "workflow",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["workflow_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -1133,9 +1069,7 @@
|
||||
"workflow_schedule_workflow_id_unique": {
|
||||
"name": "workflow_schedule_workflow_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"workflow_id"
|
||||
]
|
||||
"columns": ["workflow_id"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -1154,4 +1088,4 @@
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,12 +93,8 @@
|
||||
"name": "account_user_id_user_id_fk",
|
||||
"tableFrom": "account",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -170,12 +166,8 @@
|
||||
"name": "api_key_user_id_user_id_fk",
|
||||
"tableFrom": "api_key",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -185,9 +177,7 @@
|
||||
"api_key_key_unique": {
|
||||
"name": "api_key_key_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"key"
|
||||
]
|
||||
"columns": ["key"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -230,12 +220,8 @@
|
||||
"name": "environment_user_id_user_id_fk",
|
||||
"tableFrom": "environment",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -245,9 +231,7 @@
|
||||
"environment_user_id_unique": {
|
||||
"name": "environment_user_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id"
|
||||
]
|
||||
"columns": ["user_id"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -341,12 +325,8 @@
|
||||
"name": "marketplace_workflow_id_workflow_id_fk",
|
||||
"tableFrom": "marketplace",
|
||||
"tableTo": "workflow",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["workflow_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
@@ -354,12 +334,8 @@
|
||||
"name": "marketplace_author_id_user_id_fk",
|
||||
"tableFrom": "marketplace",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"author_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["author_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -428,12 +404,8 @@
|
||||
"name": "marketplace_star_marketplace_id_marketplace_id_fk",
|
||||
"tableFrom": "marketplace_star",
|
||||
"tableTo": "marketplace",
|
||||
"columnsFrom": [
|
||||
"marketplace_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["marketplace_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
@@ -441,12 +413,8 @@
|
||||
"name": "marketplace_star_user_id_user_id_fk",
|
||||
"tableFrom": "marketplace_star",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -516,12 +484,8 @@
|
||||
"name": "session_user_id_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -531,9 +495,7 @@
|
||||
"session_token_unique": {
|
||||
"name": "session_token_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"token"
|
||||
]
|
||||
"columns": ["token"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -576,12 +538,8 @@
|
||||
"name": "settings_user_id_user_id_fk",
|
||||
"tableFrom": "settings",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -591,9 +549,7 @@
|
||||
"settings_user_id_unique": {
|
||||
"name": "settings_user_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id"
|
||||
]
|
||||
"columns": ["user_id"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -654,9 +610,7 @@
|
||||
"user_email_unique": {
|
||||
"name": "user_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
"columns": ["email"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -735,12 +689,8 @@
|
||||
"name": "user_stats_user_id_user_id_fk",
|
||||
"tableFrom": "user_stats",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -750,9 +700,7 @@
|
||||
"user_stats_user_id_unique": {
|
||||
"name": "user_stats_user_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"user_id"
|
||||
]
|
||||
"columns": ["user_id"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -853,9 +801,7 @@
|
||||
"waitlist_email_unique": {
|
||||
"name": "waitlist_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
"columns": ["email"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -940,12 +886,8 @@
|
||||
"name": "webhook_workflow_id_workflow_id_fk",
|
||||
"tableFrom": "webhook",
|
||||
"tableTo": "workflow",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["workflow_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -1075,12 +1017,8 @@
|
||||
"name": "workflow_user_id_user_id_fk",
|
||||
"tableFrom": "workflow",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -1157,12 +1095,8 @@
|
||||
"name": "workflow_logs_workflow_id_workflow_id_fk",
|
||||
"tableFrom": "workflow_logs",
|
||||
"tableTo": "workflow",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["workflow_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -1234,12 +1168,8 @@
|
||||
"name": "workflow_schedule_workflow_id_workflow_id_fk",
|
||||
"tableFrom": "workflow_schedule",
|
||||
"tableTo": "workflow",
|
||||
"columnsFrom": [
|
||||
"workflow_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["workflow_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
@@ -1249,9 +1179,7 @@
|
||||
"workflow_schedule_workflow_id_unique": {
|
||||
"name": "workflow_schedule_workflow_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"workflow_id"
|
||||
]
|
||||
"columns": ["workflow_id"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
@@ -1270,4 +1198,4 @@
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-10
@@ -1,6 +1,6 @@
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { useConsoleStore } from '@/stores/panel/console/store'
|
||||
import { useExecutionStore } from '@/stores/execution/store'
|
||||
import { useConsoleStore } from '@/stores/panel/console/store'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
import { BlockOutput } from '@/blocks/types'
|
||||
import { SerializedBlock, SerializedWorkflow } from '@/serializer/types'
|
||||
@@ -394,8 +394,9 @@ export class Executor {
|
||||
const sourceExecuted = executedBlocks.has(conn.source)
|
||||
const sourceBlock = this.workflow.blocks.find((b) => b.id === conn.source)
|
||||
const sourceBlockState = context.blockStates.get(conn.source)
|
||||
const hasSourceError = sourceBlockState?.output?.error !== undefined ||
|
||||
sourceBlockState?.output?.response?.error !== undefined
|
||||
const hasSourceError =
|
||||
sourceBlockState?.output?.error !== undefined ||
|
||||
sourceBlockState?.output?.response?.error !== undefined
|
||||
|
||||
// For condition blocks, check if this is the selected path
|
||||
if (conn.sourceHandle?.startsWith('condition-')) {
|
||||
@@ -605,7 +606,10 @@ export class Executor {
|
||||
const hasErrorPath = this.activateErrorPath(blockId, context)
|
||||
|
||||
// Console.error the error for visibility
|
||||
logger.error(`Error executing block ${block.metadata?.name || blockId}:`, this.sanitizeError(error))
|
||||
logger.error(
|
||||
`Error executing block ${block.metadata?.name || blockId}:`,
|
||||
this.sanitizeError(error)
|
||||
)
|
||||
|
||||
// If there are error paths to follow, return error output instead of throwing
|
||||
if (hasErrorPath) {
|
||||
@@ -635,7 +639,7 @@ export class Executor {
|
||||
/**
|
||||
* Activates error paths from a block that had an error.
|
||||
* Checks for connections from the block's "error" handle and adds them to the active execution path.
|
||||
*
|
||||
*
|
||||
* @param blockId - ID of the block that had an error
|
||||
* @param context - Current execution context
|
||||
* @returns Whether there was an error path to follow
|
||||
@@ -682,7 +686,7 @@ export class Executor {
|
||||
status: output.status || 500,
|
||||
},
|
||||
error: output.error,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (output && typeof output === 'object' && 'response' in output) {
|
||||
@@ -690,10 +694,10 @@ export class Executor {
|
||||
if (output.response && output.response.error) {
|
||||
return {
|
||||
...output,
|
||||
error: output.response.error
|
||||
};
|
||||
error: output.response.error,
|
||||
}
|
||||
}
|
||||
return output as NormalizedBlockOutput;
|
||||
return output as NormalizedBlockOutput
|
||||
}
|
||||
|
||||
const blockType = block.metadata?.id
|
||||
@@ -923,4 +927,4 @@ export class Executor {
|
||||
|
||||
return { message: `Unexpected error type: ${typeof error}` }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,8 +98,10 @@ export class PathTracker {
|
||||
} else {
|
||||
// For regular blocks, activate all outgoing connections based on success or error status
|
||||
const blockState = context.blockStates.get(blockId)
|
||||
const hasError = blockState?.output?.error !== undefined || blockState?.output?.response?.error !== undefined
|
||||
|
||||
const hasError =
|
||||
blockState?.output?.error !== undefined ||
|
||||
blockState?.output?.response?.error !== undefined
|
||||
|
||||
// Get all outgoing connections
|
||||
const outgoingConnections = this.workflow.connections.filter(
|
||||
(conn) => conn.source === blockId
|
||||
@@ -111,7 +113,7 @@ export class PathTracker {
|
||||
if (hasError) {
|
||||
context.activeExecutionPath.add(conn.target)
|
||||
}
|
||||
}
|
||||
}
|
||||
// For regular (source) connections, only activate them if there's no error
|
||||
else if (conn.sourceHandle === 'source' || !conn.sourceHandle) {
|
||||
if (!hasError) {
|
||||
|
||||
+11
-13
@@ -62,7 +62,7 @@ export class InputResolver {
|
||||
if (typeof value === 'string') {
|
||||
// First check for variable references
|
||||
let resolvedValue = this.resolveVariableReferences(value)
|
||||
|
||||
|
||||
// Then resolve block references
|
||||
resolvedValue = this.resolveBlockReferences(resolvedValue, context, block)
|
||||
|
||||
@@ -102,7 +102,7 @@ export class InputResolver {
|
||||
|
||||
/**
|
||||
* Resolves workflow variable references in a string (<variable.name>).
|
||||
*
|
||||
*
|
||||
* @param value - String containing variable references
|
||||
* @returns String with resolved variable references
|
||||
*/
|
||||
@@ -114,23 +114,21 @@ export class InputResolver {
|
||||
|
||||
for (const match of variableMatches) {
|
||||
const variableName = match.slice('<variable.'.length, -1)
|
||||
|
||||
|
||||
// Find the variable by normalized name (without spaces)
|
||||
const foundVariable = Object.entries(this.workflowVariables).find(
|
||||
([_, variable]) => {
|
||||
const normalizedName = (variable.name || '').replace(/\s+/g, '')
|
||||
return normalizedName === variableName
|
||||
}
|
||||
)
|
||||
const foundVariable = Object.entries(this.workflowVariables).find(([_, variable]) => {
|
||||
const normalizedName = (variable.name || '').replace(/\s+/g, '')
|
||||
return normalizedName === variableName
|
||||
})
|
||||
|
||||
if (foundVariable) {
|
||||
const [_, variable] = foundVariable
|
||||
// Format the value appropriately
|
||||
const formattedValue =
|
||||
const formattedValue =
|
||||
typeof variable.value === 'object'
|
||||
? JSON.stringify(variable.value)
|
||||
: String(variable.value)
|
||||
|
||||
|
||||
resolvedValue = resolvedValue.replace(match, formattedValue)
|
||||
}
|
||||
}
|
||||
@@ -163,7 +161,7 @@ export class InputResolver {
|
||||
if (match.startsWith('<variable.')) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
const path = match.slice(1, -1)
|
||||
const [blockRef, ...pathParts] = path.split('.')
|
||||
|
||||
@@ -408,7 +406,7 @@ export class InputResolver {
|
||||
if (typeof value === 'string') {
|
||||
// First resolve variable references
|
||||
const resolvedVars = this.resolveVariableReferences(value)
|
||||
|
||||
|
||||
// Then resolve block references
|
||||
const resolvedReferences = this.resolveBlockReferences(resolvedVars, context, currentBlock)
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ export const STORAGE_KEYS = {
|
||||
}
|
||||
|
||||
export const API_ENDPOINTS = {
|
||||
WORKFLOW: '/api/workflows/sync',
|
||||
SYNC: '/api/workflows/sync',
|
||||
ENVIRONMENT: '/api/environment',
|
||||
SCHEDULE: '/api/schedules/schedule',
|
||||
SETTINGS: '/api/settings',
|
||||
WORKFLOW_VARIABLES: '/api/workflows/[id]/variables',
|
||||
WORKFLOWS: '/api/workflows',
|
||||
}
|
||||
|
||||
export const SYNC_INTERVALS = {
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
import { useEffect } from 'react'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { useChatStore } from './chat/store'
|
||||
import { useConsoleStore } from './panel/console/store'
|
||||
import { useCustomToolsStore } from './custom-tools/store'
|
||||
import { useExecutionStore } from './execution/store'
|
||||
import { useNotificationStore } from './notifications/store'
|
||||
import { useEnvironmentStore } from './settings/environment/store'
|
||||
import { useConsoleStore } from './panel/console/store'
|
||||
import { useVariablesStore } from './panel/variables/store'
|
||||
import { useEnvironmentStore } from './settings/environment/store'
|
||||
import { getSyncManagers, initializeSyncManagers, resetSyncManagers } from './sync-registry'
|
||||
import {
|
||||
loadRegistry,
|
||||
|
||||
@@ -62,13 +62,13 @@ export const useNotificationStore = create<NotificationStore>()(
|
||||
showNotification: (id) =>
|
||||
set((state) => {
|
||||
// Find the notification first to ensure it exists
|
||||
const notification = state.notifications.find(n => n.id === id)
|
||||
const notification = state.notifications.find((n) => n.id === id)
|
||||
if (!notification) return { notifications: state.notifications }
|
||||
|
||||
|
||||
// Bring the notification to the top and make it visible
|
||||
const filteredNotifications = state.notifications.filter(n => n.id !== id)
|
||||
const filteredNotifications = state.notifications.filter((n) => n.id !== id)
|
||||
const updatedNotification = { ...notification, isVisible: true, read: false }
|
||||
|
||||
|
||||
// Put the notification at the top so it's easily visible in dropdowns
|
||||
const newNotifications = [updatedNotification, ...filteredNotifications]
|
||||
persistNotifications(newNotifications)
|
||||
|
||||
@@ -22,4 +22,4 @@ export const usePanelStore = create<PanelStore>()(
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -5,4 +5,4 @@ export interface PanelStore {
|
||||
activeTab: PanelTab
|
||||
togglePanel: () => void
|
||||
setActiveTab: (tab: PanelTab) => void
|
||||
}
|
||||
}
|
||||
|
||||
+144
-117
@@ -1,10 +1,10 @@
|
||||
import { create } from 'zustand'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { devtools, persist } from 'zustand/middleware'
|
||||
import { Variable, VariablesStore } from './types'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { API_ENDPOINTS } from '@/stores/constants'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { Variable, VariablesStore } from './types'
|
||||
|
||||
const logger = createLogger('Variables Store')
|
||||
const SAVE_DEBOUNCE_DELAY = 500 // 500ms debounce delay
|
||||
@@ -25,61 +25,64 @@ export const useVariablesStore = create<VariablesStore>()(
|
||||
|
||||
addVariable: (variable) => {
|
||||
const id = crypto.randomUUID()
|
||||
|
||||
|
||||
// Get variables for this workflow
|
||||
const workflowVariables = get().getVariablesByWorkflowId(variable.workflowId)
|
||||
|
||||
|
||||
// Auto-generate variable name if not provided or it's a default pattern name
|
||||
if (!variable.name || /^variable\d+$/.test(variable.name)) {
|
||||
// Find the highest existing Variable N number
|
||||
const existingNumbers = workflowVariables
|
||||
.map(v => {
|
||||
.map((v) => {
|
||||
const match = v.name.match(/^variable(\d+)$/)
|
||||
return match ? parseInt(match[1]) : 0
|
||||
})
|
||||
.filter(n => !isNaN(n))
|
||||
|
||||
.filter((n) => !isNaN(n))
|
||||
|
||||
// Set new number to max + 1, or 1 if none exist
|
||||
const nextNumber = existingNumbers.length > 0
|
||||
? Math.max(...existingNumbers) + 1
|
||||
: 1
|
||||
|
||||
const nextNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1
|
||||
|
||||
variable.name = `variable${nextNumber}`
|
||||
}
|
||||
|
||||
|
||||
// Ensure name uniqueness within the workflow
|
||||
let uniqueName = variable.name
|
||||
let nameIndex = 1
|
||||
|
||||
|
||||
// Check if name already exists in this workflow
|
||||
while (workflowVariables.some(v => v.name === uniqueName)) {
|
||||
while (workflowVariables.some((v) => v.name === uniqueName)) {
|
||||
uniqueName = `${variable.name} (${nameIndex})`
|
||||
nameIndex++
|
||||
}
|
||||
|
||||
|
||||
// Handle initial value
|
||||
let variableValue = variable.value
|
||||
|
||||
|
||||
// Auto-add quotes for string values if they aren't already quoted
|
||||
if (variable.type === 'string' &&
|
||||
typeof variableValue === 'string' &&
|
||||
variableValue.trim() !== '') {
|
||||
if (
|
||||
variable.type === 'string' &&
|
||||
typeof variableValue === 'string' &&
|
||||
variableValue.trim() !== ''
|
||||
) {
|
||||
// Only add quotes if not already properly quoted
|
||||
const trimmedValue = variableValue.trim()
|
||||
|
||||
|
||||
// Check if entire string is already properly quoted
|
||||
const isAlreadyQuoted = (
|
||||
(trimmedValue.startsWith('"') && trimmedValue.endsWith('"') && trimmedValue.length >= 2) ||
|
||||
(trimmedValue.startsWith("'") && trimmedValue.endsWith("'") && trimmedValue.length >= 2)
|
||||
)
|
||||
|
||||
const isAlreadyQuoted =
|
||||
(trimmedValue.startsWith('"') &&
|
||||
trimmedValue.endsWith('"') &&
|
||||
trimmedValue.length >= 2) ||
|
||||
(trimmedValue.startsWith("'") &&
|
||||
trimmedValue.endsWith("'") &&
|
||||
trimmedValue.length >= 2)
|
||||
|
||||
if (!isAlreadyQuoted) {
|
||||
// Escape any existing quotes in the content
|
||||
const escapedValue = variableValue.replace(/"/g, '\\"')
|
||||
variableValue = `"${escapedValue}"`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
set((state) => ({
|
||||
variables: {
|
||||
...state.variables,
|
||||
@@ -92,88 +95,94 @@ export const useVariablesStore = create<VariablesStore>()(
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
|
||||
// Auto-save to DB
|
||||
get().saveVariables(variable.workflowId)
|
||||
|
||||
|
||||
return id
|
||||
},
|
||||
|
||||
updateVariable: (id, update) => {
|
||||
set((state) => {
|
||||
if (!state.variables[id]) return state
|
||||
|
||||
|
||||
// If name is being updated, ensure it's unique
|
||||
if (update.name) {
|
||||
const oldVariable = state.variables[id]
|
||||
const oldVariableName = oldVariable.name
|
||||
const workflowId = oldVariable.workflowId
|
||||
const workflowVariables = Object.values(state.variables).filter(
|
||||
v => v.workflowId === workflowId && v.id !== id
|
||||
(v) => v.workflowId === workflowId && v.id !== id
|
||||
)
|
||||
|
||||
|
||||
let uniqueName = update.name
|
||||
let nameIndex = 1
|
||||
|
||||
|
||||
// Check if name already exists in this workflow
|
||||
while (workflowVariables.some(v => v.name === uniqueName)) {
|
||||
while (workflowVariables.some((v) => v.name === uniqueName)) {
|
||||
uniqueName = `${update.name} (${nameIndex})`
|
||||
nameIndex++
|
||||
}
|
||||
|
||||
|
||||
// If name has changed, update references in subblocks
|
||||
if (uniqueName !== oldVariableName) {
|
||||
// Update references in subblock store
|
||||
const subBlockStore = useSubBlockStore.getState()
|
||||
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
||||
|
||||
|
||||
if (activeWorkflowId) {
|
||||
// Get the workflow values for the active workflow
|
||||
const workflowValues = subBlockStore.workflowValues[activeWorkflowId] || {}
|
||||
const updatedWorkflowValues = { ...workflowValues }
|
||||
|
||||
|
||||
// Loop through blocks
|
||||
Object.entries(workflowValues).forEach(([blockId, blockValues]) => {
|
||||
// Loop through subblocks and update references
|
||||
Object.entries(blockValues as Record<string, any>).forEach(([subBlockId, value]) => {
|
||||
const oldVarName = oldVariableName.replace(/\s+/g, '').toLowerCase()
|
||||
const newVarName = uniqueName.replace(/\s+/g, '').toLowerCase()
|
||||
const regex = new RegExp(`<variable\.${oldVarName}>`, 'gi')
|
||||
|
||||
// Use a recursive function to handle all object types
|
||||
updatedWorkflowValues[blockId][subBlockId] = updateReferences(
|
||||
value,
|
||||
regex,
|
||||
`<variable.${newVarName}>`
|
||||
)
|
||||
|
||||
// Helper function to recursively update references in any data structure
|
||||
function updateReferences(value: any, regex: RegExp, replacement: string): any {
|
||||
// Handle string values
|
||||
if (typeof value === 'string') {
|
||||
return regex.test(value) ? value.replace(regex, replacement) : value
|
||||
}
|
||||
|
||||
// Handle arrays
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => updateReferences(item, regex, replacement))
|
||||
}
|
||||
|
||||
// Handle objects
|
||||
if (value !== null && typeof value === 'object') {
|
||||
const result = { ...value }
|
||||
for (const key in result) {
|
||||
result[key] = updateReferences(result[key], regex, replacement)
|
||||
Object.entries(blockValues as Record<string, any>).forEach(
|
||||
([subBlockId, value]) => {
|
||||
const oldVarName = oldVariableName.replace(/\s+/g, '').toLowerCase()
|
||||
const newVarName = uniqueName.replace(/\s+/g, '').toLowerCase()
|
||||
const regex = new RegExp(`<variable\.${oldVarName}>`, 'gi')
|
||||
|
||||
// Use a recursive function to handle all object types
|
||||
updatedWorkflowValues[blockId][subBlockId] = updateReferences(
|
||||
value,
|
||||
regex,
|
||||
`<variable.${newVarName}>`
|
||||
)
|
||||
|
||||
// Helper function to recursively update references in any data structure
|
||||
function updateReferences(
|
||||
value: any,
|
||||
regex: RegExp,
|
||||
replacement: string
|
||||
): any {
|
||||
// Handle string values
|
||||
if (typeof value === 'string') {
|
||||
return regex.test(value) ? value.replace(regex, replacement) : value
|
||||
}
|
||||
return result
|
||||
|
||||
// Handle arrays
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => updateReferences(item, regex, replacement))
|
||||
}
|
||||
|
||||
// Handle objects
|
||||
if (value !== null && typeof value === 'object') {
|
||||
const result = { ...value }
|
||||
for (const key in result) {
|
||||
result[key] = updateReferences(result[key], regex, replacement)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Return unchanged for other types
|
||||
return value
|
||||
}
|
||||
|
||||
// Return unchanged for other types
|
||||
return value
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
// Update the subblock store with the new values
|
||||
useSubBlockStore.setState({
|
||||
workflowValues: {
|
||||
@@ -183,25 +192,30 @@ export const useVariablesStore = create<VariablesStore>()(
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update with unique name
|
||||
update = { ...update, name: uniqueName }
|
||||
}
|
||||
|
||||
// Auto-add quotes for string values if they aren't already quoted
|
||||
if (update.value !== undefined &&
|
||||
state.variables[id].type === 'string' &&
|
||||
typeof update.value === 'string' &&
|
||||
update.value.trim() !== '') {
|
||||
if (
|
||||
update.value !== undefined &&
|
||||
state.variables[id].type === 'string' &&
|
||||
typeof update.value === 'string' &&
|
||||
update.value.trim() !== ''
|
||||
) {
|
||||
// Only add quotes if not already properly quoted
|
||||
const trimmedValue = update.value.trim()
|
||||
|
||||
|
||||
// Check if entire string is already properly quoted
|
||||
const isAlreadyQuoted = (
|
||||
(trimmedValue.startsWith('"') && trimmedValue.endsWith('"') && trimmedValue.length >= 2) ||
|
||||
(trimmedValue.startsWith("'") && trimmedValue.endsWith("'") && trimmedValue.length >= 2)
|
||||
)
|
||||
|
||||
const isAlreadyQuoted =
|
||||
(trimmedValue.startsWith('"') &&
|
||||
trimmedValue.endsWith('"') &&
|
||||
trimmedValue.length >= 2) ||
|
||||
(trimmedValue.startsWith("'") &&
|
||||
trimmedValue.endsWith("'") &&
|
||||
trimmedValue.length >= 2)
|
||||
|
||||
if (!isAlreadyQuoted) {
|
||||
// Escape any existing quotes in the content
|
||||
const escapedValue = update.value.replace(/"/g, '\\"')
|
||||
@@ -219,18 +233,18 @@ export const useVariablesStore = create<VariablesStore>()(
|
||||
|
||||
// Debounced auto-save to DB
|
||||
const workflowId = state.variables[id].workflowId
|
||||
|
||||
|
||||
// Clear existing timer for this workflow if it exists
|
||||
if (saveTimers.has(workflowId)) {
|
||||
clearTimeout(saveTimers.get(workflowId))
|
||||
}
|
||||
|
||||
|
||||
// Set new debounced save timer
|
||||
const timer = setTimeout(() => {
|
||||
get().saveVariables(workflowId)
|
||||
saveTimers.delete(workflowId)
|
||||
}, SAVE_DEBOUNCE_DELAY)
|
||||
|
||||
|
||||
saveTimers.set(workflowId, timer)
|
||||
|
||||
return { variables: updated }
|
||||
@@ -257,15 +271,15 @@ export const useVariablesStore = create<VariablesStore>()(
|
||||
|
||||
const variable = state.variables[id]
|
||||
const newId = crypto.randomUUID()
|
||||
|
||||
|
||||
// Ensure the duplicated name is unique
|
||||
const workflowVariables = get().getVariablesByWorkflowId(variable.workflowId)
|
||||
let baseName = `${variable.name} (copy)`
|
||||
let uniqueName = baseName
|
||||
let nameIndex = 1
|
||||
|
||||
|
||||
// Check if name already exists in this workflow
|
||||
while (workflowVariables.some(v => v.name === uniqueName)) {
|
||||
while (workflowVariables.some((v) => v.name === uniqueName)) {
|
||||
uniqueName = `${baseName} (${nameIndex})`
|
||||
nameIndex++
|
||||
}
|
||||
@@ -292,27 +306,30 @@ export const useVariablesStore = create<VariablesStore>()(
|
||||
loadVariables: async (workflowId) => {
|
||||
// Skip if already loaded to prevent redundant API calls
|
||||
if (loadedWorkflows.has(workflowId)) return
|
||||
|
||||
|
||||
try {
|
||||
set({ isLoading: true, error: null })
|
||||
|
||||
const response = await fetch(`${API_ENDPOINTS.WORKFLOW_VARIABLES}/${workflowId}`)
|
||||
const response = await fetch(`${API_ENDPOINTS.WORKFLOWS}/${workflowId}/variables`)
|
||||
|
||||
// Handle 404 workflow not found gracefully
|
||||
if (response.status === 404) {
|
||||
logger.info(`No variables found for workflow ${workflowId}, initializing empty set`)
|
||||
set((state) => {
|
||||
// Keep variables from other workflows
|
||||
const otherVariables = Object.values(state.variables).reduce((acc, variable) => {
|
||||
if (variable.workflowId !== workflowId) {
|
||||
acc[variable.id] = variable
|
||||
}
|
||||
return acc
|
||||
}, {} as Record<string, Variable>)
|
||||
|
||||
const otherVariables = Object.values(state.variables).reduce(
|
||||
(acc, variable) => {
|
||||
if (variable.workflowId !== workflowId) {
|
||||
acc[variable.id] = variable
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, Variable>
|
||||
)
|
||||
|
||||
// Mark this workflow as loaded to prevent further attempts
|
||||
loadedWorkflows.add(workflowId)
|
||||
|
||||
|
||||
return {
|
||||
variables: otherVariables,
|
||||
isLoading: false,
|
||||
@@ -330,16 +347,19 @@ export const useVariablesStore = create<VariablesStore>()(
|
||||
if (data && typeof data === 'object') {
|
||||
set((state) => {
|
||||
// Merge with existing variables from other workflows
|
||||
const otherVariables = Object.values(state.variables).reduce((acc, variable) => {
|
||||
if (variable.workflowId !== workflowId) {
|
||||
acc[variable.id] = variable
|
||||
}
|
||||
return acc
|
||||
}, {} as Record<string, Variable>)
|
||||
const otherVariables = Object.values(state.variables).reduce(
|
||||
(acc, variable) => {
|
||||
if (variable.workflowId !== workflowId) {
|
||||
acc[variable.id] = variable
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, Variable>
|
||||
)
|
||||
|
||||
// Mark this workflow as loaded
|
||||
loadedWorkflows.add(workflowId)
|
||||
|
||||
|
||||
return {
|
||||
variables: { ...otherVariables, ...data },
|
||||
isLoading: false,
|
||||
@@ -348,16 +368,19 @@ export const useVariablesStore = create<VariablesStore>()(
|
||||
} else {
|
||||
set((state) => {
|
||||
// Keep variables from other workflows
|
||||
const otherVariables = Object.values(state.variables).reduce((acc, variable) => {
|
||||
if (variable.workflowId !== workflowId) {
|
||||
acc[variable.id] = variable
|
||||
}
|
||||
return acc
|
||||
}, {} as Record<string, Variable>)
|
||||
const otherVariables = Object.values(state.variables).reduce(
|
||||
(acc, variable) => {
|
||||
if (variable.workflowId !== workflowId) {
|
||||
acc[variable.id] = variable
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{} as Record<string, Variable>
|
||||
)
|
||||
|
||||
// Mark this workflow as loaded
|
||||
loadedWorkflows.add(workflowId)
|
||||
|
||||
|
||||
return {
|
||||
variables: otherVariables,
|
||||
isLoading: false,
|
||||
@@ -381,7 +404,7 @@ export const useVariablesStore = create<VariablesStore>()(
|
||||
logger.info(`Skipping variable save for non-existent workflow: ${workflowId}`)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
set({ isLoading: true, error: null })
|
||||
|
||||
// Get only variables for this workflow
|
||||
@@ -390,7 +413,7 @@ export const useVariablesStore = create<VariablesStore>()(
|
||||
)
|
||||
|
||||
// Send to DB
|
||||
const response = await fetch(`${API_ENDPOINTS.WORKFLOW_VARIABLES}/${workflowId}`, {
|
||||
const response = await fetch(`${API_ENDPOINTS.WORKFLOWS}/${workflowId}/variables`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -402,7 +425,9 @@ export const useVariablesStore = create<VariablesStore>()(
|
||||
|
||||
// Handle 404 workflow not found gracefully
|
||||
if (response.status === 404) {
|
||||
logger.info(`Cannot save variables - workflow ${workflowId} not found in database yet`)
|
||||
logger.info(
|
||||
`Cannot save variables - workflow ${workflowId} not found in database yet`
|
||||
)
|
||||
// Reset loading state but don't treat as error
|
||||
set({ isLoading: false })
|
||||
return
|
||||
@@ -428,9 +453,11 @@ export const useVariablesStore = create<VariablesStore>()(
|
||||
},
|
||||
|
||||
getVariablesByWorkflowId: (workflowId) => {
|
||||
return Object.values(get().variables).filter((variable) => variable.workflowId === workflowId)
|
||||
return Object.values(get().variables).filter(
|
||||
(variable) => variable.workflowId === workflowId
|
||||
)
|
||||
},
|
||||
|
||||
|
||||
// Reset the loaded workflow tracking
|
||||
resetLoaded: () => {
|
||||
loadedWorkflows.clear()
|
||||
|
||||
@@ -23,28 +23,28 @@ export interface VariablesStore {
|
||||
* If a variable with the same name exists, it will be suffixed with a number
|
||||
*/
|
||||
addVariable: (variable: Omit<Variable, 'id'>) => string
|
||||
|
||||
|
||||
/**
|
||||
* Updates a variable, ensuring name remains unique within the workflow
|
||||
* If an updated name conflicts with existing ones, a numbered suffix is added
|
||||
*/
|
||||
updateVariable: (id: string, update: Partial<Omit<Variable, 'id' | 'workflowId'>>) => void
|
||||
|
||||
|
||||
deleteVariable: (id: string) => void
|
||||
|
||||
|
||||
/**
|
||||
* Duplicates a variable with a "(copy)" suffix, ensuring name uniqueness
|
||||
*/
|
||||
duplicateVariable: (id: string) => string
|
||||
|
||||
|
||||
loadVariables: (workflowId: string) => Promise<void>
|
||||
saveVariables: (workflowId: string) => Promise<void>
|
||||
|
||||
|
||||
/**
|
||||
* Returns all variables for a specific workflow
|
||||
*/
|
||||
getVariablesByWorkflowId: (workflowId: string) => Variable[]
|
||||
|
||||
|
||||
/**
|
||||
* Resets tracking of loaded workflows
|
||||
*/
|
||||
|
||||
@@ -13,17 +13,17 @@ export function getWorkflowWithValues(workflowId: string) {
|
||||
const { workflows } = useWorkflowRegistry.getState()
|
||||
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
||||
const currentState = useWorkflowStore.getState()
|
||||
|
||||
|
||||
if (!workflows[workflowId]) {
|
||||
logger.warn(`Workflow ${workflowId} not found`)
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
const metadata = workflows[workflowId]
|
||||
|
||||
|
||||
// Load the specific state for this workflow
|
||||
let workflowState: WorkflowState
|
||||
|
||||
|
||||
if (workflowId === activeWorkflowId) {
|
||||
// For the active workflow, use the current state from the store
|
||||
workflowState = {
|
||||
@@ -43,10 +43,10 @@ export function getWorkflowWithValues(workflowId: string) {
|
||||
}
|
||||
workflowState = savedState
|
||||
}
|
||||
|
||||
|
||||
// Merge the subblock values for this specific workflow
|
||||
const mergedBlocks = mergeSubblockState(workflowState.blocks, workflowId)
|
||||
|
||||
|
||||
return {
|
||||
id: workflowId,
|
||||
name: metadata.name,
|
||||
|
||||
@@ -40,12 +40,10 @@ export function getNextWorkflowColor(existingWorkflows: Record<string, WorkflowM
|
||||
|
||||
// Sort workflows by lastModified date (newest first)
|
||||
const sortedWorkflows = [...workflowArray].sort((a, b) => {
|
||||
const dateA = a.lastModified instanceof Date
|
||||
? a.lastModified.getTime()
|
||||
: new Date(a.lastModified).getTime()
|
||||
const dateB = b.lastModified instanceof Date
|
||||
? b.lastModified.getTime()
|
||||
: new Date(b.lastModified).getTime()
|
||||
const dateA =
|
||||
a.lastModified instanceof Date ? a.lastModified.getTime() : new Date(a.lastModified).getTime()
|
||||
const dateB =
|
||||
b.lastModified instanceof Date ? b.lastModified.getTime() : new Date(b.lastModified).getTime()
|
||||
return dateB - dateA
|
||||
})
|
||||
|
||||
@@ -53,7 +51,9 @@ export function getNextWorkflowColor(existingWorkflows: Record<string, WorkflowM
|
||||
const newestWorkflow = sortedWorkflows[0]
|
||||
|
||||
// Find the index of the newest workflow's color, defaulting to -1 if undefined
|
||||
const currentColorIndex = newestWorkflow?.color ? WORKFLOW_COLORS.indexOf(newestWorkflow.color) : -1
|
||||
const currentColorIndex = newestWorkflow?.color
|
||||
? WORKFLOW_COLORS.indexOf(newestWorkflow.color)
|
||||
: -1
|
||||
|
||||
// Get next color index, wrapping around to 0 if we reach the end
|
||||
const nextColorIndex = (currentColorIndex + 1) % WORKFLOW_COLORS.length
|
||||
|
||||
@@ -13,8 +13,8 @@ import { BlockState } from './workflow/types'
|
||||
const logger = createLogger('Workflows Sync')
|
||||
|
||||
// Add debounce utility
|
||||
let syncDebounceTimer: NodeJS.Timeout | null = null;
|
||||
const DEBOUNCE_DELAY = 500; // 500ms delay
|
||||
let syncDebounceTimer: NodeJS.Timeout | null = null
|
||||
const DEBOUNCE_DELAY = 500 // 500ms delay
|
||||
|
||||
// Flag to prevent immediate sync back to DB after loading from DB
|
||||
let isLoadingFromDB = false
|
||||
@@ -54,7 +54,7 @@ export async function fetchWorkflowsFromDB(): Promise<void> {
|
||||
loadingFromDBStartTime = Date.now()
|
||||
|
||||
// Call the API endpoint to get workflows from DB
|
||||
const response = await fetch(API_ENDPOINTS.WORKFLOW, {
|
||||
const response = await fetch(API_ENDPOINTS.SYNC, {
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
@@ -200,13 +200,13 @@ function activeDBSyncNeeded(): boolean {
|
||||
// Only sync if we have detected a change that needs to be persisted
|
||||
const lastSynced = localStorage.getItem('last_db_sync_timestamp')
|
||||
const currentTime = Date.now()
|
||||
|
||||
|
||||
if (!lastSynced) {
|
||||
// First sync - record it and return true
|
||||
localStorage.setItem('last_db_sync_timestamp', currentTime.toString())
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
// Add additional checks here if needed for specific workflow changes
|
||||
// For now, we'll simply avoid the automatic sync after load
|
||||
return false
|
||||
@@ -214,7 +214,7 @@ function activeDBSyncNeeded(): boolean {
|
||||
|
||||
// Create the basic sync configuration
|
||||
const workflowSyncConfig = {
|
||||
endpoint: API_ENDPOINTS.WORKFLOW,
|
||||
endpoint: API_ENDPOINTS.SYNC,
|
||||
preparePayload: () => {
|
||||
if (typeof window === 'undefined') return {}
|
||||
|
||||
@@ -252,10 +252,10 @@ const workflowSyncConfig = {
|
||||
onSyncSuccess: async () => {
|
||||
logger.info('Workflows synced to DB successfully')
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Create the sync manager
|
||||
const baseWorkflowSync = createSingletonSyncManager('workflow-sync', () => workflowSyncConfig);
|
||||
const baseWorkflowSync = createSingletonSyncManager('workflow-sync', () => workflowSyncConfig)
|
||||
|
||||
// Create a debounced version of the sync manager
|
||||
export const workflowSync = {
|
||||
@@ -263,18 +263,18 @@ export const workflowSync = {
|
||||
sync: () => {
|
||||
// Clear any existing timeout
|
||||
if (syncDebounceTimer) {
|
||||
clearTimeout(syncDebounceTimer);
|
||||
clearTimeout(syncDebounceTimer)
|
||||
}
|
||||
|
||||
|
||||
// Set new timeout
|
||||
syncDebounceTimer = setTimeout(() => {
|
||||
// Perform the sync
|
||||
baseWorkflowSync.sync();
|
||||
|
||||
baseWorkflowSync.sync()
|
||||
|
||||
// Update the last sync timestamp
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('last_db_sync_timestamp', Date.now().toString());
|
||||
localStorage.setItem('last_db_sync_timestamp', Date.now().toString())
|
||||
}
|
||||
}, DEBOUNCE_DELAY);
|
||||
}
|
||||
};
|
||||
}, DEBOUNCE_DELAY)
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user