chore: updated prettier config and ran format, updated README

This commit is contained in:
Waleed Latif
2025-03-17 12:25:01 -07:00
parent 21105363e4
commit b41982558d
74 changed files with 446 additions and 161 deletions
-22
View File
@@ -1,22 +0,0 @@
module.exports = {
semi: false,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
printWidth: 100,
bracketSpacing: true,
arrowParens: 'always',
endOfLine: 'lf',
plugins: ['prettier-plugin-tailwindcss', '@trivago/prettier-plugin-sort-imports'],
importOrder: [
'^(react/(.*)$)|^(react$)',
'^(next/(.*)$)|^(next$)',
'<THIRD_PARTY_MODULES>',
'^@/components/(.*)$',
'^@/lib/(.*)$',
'^@/stores/(.*)$',
'^@/(.*)$',
'^[./]',
],
importOrderSortSpecifiers: true,
}
+7 -8
View File
@@ -2,14 +2,13 @@
<img src="sim/public/sim.png" alt="Sim Studio Logo" width="500"/>
</p>
# Sim Studio
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Discord](https://img.shields.io/badge/Discord-Join%20Server-7289DA?logo=discord&logoColor=white)](https://discord.gg/pQKwMTvNrg)
[![Twitter](https://img.shields.io/twitter/follow/simstudio?style=social)](https://x.com/simstudioai)
[![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/simstudioai/sim/pulls)
[![support](https://img.shields.io/badge/support-contact%20author-purple.svg)](https://github.com/simstudioai/sim/issues)
<p align="center">
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
<a href="https://discord.gg/pQKwMTvNrg"><img src="https://img.shields.io/badge/Discord-Join%20Server-7289DA?logo=discord&logoColor=white" alt="Discord"></a>
<a href="https://x.com/simstudioai"><img src="https://img.shields.io/twitter/follow/simstudio?style=social" alt="Twitter"></a>
<a href="https://github.com/simstudioai/sim/pulls"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs welcome"></a>
<a href="https://github.com/simstudioai/sim/issues"><img src="https://img.shields.io/badge/support-contact%20author-purple.svg" alt="support"></a>
</p>
**Sim Studio** is a powerful, user-friendly platform for building, testing, and optimizing agentic workflows.
+1 -1
View File
@@ -34,4 +34,4 @@ coverage
**/public/sw.js
**/public/workbox-*.js
**/public/worker-*.js
**/public/fallback-*.js
**/public/fallback-*.js
+36
View File
@@ -0,0 +1,36 @@
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf",
"useTabs": false,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"bracketSameLine": false,
"htmlWhitespaceSensitivity": "css",
"vueIndentScriptAndStyle": false,
"embeddedLanguageFormatting": "auto",
"singleAttributePerLine": false,
"plugins": ["prettier-plugin-tailwindcss", "@trivago/prettier-plugin-sort-imports"],
"importOrder": [
"^(react/(.*)$)|^(react$)",
"^(next/(.*)$)|^(next$)",
"<THIRD_PARTY_MODULES>",
"^@/components/(.*)$",
"^@/lib/(.*)$",
"^@/stores/(.*)$",
"^@/hooks/(.*)$",
"^@/utils/(.*)$",
"^@/types/(.*)$",
"^@/styles/(.*)$",
"^@/(.*)$",
"^[./]"
],
"importOrderSortSpecifiers": true,
"importOrderCaseInsensitive": true,
"importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"]
}
+5 -1
View File
@@ -33,7 +33,11 @@ export default function LoginPage() {
const password = formData.get('password') as string
try {
const result = await client.signIn.email({ email, password, callbackURL: '/w' })
const result = await client.signIn.email({
email,
password,
callbackURL: '/w',
})
if (!result || result.error) {
throw new Error(result?.error?.message || 'Authentication failed')
+3 -1
View File
@@ -67,7 +67,9 @@ export async function GET(request: NextRequest) {
displayName = decoded.name
}
} catch (error) {
logger.warn(`[${requestId}] Error decoding ID token`, { accountId: acc.id })
logger.warn(`[${requestId}] Error decoding ID token`, {
accountId: acc.id,
})
}
}
+6 -2
View File
@@ -69,7 +69,9 @@ export async function GET(request: NextRequest) {
displayName = decoded.name
}
} catch (error) {
logger.warn(`[${requestId}] Error decoding ID token`, { accountId: acc.id })
logger.warn(`[${requestId}] Error decoding ID token`, {
accountId: acc.id,
})
}
}
@@ -91,7 +93,9 @@ export async function GET(request: NextRequest) {
displayName = userRecord[0].email
}
} catch (error) {
logger.warn(`[${requestId}] Error fetching user email`, { userId: acc.userId })
logger.warn(`[${requestId}] Error fetching user email`, {
userId: acc.userId,
})
}
}
+3 -1
View File
@@ -121,7 +121,9 @@ export async function GET(request: NextRequest) {
fileId,
})
return NextResponse.json(
{ error: error.error?.message || 'Failed to fetch file from Google Drive' },
{
error: error.error?.message || 'Failed to fetch file from Google Drive',
},
{ status: response.status }
)
}
+6 -2
View File
@@ -57,7 +57,9 @@ export async function GET(request: NextRequest) {
// Check if the access token is valid
if (!credential.accessToken) {
logger.warn(`[${requestId}] No access token available for credential`, { credentialId })
logger.warn(`[${requestId}] No access token available for credential`, {
credentialId,
})
return NextResponse.json({ error: 'No access token available' }, { status: 400 })
}
@@ -140,7 +142,9 @@ export async function GET(request: NextRequest) {
error: error.error?.message || 'Failed to fetch files from Google Drive',
})
return NextResponse.json(
{ error: error.error?.message || 'Failed to fetch files from Google Drive' },
{
error: error.error?.message || 'Failed to fetch files from Google Drive',
},
{ status: response.status }
)
}
+3 -1
View File
@@ -180,7 +180,9 @@ export async function POST(request: Request) {
// Process tool calls if present
if (message.tool_calls) {
logger.debug(`[${requestId}] Tool calls:`, { toolCalls: message.tool_calls })
logger.debug(`[${requestId}] Tool calls:`, {
toolCalls: message.tool_calls,
})
const actions = message.tool_calls.map((call) => ({
name: call.function.name,
parameters: JSON.parse(call.function.arguments),
+3 -1
View File
@@ -83,7 +83,9 @@ export async function GET(request: Request) {
)
}
const generalSettings = result[0].general as { isAutoConnectEnabled: boolean }
const generalSettings = result[0].general as {
isAutoConnectEnabled: boolean
}
return NextResponse.json(
{
data: {
+5 -2
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
import { SQL, and, eq, gte, lte, or, sql } from 'drizzle-orm'
import { and, eq, gte, lte, or, SQL, sql } from 'drizzle-orm'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
import { createLogger } from '@/lib/logs/console-logger'
@@ -166,7 +166,10 @@ export async function GET(request: NextRequest) {
errors: validationError.errors,
})
return NextResponse.json(
{ error: 'Invalid request parameters', details: validationError.errors },
{
error: 'Invalid request parameters',
details: validationError.errors,
},
{ status: 400 }
)
}
+11 -6
View File
@@ -79,14 +79,19 @@ export async function POST(req: NextRequest) {
.select()
.from(workflow)
.where(eq(workflow.userId, session.user.id))
// If user has existing workflows, but client sends empty, reject the sync
if (existingWorkflows.length > 0) {
logger.warn(`[${requestId}] Prevented data loss: Client attempted to sync empty workflows while DB has ${existingWorkflows.length} workflows`)
return NextResponse.json({
error: 'Sync rejected to prevent data loss',
message: 'Client sent empty workflows, but user has existing workflows in database'
}, { status: 409 })
logger.warn(
`[${requestId}] Prevented data loss: Client attempted to sync empty workflows while DB has ${existingWorkflows.length} workflows`
)
return NextResponse.json(
{
error: 'Sync rejected to prevent data loss',
message: 'Client sent empty workflows, but user has existing workflows in database',
},
{ status: 409 }
)
}
}
+4 -2
View File
@@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from 'next/server'
import { Script, createContext } from 'vm'
import { createContext, Script } from 'vm'
import { createLogger } from '@/lib/logs/console-logger'
// Explicitly export allowed methods
@@ -101,7 +101,9 @@ export async function POST(req: NextRequest) {
})
const executionTime = Date.now() - startTime
logger.info(`[${requestId}] Function executed successfully`, { executionTime })
logger.info(`[${requestId}] Function executed successfully`, {
executionTime,
})
const response = {
success: true,
+4 -1
View File
@@ -104,7 +104,10 @@ export async function POST(request: NextRequest) {
requestedWorkflowId: workflowId,
})
return NextResponse.json(
{ error: 'Webhook path already exists. Please use a different path.', code: 'PATH_EXISTS' },
{
error: 'Webhook path already exists. Please use a different path.',
code: 'PATH_EXISTS',
},
{ status: 409 }
)
}
+9 -3
View File
@@ -97,7 +97,9 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
return new NextResponse('OK', { status: 200 })
} catch (error: any) {
logger.error(`[${requestId}] Error processing webhook verification`, error)
return new NextResponse(`Internal Server Error: ${error.message}`, { status: 500 })
return new NextResponse(`Internal Server Error: ${error.message}`, {
status: 500,
})
} finally {
// Ensure Redis connection is properly closed in serverless environment
await closeRedisConnection()
@@ -214,7 +216,9 @@ export async function POST(
await persistExecutionError(foundWorkflow.id, executionId, error, 'webhook')
}
return new NextResponse(`Internal Server Error: ${error.message}`, { status: 500 })
return new NextResponse(`Internal Server Error: ${error.message}`, {
status: 500,
})
} finally {
// Ensure Redis connection is properly closed in serverless environment
await closeRedisConnection()
@@ -519,6 +523,8 @@ async function processWebhook(
await persistExecutionError(foundWorkflow.id, executionId, error, 'webhook')
}
return new NextResponse(`Internal Server Error: ${error.message}`, { status: 500 })
return new NextResponse(`Internal Server Error: ${error.message}`, {
status: 500,
})
}
}
+5 -1
View File
@@ -74,7 +74,11 @@ export async function DELETE(
.where(eq(workflow.id, id))
logger.info(`[${requestId}] Workflow undeployed successfully: ${id}`)
return createSuccessResponse({ isDeployed: false, deployedAt: null, apiKey: null })
return createSuccessResponse({
isDeployed: false,
deployedAt: null,
apiKey: null,
})
} catch (error: any) {
logger.error(`[${requestId}] Error undeploying workflow: ${id}`, error)
return createErrorResponse(error.message || 'Failed to undeploy workflow', 500)
+3 -1
View File
@@ -33,7 +33,9 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
// Use persistExecutionLogs which handles tool call extraction
await persistExecutionLogs(id, executionId, result, 'manual')
return createSuccessResponse({ message: 'Execution logs persisted successfully' })
return createSuccessResponse({
message: 'Execution logs persisted successfully',
})
}
// Fall back to the original log format if 'result' isn't provided
+4 -1
View File
@@ -36,7 +36,10 @@ export const PineconeBlock: BlockConfig<PineconeResponse> = {
options: [
{ label: 'multilingual-e5-large', id: 'multilingual-e5-large' },
{ label: 'llama-text-embed-v2', id: 'llama-text-embed-v2' },
{ label: 'pinecone-sparse-english-v0', id: 'pinecone-sparse-english-v0' },
{
label: 'pinecone-sparse-english-v0',
id: 'pinecone-sparse-english-v0',
},
],
condition: { field: 'operation', value: 'generate' },
},
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from 'react'
import { type VariantProps, cva } from 'class-variance-authority'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const alertVariants = cva(
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from 'react'
import { type VariantProps, cva } from 'class-variance-authority'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const badgeVariants = cva(
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { type VariantProps, cva } from 'class-variance-authority'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const buttonVariants = cva(
+24
View File
@@ -12,6 +12,30 @@ 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
// 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
// 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> & {
+3 -1
View File
@@ -30,7 +30,9 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & { hideCloseButton?: boolean }
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
hideCloseButton?: boolean
}
>(({ className, children, hideCloseButton = false, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
+1 -1
View File
@@ -2,7 +2,7 @@
import * as React from 'react'
import * as LabelPrimitive from '@radix-ui/react-label'
import { type VariantProps, cva } from 'class-variance-authority'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const labelVariants = cva(
+1 -1
View File
@@ -2,7 +2,7 @@
import * as React from 'react'
import * as SheetPrimitive from '@radix-ui/react-dialog'
import { type VariantProps, cva } from 'class-variance-authority'
import { cva, type VariantProps } from 'class-variance-authority'
import { X } from 'lucide-react'
import { cn } from '@/lib/utils'
+6 -2
View File
@@ -34,8 +34,12 @@ export const account = pgTable(
accessToken: text('access_token'),
refreshToken: text('refresh_token'),
idToken: text('id_token'),
accessTokenExpiresAt: timestamp('access_token_expires_at', { mode: 'string' }),
refreshTokenExpiresAt: timestamp('refresh_token_expires_at', { mode: 'string' }),
accessTokenExpiresAt: timestamp('access_token_expires_at', {
mode: 'string',
}),
refreshTokenExpiresAt: timestamp('refresh_token_expires_at', {
mode: 'string',
}),
scope: text(),
password: text(),
createdAt: timestamp('created_at', { mode: 'string' }).notNull(),
@@ -25,7 +25,9 @@ export class TestExecutor extends Executor {
// Return a successful result
return {
success: true,
output: { response: { result: 'Test execution completed' } } as NormalizedBlockOutput,
output: {
response: { result: 'Test execution completed' },
} as NormalizedBlockOutput,
logs: [],
metadata: {
duration: 100,
+10 -2
View File
@@ -489,7 +489,11 @@ export class Executor {
content: '',
model: '',
tokens: { prompt: 0, completion: 0, total: 0 },
selectedPath: output?.selectedPath || { blockId: '', blockType: '', blockTitle: '' },
selectedPath: output?.selectedPath || {
blockId: '',
blockType: '',
blockTitle: '',
},
},
}
}
@@ -513,7 +517,11 @@ export class Executor {
return {
response: {
conditionResult: output?.conditionResult || false,
selectedPath: output?.selectedPath || { blockId: '', blockType: '', blockTitle: '' },
selectedPath: output?.selectedPath || {
blockId: '',
blockType: '',
blockTitle: '',
},
selectedConditionId: output?.selectedConditionId || '',
},
}
+5 -1
View File
@@ -14,7 +14,11 @@ const logger = createLogger('Auth')
// In that case, we don't want to send emails and just log them
const resend = process.env.RESEND_API_KEY
? new Resend(process.env.RESEND_API_KEY)
: { emails: { send: async (...args: any[]) => logger.info('Email sent:', args) } }
: {
emails: {
send: async (...args: any[]) => logger.info('Email sent:', args),
},
}
export const auth = betterAuth({
database: drizzleAdapter(db, {
+12 -4
View File
@@ -76,7 +76,9 @@ export async function persistExecutionLogs(
// Case 1: Direct toolCalls array
if (Array.isArray(log.output.toolCalls)) {
logger.debug('Found direct toolCalls array', { count: log.output.toolCalls.length })
logger.debug('Found direct toolCalls array', {
count: log.output.toolCalls.length,
})
// Log raw timing data for debugging
log.output.toolCalls.forEach((tc: any, idx: number) => {
@@ -172,7 +174,9 @@ export async function persistExecutionLogs(
? log.output.response.toolCalls
: log.output.response.toolCalls.list || []
logger.debug('Found toolCalls in response', { count: toolCalls.length })
logger.debug('Found toolCalls in response', {
count: toolCalls.length,
})
// Log raw timing data for debugging
toolCalls.forEach((tc: any, idx: number) => {
@@ -409,7 +413,9 @@ export async function persistExecutionLogs(
},
})
} catch (error: any) {
logger.error(`Error persisting execution logs: ${error.message}`, { error })
logger.error(`Error persisting execution logs: ${error.message}`, {
error,
})
}
}
@@ -440,7 +446,9 @@ export async function persistExecutionError(
createdAt: new Date(),
})
} catch (logError: any) {
logger.error(`Error persisting execution error log: ${logError.message}`, { logError })
logger.error(`Error persisting execution error log: ${logError.message}`, {
logError,
})
}
}
+4 -1
View File
@@ -342,7 +342,10 @@ export async function refreshOAuthToken(
if (!response.ok) {
const errorText = await response.text()
logger.error('Token refresh failed:', { status: response.status, error: errorText })
logger.error('Token refresh failed:', {
status: response.status,
error: errorText,
})
throw new Error(`Failed to refresh token: ${response.status} ${errorText}`)
}
+3 -1
View File
@@ -337,7 +337,9 @@ export async function executeCode(
try {
await process.kill()
} catch (killError) {
logger.error('WebContainer executeCode - Failed to kill process:', { killError })
logger.error('WebContainer executeCode - Failed to kill process:', {
killError,
})
}
}
+4 -1
View File
@@ -419,7 +419,10 @@ ${fieldDescriptions}
const providerEndTimeISO = new Date(providerEndTime).toISOString()
const totalDuration = providerEndTime - providerStartTime
logger.error('Error in Anthropic request:', { error, duration: totalDuration })
logger.error('Error in Anthropic request:', {
error,
duration: totalDuration,
})
// Create a new error with timing information
const enhancedError = new Error(error instanceof Error ? error.message : String(error))
+4 -1
View File
@@ -344,7 +344,10 @@ export const cerebrasProvider: ProviderConfig = {
const providerEndTimeISO = new Date(providerEndTime).toISOString()
const totalDuration = providerEndTime - providerStartTime
logger.error('Error in Cerebras request:', { error, duration: totalDuration })
logger.error('Error in Cerebras request:', {
error,
duration: totalDuration,
})
// Create a new error with timing information
const enhancedError = new Error(error instanceof Error ? error.message : String(error))
+4 -1
View File
@@ -277,7 +277,10 @@ export const deepseekProvider: ProviderConfig = {
const providerEndTimeISO = new Date(providerEndTime).toISOString()
const totalDuration = providerEndTime - providerStartTime
logger.error('Error in Deepseek request:', { error, duration: totalDuration })
logger.error('Error in Deepseek request:', {
error,
duration: totalDuration,
})
// Create a new error with timing information
const enhancedError = new Error(error instanceof Error ? error.message : String(error))
+4 -1
View File
@@ -275,7 +275,10 @@ export const googleProvider: ProviderConfig = {
const providerEndTimeISO = new Date(providerEndTime).toISOString()
const totalDuration = providerEndTime - providerStartTime
logger.error('Error in Google Gemini request:', { error, duration: totalDuration })
logger.error('Error in Google Gemini request:', {
error,
duration: totalDuration,
})
// Create a new error with timing information
const enhancedError = new Error(error instanceof Error ? error.message : String(error))
+4 -1
View File
@@ -272,7 +272,10 @@ export const groqProvider: ProviderConfig = {
const providerEndTimeISO = new Date(providerEndTime).toISOString()
const totalDuration = providerEndTime - providerStartTime
logger.error('Error in Groq request:', { error, duration: totalDuration })
logger.error('Error in Groq request:', {
error,
duration: totalDuration,
})
// Create a new error with timing information
const enhancedError = new Error(error instanceof Error ? error.message : String(error))
+4 -1
View File
@@ -298,7 +298,10 @@ export const openaiProvider: ProviderConfig = {
const providerEndTimeISO = new Date(providerEndTime).toISOString()
const totalDuration = providerEndTime - providerStartTime
logger.error('Error in OpenAI request:', { error, duration: totalDuration })
logger.error('Error in OpenAI request:', {
error,
duration: totalDuration,
})
// Create a new error with timing information
const enhancedError = new Error(error instanceof Error ? error.message : String(error))
+3 -1
View File
@@ -137,7 +137,9 @@ export const useChatStore = create<ChatStore>()(
}
} catch (error) {
logger.error('Chat error:', { error })
set({ error: error instanceof Error ? error.message : 'Unknown error' })
set({
error: error instanceof Error ? error.message : 'Unknown error',
})
} finally {
set({ isProcessing: false })
}
+11 -7
View File
@@ -52,7 +52,7 @@ async function initializeApplication(): Promise<void> {
// This is a fallback in case DB sync failed or there's no data in DB
const registryState = useWorkflowRegistry.getState()
const hasDbWorkflows = Object.keys(registryState.workflows).length > 0
if (!hasDbWorkflows) {
// No workflows loaded from DB, try localStorage as fallback
const workflows = loadRegistry()
@@ -65,11 +65,11 @@ async function initializeApplication(): Promise<void> {
initializeWorkflowState(activeWorkflowId)
}
} else if (isNewLoginSession) {
// Critical safeguard: For new login sessions with no DB workflows
// Critical safeguard: For new login sessions with no DB workflows
// and no localStorage, we disable sync temporarily to prevent data loss
logger.info('New login session with no workflows - preventing initial sync')
const syncManagers = getSyncManagers()
syncManagers.forEach(manager => manager.stopIntervalSync())
syncManagers.forEach((manager) => manager.stopIntervalSync())
}
} else {
logger.info('Using workflows loaded from DB, ignoring localStorage')
@@ -242,7 +242,11 @@ export const resetAllStores = () => {
useWorkflowStore.getState().clear()
useSubBlockStore.getState().clear()
useNotificationStore.setState({ notifications: [] })
useEnvironmentStore.setState({ variables: {}, isLoading: false, error: null })
useEnvironmentStore.setState({
variables: {},
isLoading: false,
error: null,
})
useExecutionStore.getState().reset()
useConsoleStore.setState({ entries: [], isOpen: false })
useChatStore.setState({ messages: [], isProcessing: false, error: null })
@@ -279,13 +283,13 @@ export async function reinitializeAfterLogin(): Promise<void> {
try {
// Reset sync managers to prevent any active syncs during reinitialization
resetSyncManagers()
// Clean existing state to avoid stale data
resetAllStores()
// Mark as a new login session
sessionStorage.removeItem('app_initialized')
// Reset initialization flags to force a fresh load
isInitializing = false
+3 -1
View File
@@ -23,7 +23,9 @@ export const useGeneralStore = create<GeneralStore>()(
isDebugModeEnabled: false,
theme: 'system',
toggleAutoConnect: () =>
set((state) => ({ isAutoConnectEnabled: !state.isAutoConnectEnabled })),
set((state) => ({
isAutoConnectEnabled: !state.isAutoConnectEnabled,
})),
toggleDebugMode: () => set((state) => ({ isDebugModeEnabled: !state.isDebugModeEnabled })),
setTheme: (theme: 'system' | 'light' | 'dark') => set({ theme }),
}),
+4 -1
View File
@@ -58,7 +58,10 @@ export async function performSync(config: SyncConfig): Promise<boolean> {
if (isLocalStorageMode()) {
// Still call onSyncSuccess to maintain expected behavior
if (config.onSyncSuccess) {
config.onSyncSuccess({ success: true, message: 'Skipped sync in localStorage mode' })
config.onSyncSuccess({
success: true,
message: 'Skipped sync in localStorage mode',
})
}
return true
}
+2 -2
View File
@@ -5,10 +5,10 @@ import { createLogger } from '@/lib/logs/console-logger'
import { SYNC_INTERVALS } from './constants'
import {
DEFAULT_SYNC_CONFIG,
SyncConfig,
SyncOperations,
isLocalStorageMode,
performSync,
SyncConfig,
SyncOperations,
} from './sync-core'
const logger = createLogger('Sync')
+7 -1
View File
@@ -338,7 +338,13 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
history: history || {
past: [],
present: {
state: { blocks, edges, loops, isDeployed: isDeployed || false, deployedAt },
state: {
blocks,
edges,
loops,
isDeployed: isDeployed || false,
deployedAt,
},
timestamp: Date.now(),
action: 'Initial state',
subblockValues: {},
+11 -7
View File
@@ -28,14 +28,14 @@ const scheduledWorkflows = new Set<string>()
*/
export function isActivelyLoadingFromDB(): boolean {
if (!loadingFromDBToken) return false
// Safety check: ensure loading doesn't block syncs indefinitely
const elapsedTime = Date.now() - loadingFromDBStartTime
if (elapsedTime > LOADING_TIMEOUT) {
loadingFromDBToken = null
return false
}
return true
}
@@ -94,7 +94,9 @@ async function updateWorkflowSchedule(workflowId: string, state: any): Promise<v
logger.info(`Schedule cancelled for workflow ${workflowId}:`, result)
}
} catch (error) {
logger.error(`Error managing schedule for workflow ${workflowId}:`, { error })
logger.error(`Error managing schedule for workflow ${workflowId}:`, {
error,
})
}
}
@@ -238,12 +240,12 @@ export async function fetchWorkflowsFromDB(): Promise<void> {
setTimeout(() => {
isLoadingFromDB = false
loadingFromDBToken = null
// Verify if registry has workflows as a final check
const registryWorkflows = useWorkflowRegistry.getState().workflows
const workflowCount = Object.keys(registryWorkflows).length
logger.info(`DB loading complete. Workflows in registry: ${workflowCount}`)
// Trigger one final sync to ensure consistency
if (workflowCount > 0) {
// Small delay for state to fully settle before allowing syncs
@@ -275,10 +277,12 @@ export const workflowSync = createSingletonSyncManager('workflow-sync', () => ({
// Safety check: if registry has workflows but we're sending empty data, something is wrong
const registryWorkflows = useWorkflowRegistry.getState().workflows
if (Object.keys(registryWorkflows).length > 0) {
logger.warn('Potential data loss prevented: Registry has workflows but sync payload is empty')
logger.warn(
'Potential data loss prevented: Registry has workflows but sync payload is empty'
)
return { skipSync: true }
}
logger.info('Skipping workflow sync - no workflows to sync')
return { skipSync: true }
}
+1 -1
View File
@@ -3,7 +3,7 @@ import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
import { getBlock } from '@/blocks'
import { resolveOutputType } from '@/blocks/utils'
import { WorkflowStoreWithHistory, pushHistory, withHistory } from '../middleware'
import { pushHistory, withHistory, WorkflowStoreWithHistory } from '../middleware'
import { saveWorkflowState } from '../persistence'
import { useWorkflowRegistry } from '../registry/store'
import { useSubBlockStore } from '../subblock/store'
+3 -1
View File
@@ -125,7 +125,9 @@ export const createTool: ToolConfig<GoogleDocsToolParams, GoogleDocsCreateRespon
},
}
} catch (error) {
logger.error('Google Docs create - Error processing response:', { error })
logger.error('Google Docs create - Error processing response:', {
error,
})
throw error
}
},
+15 -4
View File
@@ -351,7 +351,9 @@ export async function executeTool(
},
}
} catch (error) {
logger.error(`Error in post-processing for tool ${toolId}:`, { error })
logger.error(`Error in post-processing for tool ${toolId}:`, {
error,
})
// Return original result if post-processing fails
// Still include timing data
const endTime = new Date()
@@ -438,7 +440,10 @@ export async function executeTool(
if (toolId.startsWith('custom_')) {
const identifier = toolId.replace('custom_', '')
const allTools = useCustomToolsStore.getState().getAllTools()
const availableTools = allTools.map((t) => ({ id: t.id, title: t.title }))
const availableTools = allTools.map((t) => ({
id: t.id,
title: t.title,
}))
logger.error('Available custom tools:', availableTools)
logger.error(`Looking for custom tool with identifier: ${identifier}`)
@@ -570,7 +575,9 @@ async function handleInternalRequest(
}
}
} catch (transformError) {
logger.error(`Error transforming error for tool ${toolId}:`, { transformError })
logger.error(`Error transforming error for tool ${toolId}:`, {
transformError,
})
return {
success: false,
output: {},
@@ -592,7 +599,11 @@ async function handleInternalRequest(
*/
function validateClientSideParams(
params: Record<string, any>,
schema: { type: string; properties: Record<string, any>; required?: string[] }
schema: {
type: string
properties: Record<string, any>
required?: string[]
}
) {
if (!schema || schema.type !== 'object') {
throw new Error('Invalid schema format')
+10 -2
View File
@@ -16,7 +16,11 @@ export const embeddingsTool: ToolConfig<OpenAIEmbeddingsParams> = {
params: {
apiKey: { type: 'string', required: true, description: 'OpenAI API key' },
input: { type: 'string', required: true, description: 'Text to generate embeddings for' },
input: {
type: 'string',
required: true,
description: 'Text to generate embeddings for',
},
model: {
type: 'string',
required: false,
@@ -29,7 +33,11 @@ export const embeddingsTool: ToolConfig<OpenAIEmbeddingsParams> = {
description: 'The format to return the embeddings in',
default: 'float',
},
user: { type: 'string', required: false, description: 'A unique identifier for the end-user' },
user: {
type: 'string',
required: false,
description: 'A unique identifier for the end-user',
},
},
request: {
+5 -1
View File
@@ -26,7 +26,11 @@ export const fetchTool: ToolConfig<PineconeFetchParams, PineconeResponse> = {
requiredForToolCall: true,
description: 'Array of vector IDs to fetch',
},
namespace: { type: 'string', required: false, description: 'Namespace to fetch vectors from' },
namespace: {
type: 'string',
required: false,
description: 'Namespace to fetch vectors from',
},
},
request: {
+30 -6
View File
@@ -20,12 +20,36 @@ export const searchTextTool: ToolConfig<PineconeSearchTextParams, PineconeRespon
requiredForToolCall: true,
description: 'Full Pinecone index host URL',
},
namespace: { type: 'string', required: false, description: 'Namespace to search in' },
searchQuery: { type: 'string', required: true, description: 'Text to search for' },
topK: { type: 'string', required: false, description: 'Number of results to return' },
fields: { type: 'array', required: false, description: 'Fields to return in the results' },
filter: { type: 'object', required: false, description: 'Filter to apply to the search' },
rerank: { type: 'object', required: false, description: 'Reranking parameters' },
namespace: {
type: 'string',
required: false,
description: 'Namespace to search in',
},
searchQuery: {
type: 'string',
required: true,
description: 'Text to search for',
},
topK: {
type: 'string',
required: false,
description: 'Number of results to return',
},
fields: {
type: 'array',
required: false,
description: 'Fields to return in the results',
},
filter: {
type: 'object',
required: false,
description: 'Filter to apply to the search',
},
rerank: {
type: 'object',
required: false,
description: 'Reranking parameters',
},
},
request: {
+20 -4
View File
@@ -20,10 +20,26 @@ export const searchVectorTool: ToolConfig<PineconeSearchVectorParams, PineconeRe
requiredForToolCall: true,
description: 'Full Pinecone index host URL',
},
namespace: { type: 'string', required: false, description: 'Namespace to search in' },
vector: { type: 'array', required: true, description: 'Vector to search for' },
topK: { type: 'number', required: false, description: 'Number of results to return' },
filter: { type: 'object', required: false, description: 'Filter to apply to the search' },
namespace: {
type: 'string',
required: false,
description: 'Namespace to search in',
},
vector: {
type: 'array',
required: true,
description: 'Vector to search for',
},
topK: {
type: 'number',
required: false,
description: 'Number of results to return',
},
filter: {
type: 'object',
required: false,
description: 'Filter to apply to the search',
},
includeValues: {
type: 'boolean',
required: false,
+5 -1
View File
@@ -20,7 +20,11 @@ export const upsertTextTool: ToolConfig<PineconeUpsertTextParams, PineconeRespon
requiredForToolCall: true,
description: 'Full Pinecone index host URL',
},
namespace: { type: 'string', required: true, description: 'Namespace to upsert records into' },
namespace: {
type: 'string',
required: true,
description: 'Namespace to upsert records into',
},
records: {
type: 'array',
required: true,
+4 -1
View File
@@ -82,7 +82,10 @@ export const visionTool: ToolConfig<VisionParams, VisionResponse> = {
role: 'user',
content: [
{ type: 'text', text: prompt },
{ type: 'image', source: { type: 'url', url: params.imageUrl } },
{
type: 'image',
source: { type: 'url', url: params.imageUrl },
},
],
},
],
@@ -4,8 +4,8 @@ import { highlight, languages } from 'prismjs'
import 'prismjs/components/prism-javascript'
import 'prismjs/themes/prism.css'
import Editor from 'react-simple-code-editor'
import { EnvVarDropdown, checkEnvVarTrigger } from '@/components/ui/env-var-dropdown'
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
import { checkEnvVarTrigger, EnvVarDropdown } from '@/components/ui/env-var-dropdown'
import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown'
import { createLogger } from '@/lib/logs/console-logger'
import { cn } from '@/lib/utils'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
@@ -7,8 +7,8 @@ import 'prismjs/themes/prism.css'
import Editor from 'react-simple-code-editor'
import { Handle, Position, useUpdateNodeInternals } from 'reactflow'
import { Button } from '@/components/ui/button'
import { EnvVarDropdown, checkEnvVarTrigger } from '@/components/ui/env-var-dropdown'
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
import { checkEnvVarTrigger, EnvVarDropdown } from '@/components/ui/env-var-dropdown'
import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { createLogger } from '@/lib/logs/console-logger'
import { cn } from '@/lib/utils'
@@ -43,7 +43,9 @@ export function ConditionInput({ blockId, subBlockId, isConnecting }: ConditionI
const [cursorPosition, setCursorPosition] = useState(0)
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
const editorRef = useRef<HTMLDivElement>(null)
const [visualLineHeights, setVisualLineHeights] = useState<{ [key: string]: number[] }>({})
const [visualLineHeights, setVisualLineHeights] = useState<{
[key: string]: number[]
}>({})
const updateNodeInternals = useUpdateNodeInternals()
const removeEdge = useWorkflowStore((state) => state.removeEdge)
const edges = useWorkflowStore((state) => state.edges)
@@ -602,7 +604,11 @@ export function ConditionInput({ blockId, subBlockId, isConnecting }: ConditionI
setConditionalBlocks((blocks) =>
blocks.map((b) =>
b.id === block.id
? { ...b, showTags: false, activeSourceBlockId: null }
? {
...b,
showTags: false,
activeSourceBlockId: null,
}
: b
)
)
@@ -13,10 +13,10 @@ import {
import { client } from '@/lib/auth-client'
import { createLogger } from '@/lib/logs/console-logger'
import {
OAUTH_PROVIDERS,
OAuthProvider,
getProviderIdFromServiceId,
getServiceIdFromScopes,
OAUTH_PROVIDERS,
OAuthProvider,
parseProvider,
} from '@/lib/oauth'
import { saveToStorage } from '@/stores/workflows/persistence'
@@ -15,10 +15,10 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
import { createLogger } from '@/lib/logs/console-logger'
import {
Credential,
OAUTH_PROVIDERS,
OAuthProvider,
getProviderIdFromServiceId,
getServiceIdFromScopes,
OAUTH_PROVIDERS,
OAuthProvider,
parseProvider,
} from '@/lib/oauth'
import { saveToStorage } from '@/stores/workflows/persistence'
@@ -16,11 +16,11 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
import { createLogger } from '@/lib/logs/console-logger'
import {
Credential,
OAUTH_PROVIDERS,
OAuthProvider,
getProviderIdFromServiceId,
getServiceByProviderAndId,
getServiceIdFromScopes,
OAUTH_PROVIDERS,
OAuthProvider,
parseProvider,
} from '@/lib/oauth'
import { saveToStorage } from '@/stores/workflows/persistence'
@@ -153,7 +153,9 @@ export function GoogleDrivePicker({
return data.file
}
} else {
logger.error('Error fetching file by ID:', { error: await response.text() })
logger.error('Error fetching file by ID:', {
error: await response.text(),
})
}
return null
} catch (error) {
@@ -137,7 +137,9 @@ export function FolderSelector({
return data.label
}
} else {
logger.error('Error fetching folder by ID:', { error: await response.text() })
logger.error('Error fetching folder by ID:', {
error: await response.text(),
})
}
return null
} catch (error) {
@@ -187,7 +189,9 @@ export function FolderSelector({
}
}
} else {
logger.error('Error fetching folders:', { error: await response.text() })
logger.error('Error fetching folders:', {
error: await response.text(),
})
setFolders([])
}
} catch (error) {
@@ -1,8 +1,8 @@
import { useRef, useState } from 'react'
import { useReactFlow } from 'reactflow'
import { EnvVarDropdown, checkEnvVarTrigger } from '@/components/ui/env-var-dropdown'
import { checkEnvVarTrigger, EnvVarDropdown } from '@/components/ui/env-var-dropdown'
import { formatDisplayText } from '@/components/ui/formatted-text'
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown'
import { Textarea } from '@/components/ui/textarea'
import { createLogger } from '@/lib/logs/console-logger'
import { cn } from '@/lib/utils'
@@ -1,9 +1,9 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { useReactFlow } from 'reactflow'
import { EnvVarDropdown, checkEnvVarTrigger } from '@/components/ui/env-var-dropdown'
import { checkEnvVarTrigger, EnvVarDropdown } from '@/components/ui/env-var-dropdown'
import { formatDisplayText } from '@/components/ui/formatted-text'
import { Input } from '@/components/ui/input'
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown'
import { createLogger } from '@/lib/logs/console-logger'
import { cn } from '@/lib/utils'
import { SubBlockConfig } from '@/blocks/types'
@@ -1,10 +1,10 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { Trash2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { EnvVarDropdown, checkEnvVarTrigger } from '@/components/ui/env-var-dropdown'
import { checkEnvVarTrigger, EnvVarDropdown } from '@/components/ui/env-var-dropdown'
import { formatDisplayText } from '@/components/ui/formatted-text'
import { Input } from '@/components/ui/input'
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown'
import { cn } from '@/lib/utils'
import { useSubBlockValue } from '../hooks/use-sub-block-value'
@@ -9,9 +9,9 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { EnvVarDropdown, checkEnvVarTrigger } from '@/components/ui/env-var-dropdown'
import { checkEnvVarTrigger, EnvVarDropdown } from '@/components/ui/env-var-dropdown'
import { Label } from '@/components/ui/label'
import { TagDropdown, checkTagTrigger } from '@/components/ui/tag-dropdown'
import { checkTagTrigger, TagDropdown } from '@/components/ui/tag-dropdown'
import { createLogger } from '@/lib/logs/console-logger'
import { cn } from '@/lib/utils'
import { useCustomToolsStore } from '@/stores/custom-tools/store'
@@ -1,6 +1,6 @@
import React, {
ReactNode,
createContext,
ReactNode,
useCallback,
useContext,
useEffect,
@@ -374,7 +374,10 @@ export function ToolInput({ blockId, subBlockId }: ToolInputProps) {
])
} else {
setValue([
...selectedTools.map((tool) => ({ ...tool, isExpanded: false })),
...selectedTools.map((tool) => ({
...tool,
isExpanded: false,
})),
newTool,
])
}
@@ -54,7 +54,10 @@ export function WebhookModal({
const [isDeleting, setIsDeleting] = useState(false)
const [isTesting, setIsTesting] = useState(false)
const [isLoadingToken, setIsLoadingToken] = useState(false)
const [testResult, setTestResult] = useState<{ success: boolean; message?: string } | null>(null)
const [testResult, setTestResult] = useState<{
success: boolean
message?: string
} | null>(null)
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
const isConfigured = Boolean(webhookId)
@@ -221,7 +224,9 @@ export function WebhookModal({
// Get provider icon
const getProviderIcon = () => {
return provider.icon({ className: 'h-5 w-5 text-green-500 dark:text-green-400' })
return provider.icon({
className: 'h-5 w-5 text-green-500 dark:text-green-400',
})
}
// Get provider-specific title
@@ -247,7 +247,9 @@ export function WebhookConfig({ blockId, subBlockId, isConnecting }: WebhookConf
}
const provider = WEBHOOK_PROVIDERS[webhookProvider || 'generic']
return provider.icon({ className: 'h-4 w-4 mr-2 text-green-500 dark:text-green-400' })
return provider.icon({
className: 'h-4 w-4 mr-2 text-green-500 dark:text-green-400',
})
}
// Check if the webhook is connected for the selected provider
+3 -1
View File
@@ -134,7 +134,9 @@ function WorkflowContent() {
const blockConfig = getBlock(block.type)
if (!blockConfig) {
logger.error(`No configuration found for block type: ${block.type}`, { block })
logger.error(`No configuration found for block type: ${block.type}`, {
block,
})
return
}
@@ -127,7 +127,10 @@ export function Account({ onOpenChange }: AccountProps) {
<div className="relative flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted animate-pulse">
<div
className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent animate-[shimmer_1.5s_infinite]"
style={{ transform: 'translateX(-100%)', animation: 'shimmer 1.5s infinite' }}
style={{
transform: 'translateX(-100%)',
animation: 'shimmer 1.5s infinite',
}}
></div>
</div>
<div className="flex flex-col gap-2">
+8 -2
View File
@@ -35,8 +35,14 @@ const nextConfig: NextConfig = {
source: '/api/:path*',
headers: [
{ key: 'Access-Control-Allow-Credentials', value: 'true' },
{ key: 'Access-Control-Allow-Origin', value: 'https://localhost:3001' },
{ key: 'Access-Control-Allow-Methods', value: 'GET,POST,OPTIONS,PUT,DELETE' },
{
key: 'Access-Control-Allow-Origin',
value: 'https://localhost:3001',
},
{
key: 'Access-Control-Allow-Methods',
value: 'GET,POST,OPTIONS,PUT,DELETE',
},
{
key: 'Access-Control-Allow-Headers',
value:
+10 -2
View File
@@ -2,8 +2,16 @@
"name": "Sim Studio",
"short_name": "Sim Studio",
"icons": [
{ "src": "/favicon/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/favicon/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
{
"src": "/favicon/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/favicon/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
+20 -5
View File
@@ -29,21 +29,36 @@ export default defineConfig({
find: '@/stores/execution/store',
replacement: path.resolve(__dirname, 'app/stores/execution/store.ts'),
},
{ find: '@/blocks/types', replacement: path.resolve(__dirname, 'app/blocks/types.ts') },
{
find: '@/blocks/types',
replacement: path.resolve(__dirname, 'app/blocks/types.ts'),
},
{
find: '@/serializer/types',
replacement: path.resolve(__dirname, 'app/serializer/types.ts'),
},
{ find: '@/lib', replacement: path.resolve(__dirname, 'app/lib') },
{ find: '@/stores', replacement: path.resolve(__dirname, 'app/stores') },
{ find: '@/components', replacement: path.resolve(__dirname, 'app/components') },
{
find: '@/components',
replacement: path.resolve(__dirname, 'app/components'),
},
{ find: '@/app', replacement: path.resolve(__dirname, 'app') },
{ find: '@/api', replacement: path.resolve(__dirname, 'app/api') },
{ find: '@/executor', replacement: path.resolve(__dirname, 'app/executor') },
{ find: '@/providers', replacement: path.resolve(__dirname, 'app/providers') },
{
find: '@/executor',
replacement: path.resolve(__dirname, 'app/executor'),
},
{
find: '@/providers',
replacement: path.resolve(__dirname, 'app/providers'),
},
{ find: '@/tools', replacement: path.resolve(__dirname, 'app/tools') },
{ find: '@/blocks', replacement: path.resolve(__dirname, 'app/blocks') },
{ find: '@/serializer', replacement: path.resolve(__dirname, 'app/serializer') },
{
find: '@/serializer',
replacement: path.resolve(__dirname, 'app/serializer'),
},
{ find: '@', replacement: path.resolve(__dirname) },
],
},