diff --git a/apps/docs/app/[lang]/layout.tsx b/apps/docs/app/[lang]/layout.tsx index dc071493f3..7641682aa2 100644 --- a/apps/docs/app/[lang]/layout.tsx +++ b/apps/docs/app/[lang]/layout.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from 'react' import { defineI18nUI } from 'fumadocs-ui/i18n' import { DocsLayout } from 'fumadocs-ui/layouts/docs' -import { RootProvider } from 'fumadocs-ui/provider' +import { RootProvider } from 'fumadocs-ui/provider/next' import { ExternalLink, GithubIcon } from 'lucide-react' import { Inter } from 'next/font/google' import Image from 'next/image' diff --git a/apps/docs/package.json b/apps/docs/package.json index 3c1d3ac602..12f73d9045 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -15,9 +15,9 @@ "@vercel/analytics": "1.5.0", "@vercel/og": "^0.6.5", "clsx": "^2.1.1", - "fumadocs-core": "^15.7.5", - "fumadocs-mdx": "^11.5.6", - "fumadocs-ui": "^15.7.5", + "fumadocs-core": "15.8.2", + "fumadocs-mdx": "11.10.1", + "fumadocs-ui": "15.8.2", "lucide-react": "^0.511.0", "next": "15.4.1", "next-themes": "^0.4.6", diff --git a/apps/sim/.env.example b/apps/sim/.env.example index 0f3a44bb94..b7808de726 100644 --- a/apps/sim/.env.example +++ b/apps/sim/.env.example @@ -1,5 +1,8 @@ # Database (Required) DATABASE_URL="postgresql://postgres:password@localhost:5432/postgres" +# DATABASE_SSL=disable # Optional: SSL mode (disable, prefer, require, verify-ca, verify-full) +# DATABASE_SSL_CA= # Optional: Base64-encoded CA certificate (required for verify-ca/verify-full) + # To generate: cat your-ca.crt | base64 | tr -d '\n' # PostgreSQL Port (Optional) - defaults to 5432 if not specified # POSTGRES_PORT=5432 diff --git a/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts b/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts index 3cd75ccd0b..d1fdb6d526 100644 --- a/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts +++ b/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts @@ -237,7 +237,6 @@ describe('Copilot Checkpoints Revert API Route', () => { parallels: {}, isDeployed: true, deploymentStatuses: { production: 'deployed' }, - hasActiveWebhook: false, }, } @@ -287,7 +286,6 @@ describe('Copilot Checkpoints Revert API Route', () => { parallels: {}, isDeployed: true, deploymentStatuses: { production: 'deployed' }, - hasActiveWebhook: false, lastSaved: 1640995200000, }, }, @@ -309,7 +307,6 @@ describe('Copilot Checkpoints Revert API Route', () => { parallels: {}, isDeployed: true, deploymentStatuses: { production: 'deployed' }, - hasActiveWebhook: false, lastSaved: 1640995200000, }), } @@ -445,7 +442,6 @@ describe('Copilot Checkpoints Revert API Route', () => { parallels: {}, isDeployed: false, deploymentStatuses: {}, - hasActiveWebhook: false, lastSaved: 1640995200000, }) }) @@ -722,7 +718,6 @@ describe('Copilot Checkpoints Revert API Route', () => { production: 'deployed', staging: 'pending', }, - hasActiveWebhook: true, deployedAt: '2024-01-01T10:00:00.000Z', }, } @@ -769,7 +764,6 @@ describe('Copilot Checkpoints Revert API Route', () => { production: 'deployed', staging: 'pending', }, - hasActiveWebhook: true, deployedAt: '2024-01-01T10:00:00.000Z', lastSaved: 1640995200000, }) diff --git a/apps/sim/app/api/copilot/checkpoints/revert/route.ts b/apps/sim/app/api/copilot/checkpoints/revert/route.ts index a6c70de3fd..c3e5f0fb70 100644 --- a/apps/sim/app/api/copilot/checkpoints/revert/route.ts +++ b/apps/sim/app/api/copilot/checkpoints/revert/route.ts @@ -73,7 +73,6 @@ export async function POST(request: NextRequest) { parallels: checkpointState?.parallels || {}, isDeployed: checkpointState?.isDeployed || false, deploymentStatuses: checkpointState?.deploymentStatuses || {}, - hasActiveWebhook: checkpointState?.hasActiveWebhook || false, lastSaved: Date.now(), // Only include deployedAt if it's a valid date string that can be converted ...(checkpointState?.deployedAt && diff --git a/apps/sim/app/api/copilot/training/examples/route.ts b/apps/sim/app/api/copilot/training/examples/route.ts new file mode 100644 index 0000000000..dd02d56505 --- /dev/null +++ b/apps/sim/app/api/copilot/training/examples/route.ts @@ -0,0 +1,59 @@ +import { type NextRequest, NextResponse } from 'next/server' +import { env } from '@/lib/env' +import { createLogger } from '@/lib/logs/console/logger' + +const logger = createLogger('CopilotTrainingExamplesAPI') + +export const runtime = 'nodejs' +export const dynamic = 'force-dynamic' + +export async function POST(request: NextRequest) { + const baseUrl = env.AGENT_INDEXER_URL + if (!baseUrl) { + logger.error('Missing AGENT_INDEXER_URL environment variable') + return NextResponse.json({ error: 'Missing AGENT_INDEXER_URL env' }, { status: 500 }) + } + + const apiKey = env.AGENT_INDEXER_API_KEY + if (!apiKey) { + logger.error('Missing AGENT_INDEXER_API_KEY environment variable') + return NextResponse.json({ error: 'Missing AGENT_INDEXER_API_KEY env' }, { status: 500 }) + } + + try { + const body = await request.json() + + logger.info('Sending workflow example to agent indexer', { + hasJsonField: typeof body?.json === 'string', + }) + + const upstream = await fetch(`${baseUrl}/examples/add`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-api-key': apiKey, + }, + body: JSON.stringify(body), + }) + + if (!upstream.ok) { + const errorText = await upstream.text() + logger.error('Agent indexer rejected the example', { + status: upstream.status, + error: errorText, + }) + return NextResponse.json({ error: errorText }, { status: upstream.status }) + } + + const data = await upstream.json() + logger.info('Successfully sent workflow example to agent indexer') + + return NextResponse.json(data, { + headers: { 'content-type': 'application/json' }, + }) + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to add example' + logger.error('Failed to send workflow example', { error: err }) + return NextResponse.json({ error: errorMessage }, { status: 502 }) + } +} diff --git a/apps/sim/app/api/logs/route.ts b/apps/sim/app/api/logs/route.ts index f34b0ebe1a..e6e9505826 100644 --- a/apps/sim/app/api/logs/route.ts +++ b/apps/sim/app/api/logs/route.ts @@ -97,7 +97,13 @@ export async function GET(request: NextRequest) { const baseQuery = db .select(selectColumns) .from(workflowExecutionLogs) - .innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id)) + .innerJoin( + workflow, + and( + eq(workflowExecutionLogs.workflowId, workflow.id), + eq(workflow.workspaceId, params.workspaceId) + ) + ) .innerJoin( permissions, and( @@ -107,8 +113,8 @@ export async function GET(request: NextRequest) { ) ) - // Build conditions for the joined query - let conditions: SQL | undefined = eq(workflow.workspaceId, params.workspaceId) + // Build additional conditions for the query + let conditions: SQL | undefined // Filter by level if (params.level && params.level !== 'all') { @@ -180,7 +186,13 @@ export async function GET(request: NextRequest) { const countQuery = db .select({ count: sql`count(*)` }) .from(workflowExecutionLogs) - .innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id)) + .innerJoin( + workflow, + and( + eq(workflowExecutionLogs.workflowId, workflow.id), + eq(workflow.workspaceId, params.workspaceId) + ) + ) .innerJoin( permissions, and( diff --git a/apps/sim/app/api/users/me/settings/route.ts b/apps/sim/app/api/users/me/settings/route.ts index cba8148ad6..309b2176a5 100644 --- a/apps/sim/app/api/users/me/settings/route.ts +++ b/apps/sim/app/api/users/me/settings/route.ts @@ -76,6 +76,8 @@ export async function GET() { telemetryEnabled: userSettings.telemetryEnabled, emailPreferences: userSettings.emailPreferences ?? {}, billingUsageNotificationsEnabled: userSettings.billingUsageNotificationsEnabled ?? true, + showFloatingControls: userSettings.showFloatingControls ?? true, + showTrainingControls: userSettings.showTrainingControls ?? false, }, }, { status: 200 } diff --git a/apps/sim/app/api/v1/logs/route.ts b/apps/sim/app/api/v1/logs/route.ts index 69b95273d7..fb6170a171 100644 --- a/apps/sim/app/api/v1/logs/route.ts +++ b/apps/sim/app/api/v1/logs/route.ts @@ -124,7 +124,13 @@ export async function GET(request: NextRequest) { workflowDescription: workflow.description, }) .from(workflowExecutionLogs) - .innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id)) + .innerJoin( + workflow, + and( + eq(workflowExecutionLogs.workflowId, workflow.id), + eq(workflow.workspaceId, params.workspaceId) + ) + ) .innerJoin( permissions, and( diff --git a/apps/sim/app/api/workflows/[id]/deployments/[version]/revert/route.ts b/apps/sim/app/api/workflows/[id]/deployments/[version]/revert/route.ts index 41434061a7..01c0e0705b 100644 --- a/apps/sim/app/api/workflows/[id]/deployments/[version]/revert/route.ts +++ b/apps/sim/app/api/workflows/[id]/deployments/[version]/revert/route.ts @@ -76,7 +76,6 @@ export async function POST( isDeployed: true, deployedAt: new Date(), deploymentStatuses: deployedState.deploymentStatuses || {}, - hasActiveWebhook: deployedState.hasActiveWebhook || false, }) if (!saveResult.success) { diff --git a/apps/sim/app/api/workflows/[id]/route.ts b/apps/sim/app/api/workflows/[id]/route.ts index 26e92cef98..aa7b31d9ba 100644 --- a/apps/sim/app/api/workflows/[id]/route.ts +++ b/apps/sim/app/api/workflows/[id]/route.ts @@ -133,7 +133,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ state: { // Default values for expected properties deploymentStatuses: {}, - hasActiveWebhook: false, // Data from normalized tables blocks: normalizedData.blocks, edges: normalizedData.edges, diff --git a/apps/sim/app/api/workflows/[id]/state/route.ts b/apps/sim/app/api/workflows/[id]/state/route.ts index 3d593339d9..c8509bb900 100644 --- a/apps/sim/app/api/workflows/[id]/state/route.ts +++ b/apps/sim/app/api/workflows/[id]/state/route.ts @@ -89,13 +89,6 @@ const ParallelSchema = z.object({ parallelType: z.enum(['count', 'collection']).optional(), }) -const DeploymentStatusSchema = z.object({ - id: z.string(), - status: z.enum(['deploying', 'deployed', 'failed', 'stopping', 'stopped']), - deployedAt: z.date().optional(), - error: z.string().optional(), -}) - const WorkflowStateSchema = z.object({ blocks: z.record(BlockStateSchema), edges: z.array(EdgeSchema), @@ -103,9 +96,7 @@ const WorkflowStateSchema = z.object({ parallels: z.record(ParallelSchema).optional(), lastSaved: z.number().optional(), isDeployed: z.boolean().optional(), - deployedAt: z.date().optional(), - deploymentStatuses: z.record(DeploymentStatusSchema).optional(), - hasActiveWebhook: z.boolean().optional(), + deployedAt: z.coerce.date().optional(), }) /** @@ -204,8 +195,6 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{ lastSaved: state.lastSaved || Date.now(), isDeployed: state.isDeployed || false, deployedAt: state.deployedAt, - deploymentStatuses: state.deploymentStatuses || {}, - hasActiveWebhook: state.hasActiveWebhook || false, } const saveResult = await saveWorkflowToNormalizedTables(workflowId, workflowState as any) diff --git a/apps/sim/app/api/workflows/yaml/export/route.ts b/apps/sim/app/api/workflows/yaml/export/route.ts index 198b8203d4..284da1bf6e 100644 --- a/apps/sim/app/api/workflows/yaml/export/route.ts +++ b/apps/sim/app/api/workflows/yaml/export/route.ts @@ -89,7 +89,6 @@ export async function GET(request: NextRequest) { // Use normalized table data - construct state from normalized tables workflowState = { deploymentStatuses: {}, - hasActiveWebhook: false, blocks: normalizedData.blocks, edges: normalizedData.edges, loops: normalizedData.loops, diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/diff-controls.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/diff-controls.tsx index b7b8a55e59..db334379c3 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/diff-controls.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/diff-controls.tsx @@ -86,7 +86,6 @@ export function DiffControls() { lastSaved: rawState.lastSaved || Date.now(), isDeployed: rawState.isDeployed || false, deploymentStatuses: rawState.deploymentStatuses || {}, - hasActiveWebhook: rawState.hasActiveWebhook || false, // Only include deployedAt if it's a valid date, never include null/undefined ...(rawState.deployedAt && rawState.deployedAt instanceof Date ? { deployedAt: rawState.deployedAt } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/training-controls/training-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/training-controls/training-modal.tsx index 7cf75903d9..89105b1930 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/training-controls/training-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/training-controls/training-modal.tsx @@ -30,6 +30,7 @@ import { Textarea } from '@/components/ui/textarea' import { cn } from '@/lib/utils' import { sanitizeForCopilot } from '@/lib/workflows/json-sanitizer' import { formatEditSequence } from '@/lib/workflows/training/compute-edit-sequence' +import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow' import { useCopilotTrainingStore } from '@/stores/copilot-training/store' /** @@ -52,6 +53,8 @@ export function TrainingModal() { markDatasetSent, } = useCopilotTrainingStore() + const currentWorkflow = useCurrentWorkflow() + const [localPrompt, setLocalPrompt] = useState(currentPrompt) const [localTitle, setLocalTitle] = useState(currentTitle) const [copiedId, setCopiedId] = useState(null) @@ -63,6 +66,11 @@ export function TrainingModal() { const [sendingSelected, setSendingSelected] = useState(false) const [sentDatasets, setSentDatasets] = useState>(new Set()) const [failedDatasets, setFailedDatasets] = useState>(new Set()) + const [sendingLiveWorkflow, setSendingLiveWorkflow] = useState(false) + const [liveWorkflowSent, setLiveWorkflowSent] = useState(false) + const [liveWorkflowFailed, setLiveWorkflowFailed] = useState(false) + const [liveWorkflowTitle, setLiveWorkflowTitle] = useState('') + const [liveWorkflowDescription, setLiveWorkflowDescription] = useState('') const handleStart = () => { if (localTitle.trim() && localPrompt.trim()) { @@ -285,6 +293,46 @@ export function TrainingModal() { } } + const handleSendLiveWorkflow = async () => { + if (!liveWorkflowTitle.trim() || !liveWorkflowDescription.trim()) { + return + } + + setLiveWorkflowSent(false) + setLiveWorkflowFailed(false) + setSendingLiveWorkflow(true) + + try { + const sanitizedWorkflow = sanitizeForCopilot(currentWorkflow.workflowState) + + const response = await fetch('/api/copilot/training/examples', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + json: JSON.stringify(sanitizedWorkflow), + source_path: liveWorkflowTitle, + summary: liveWorkflowDescription, + }), + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(error.error || 'Failed to send live workflow') + } + + setLiveWorkflowSent(true) + setLiveWorkflowTitle('') + setLiveWorkflowDescription('') + setTimeout(() => setLiveWorkflowSent(false), 5000) + } catch (error) { + console.error('Failed to send live workflow:', error) + setLiveWorkflowFailed(true) + setTimeout(() => setLiveWorkflowFailed(false), 5000) + } finally { + setSendingLiveWorkflow(false) + } + } + return ( @@ -335,24 +383,24 @@ export function TrainingModal() { )} - + New Session Datasets ({datasets.length}) + Send Live State {/* New Training Session Tab */} - {startSnapshot && ( -
-

Current Workflow State

-

- {Object.keys(startSnapshot.blocks).length} blocks, {startSnapshot.edges.length}{' '} - edges -

-
- )} +
+

+ Current Workflow State +

+

+ {currentWorkflow.getBlockCount()} blocks, {currentWorkflow.getEdgeCount()} edges +

+
@@ -628,6 +676,94 @@ export function TrainingModal() { )} + + {/* Send Live State Tab */} + +
+

+ Current Workflow State +

+

+ {currentWorkflow.getBlockCount()} blocks, {currentWorkflow.getEdgeCount()} edges +

+
+ +
+ + setLiveWorkflowTitle(e.target.value)} + /> +

+ A short title identifying this workflow +

+
+ +
+ +