Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,14 @@ Access the application at [http://localhost:3000/](http://localhost:3000/)

To use local models with Sim:

1. Pull models using our helper script:
1. Install Ollama and pull models:

```bash
./apps/sim/scripts/ollama_docker.sh pull <model_name>
# Install Ollama (if not already installed)
curl -fsSL https://ollama.ai/install.sh | sh

# Pull a model (e.g., gemma3:4b)
ollama pull gemma3:4b
```

2. Start Sim with local model support:
Expand Down
24 changes: 9 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,27 +59,21 @@ docker compose -f docker-compose.prod.yml up -d

Access the application at [http://localhost:3000/](http://localhost:3000/)

#### Using Local Models
#### Using Local Models with Ollama

To use local models with Sim:

1. Pull models using our helper script:
Run Sim with local AI models using [Ollama](https://ollama.ai) - no external APIs required:

```bash
./apps/sim/scripts/ollama_docker.sh pull <model_name>
```
# Start with GPU support (automatically downloads gemma3:4b model)
docker compose -f docker-compose.ollama.yml --profile setup up -d

2. Start Sim with local model support:
# For CPU-only systems:
docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
```

Wait for the model to download, then visit [http://localhost:3000](http://localhost:3000). Add more models with:
```bash
# With NVIDIA GPU support
docker compose --profile local-gpu -f docker-compose.ollama.yml up -d

# Without GPU (CPU only)
docker compose --profile local-cpu -f docker-compose.ollama.yml up -d

# If hosting on a server, update the environment variables in the docker-compose.prod.yml file to include the server's public IP then start again (OLLAMA_URL to i.e. http://1.1.1.1:11434)
docker compose -f docker-compose.prod.yml up -d
docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.1:8b
```

### Option 3: Dev Containers
Expand Down
3 changes: 3 additions & 0 deletions apps/sim/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ ENCRYPTION_KEY=your_encryption_key # Use `openssl rand -hex 32` to generate
# RESEND_API_KEY= # Uncomment and add your key from https://resend.com to send actual emails
# If left commented out, emails will be logged to console instead

# Local AI Models (Optional)
# OLLAMA_URL=http://localhost:11434 # URL for local Ollama server - uncomment if using local models

52 changes: 52 additions & 0 deletions apps/sim/app/api/providers/ollama/models/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { type NextRequest, NextResponse } from 'next/server'
import { env } from '@/lib/env'
import { createLogger } from '@/lib/logs/console/logger'
import type { ModelsObject } from '@/providers/ollama/types'

const logger = createLogger('OllamaModelsAPI')
const OLLAMA_HOST = env.OLLAMA_URL || 'http://localhost:11434'

export const dynamic = 'force-dynamic'

/**
* Get available Ollama models
*/
export async function GET(request: NextRequest) {
try {
logger.info('Fetching Ollama models', {
host: OLLAMA_HOST,
})

const response = await fetch(`${OLLAMA_HOST}/api/tags`, {
headers: {
'Content-Type': 'application/json',
},
})

if (!response.ok) {
logger.warn('Ollama service is not available', {
status: response.status,
statusText: response.statusText,
})
return NextResponse.json({ models: [] })
}

const data = (await response.json()) as ModelsObject
const models = data.models.map((model) => model.name)

logger.info('Successfully fetched Ollama models', {
count: models.length,
models,
})

return NextResponse.json({ models })
} catch (error) {
logger.error('Failed to fetch Ollama models', {
error: error instanceof Error ? error.message : 'Unknown error',
host: OLLAMA_HOST,
})

// Return empty array instead of error to avoid breaking the UI
return NextResponse.json({ models: [] })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -405,33 +405,37 @@ export function WorkflowBlock({ id, data }: NodeProps<WorkflowBlockProps>) {
// If there's no condition, the block should be shown
if (!block.condition) return true

// If condition is a function, call it to get the actual condition object
const actualCondition =
typeof block.condition === 'function' ? block.condition() : block.condition

// Get the values of the fields this block depends on from the appropriate state
const fieldValue = stateToUse[block.condition.field]?.value
const andFieldValue = block.condition.and
? stateToUse[block.condition.and.field]?.value
const fieldValue = stateToUse[actualCondition.field]?.value
const andFieldValue = actualCondition.and
? stateToUse[actualCondition.and.field]?.value
: undefined

// Check if the condition value is an array
const isValueMatch = Array.isArray(block.condition.value)
const isValueMatch = Array.isArray(actualCondition.value)
? fieldValue != null &&
(block.condition.not
? !block.condition.value.includes(fieldValue as string | number | boolean)
: block.condition.value.includes(fieldValue as string | number | boolean))
: block.condition.not
? fieldValue !== block.condition.value
: fieldValue === block.condition.value
(actualCondition.not
? !actualCondition.value.includes(fieldValue as string | number | boolean)
: actualCondition.value.includes(fieldValue as string | number | boolean))
: actualCondition.not
? fieldValue !== actualCondition.value
: fieldValue === actualCondition.value

// Check both conditions if 'and' is present
const isAndValueMatch =
!block.condition.and ||
(Array.isArray(block.condition.and.value)
!actualCondition.and ||
(Array.isArray(actualCondition.and.value)
? andFieldValue != null &&
(block.condition.and.not
? !block.condition.and.value.includes(andFieldValue as string | number | boolean)
: block.condition.and.value.includes(andFieldValue as string | number | boolean))
: block.condition.and.not
? andFieldValue !== block.condition.and.value
: andFieldValue === block.condition.and.value)
(actualCondition.and.not
? !actualCondition.and.value.includes(andFieldValue as string | number | boolean)
: actualCondition.and.value.includes(andFieldValue as string | number | boolean))
: actualCondition.and.not
? andFieldValue !== actualCondition.and.value
: andFieldValue === actualCondition.and.value)

return isValueMatch && isAndValueMatch
})
Expand Down
14 changes: 12 additions & 2 deletions apps/sim/blocks/blocks/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import {
MODELS_WITH_TEMPERATURE_SUPPORT,
providers,
} from '@/providers/utils'

// Get current Ollama models dynamically
const getCurrentOllamaModels = () => {
return useOllamaStore.getState().models
}
Comment on lines +16 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: This function references useOllamaStore before it's imported on line 21, which will cause a ReferenceError at runtime.


import { useOllamaStore } from '@/stores/ollama/store'
Comment on lines +16 to 21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: Move the import statement before the function definition to fix the reference error.

Suggested change
// Get current Ollama models dynamically
const getCurrentOllamaModels = () => {
return useOllamaStore.getState().models
}
import { useOllamaStore } from '@/stores/ollama/store'
import { useOllamaStore } from '@/stores/ollama/store'
// Get current Ollama models dynamically
const getCurrentOllamaModels = () => {
return useOllamaStore.getState().models
}

import type { ToolResponse } from '@/tools/types'

Expand Down Expand Up @@ -213,14 +219,18 @@ Create a system prompt appropriately detailed for the request, using clear langu
password: true,
connectionDroppable: false,
required: true,
// Hide API key for all hosted models when running on hosted version
// Hide API key for hosted models and Ollama models
condition: isHosted
? {
field: 'model',
value: getHostedModels(),
not: true, // Show for all models EXCEPT those listed
}
: undefined, // Show for all models in non-hosted environments
: () => ({
field: 'model',
value: getCurrentOllamaModels(),
not: true, // Show for all models EXCEPT Ollama models
}),
},
{
id: 'azureEndpoint',
Expand Down
31 changes: 21 additions & 10 deletions apps/sim/blocks/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,27 @@ export interface SubBlockConfig {
hidden?: boolean
description?: string
value?: (params: Record<string, any>) => string
condition?: {
field: string
value: string | number | boolean | Array<string | number | boolean>
not?: boolean
and?: {
field: string
value: string | number | boolean | Array<string | number | boolean> | undefined
not?: boolean
}
}
condition?:
| {
field: string
value: string | number | boolean | Array<string | number | boolean>
not?: boolean
and?: {
field: string
value: string | number | boolean | Array<string | number | boolean> | undefined
not?: boolean
}
}
| (() => {
field: string
value: string | number | boolean | Array<string | number | boolean>
not?: boolean
and?: {
field: string
value: string | number | boolean | Array<string | number | boolean> | undefined
not?: boolean
}
})
// Props specific to 'code' sub-block type
language?: 'javascript' | 'json'
generationType?: GenerationType
Expand Down
45 changes: 27 additions & 18 deletions apps/sim/executor/resolver/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class InputResolver {

/**
* Evaluates if a sub-block should be active based on its condition
* @param condition - The condition to evaluate
* @param condition - The condition to evaluate (can be static object or function)
* @param currentValues - Current values of all inputs
* @returns True if the sub-block should be active
*/
Expand All @@ -70,37 +70,46 @@ export class InputResolver {
not?: boolean
and?: { field: string; value: any; not?: boolean }
}
| (() => {
field: string
value: any
not?: boolean
and?: { field: string; value: any; not?: boolean }
})
| undefined,
currentValues: Record<string, any>
): boolean {
if (!condition) return true

// If condition is a function, call it to get the actual condition object
const actualCondition = typeof condition === 'function' ? condition() : condition

// Get the field value
const fieldValue = currentValues[condition.field]
const fieldValue = currentValues[actualCondition.field]

// Check if the condition value is an array
const isValueMatch = Array.isArray(condition.value)
const isValueMatch = Array.isArray(actualCondition.value)
? fieldValue != null &&
(condition.not
? !condition.value.includes(fieldValue)
: condition.value.includes(fieldValue))
: condition.not
? fieldValue !== condition.value
: fieldValue === condition.value
(actualCondition.not
? !actualCondition.value.includes(fieldValue)
: actualCondition.value.includes(fieldValue))
: actualCondition.not
? fieldValue !== actualCondition.value
: fieldValue === actualCondition.value

// Check both conditions if 'and' is present
const isAndValueMatch =
!condition.and ||
!actualCondition.and ||
(() => {
const andFieldValue = currentValues[condition.and!.field]
return Array.isArray(condition.and!.value)
const andFieldValue = currentValues[actualCondition.and!.field]
return Array.isArray(actualCondition.and!.value)
? andFieldValue != null &&
(condition.and!.not
? !condition.and!.value.includes(andFieldValue)
: condition.and!.value.includes(andFieldValue))
: condition.and!.not
? andFieldValue !== condition.and!.value
: andFieldValue === condition.and!.value
(actualCondition.and!.not
? !actualCondition.and!.value.includes(andFieldValue)
: actualCondition.and!.value.includes(andFieldValue))
: actualCondition.and!.not
? andFieldValue !== actualCondition.and!.value
: andFieldValue === actualCondition.and!.value
})()

return isValueMatch && isAndValueMatch
Expand Down
Loading