mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
Compare commits
27 Commits
v0.5.47
...
feat/aws-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f553667242 | ||
|
|
0c753c4394 | ||
|
|
2ac203e233 | ||
|
|
d29692ede4 | ||
|
|
f60232fa5b | ||
|
|
4ceec7ff9a | ||
|
|
0ff86a1413 | ||
|
|
4886e5aae8 | ||
|
|
6274bdcb18 | ||
|
|
7064f69520 | ||
|
|
154d8a674a | ||
|
|
a6e144ad93 | ||
|
|
d0514a39a8 | ||
|
|
ee66cd262b | ||
|
|
5aab24e1ed | ||
|
|
689d88fd7e | ||
|
|
b1047503b9 | ||
|
|
ec1eec4546 | ||
|
|
2b3989edd2 | ||
|
|
cb393c1638 | ||
|
|
c82e5ac3b3 | ||
|
|
67030d9576 | ||
|
|
8c157083bc | ||
|
|
6f07c2958e | ||
|
|
be100e4f86 | ||
|
|
46be9e3558 | ||
|
|
abf1ac06ce |
@@ -1,35 +0,0 @@
|
||||
---
|
||||
description: EMCN component library patterns
|
||||
globs: ["apps/sim/components/emcn/**"]
|
||||
---
|
||||
|
||||
# EMCN Components
|
||||
|
||||
Import from `@/components/emcn`, never from subpaths (except CSS files).
|
||||
|
||||
## CVA vs Direct Styles
|
||||
|
||||
**Use CVA when:** 2+ variants (primary/secondary, sm/md/lg)
|
||||
|
||||
```tsx
|
||||
const buttonVariants = cva('base-classes', {
|
||||
variants: { variant: { default: '...', primary: '...' } }
|
||||
})
|
||||
export { Button, buttonVariants }
|
||||
```
|
||||
|
||||
**Use direct className when:** Single consistent style, no variations
|
||||
|
||||
```tsx
|
||||
function Label({ className, ...props }) {
|
||||
return <Primitive className={cn('style-classes', className)} {...props} />
|
||||
}
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- Use Radix UI primitives for accessibility
|
||||
- Export component and variants (if using CVA)
|
||||
- TSDoc with usage examples
|
||||
- Consistent tokens: `font-medium`, `text-[12px]`, `rounded-[4px]`
|
||||
- `transition-colors` for hover states
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
description: Global coding standards that apply to all files
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Global Standards
|
||||
|
||||
You are a professional software engineer. All code must follow best practices: accurate, readable, clean, and efficient.
|
||||
|
||||
## Logging
|
||||
Import `createLogger` from `sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`.
|
||||
|
||||
## Comments
|
||||
Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
|
||||
|
||||
## Styling
|
||||
Never update global styles. Keep all styling local to components.
|
||||
|
||||
## Package Manager
|
||||
Use `bun` and `bunx`, not `npm` and `npx`.
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
description: Core architecture principles for the Sim app
|
||||
globs: ["apps/sim/**"]
|
||||
---
|
||||
|
||||
# Sim App Architecture
|
||||
|
||||
## Core Principles
|
||||
1. **Single Responsibility**: Each component, hook, store has one clear purpose
|
||||
2. **Composition Over Complexity**: Break down complex logic into smaller pieces
|
||||
3. **Type Safety First**: TypeScript interfaces for all props, state, return types
|
||||
4. **Predictable State**: Zustand for global state, useState for UI-only concerns
|
||||
|
||||
## Root-Level Structure
|
||||
|
||||
```
|
||||
apps/sim/
|
||||
├── app/ # Next.js app router (pages, API routes)
|
||||
├── blocks/ # Block definitions and registry
|
||||
├── components/ # Shared UI (emcn/, ui/)
|
||||
├── executor/ # Workflow execution engine
|
||||
├── hooks/ # Shared hooks (queries/, selectors/)
|
||||
├── lib/ # App-wide utilities
|
||||
├── providers/ # LLM provider integrations
|
||||
├── stores/ # Zustand stores
|
||||
├── tools/ # Tool definitions
|
||||
└── triggers/ # Trigger definitions
|
||||
```
|
||||
|
||||
## Feature Organization
|
||||
|
||||
Features live under `app/workspace/[workspaceId]/`:
|
||||
|
||||
```
|
||||
feature/
|
||||
├── components/ # Feature components
|
||||
├── hooks/ # Feature-scoped hooks
|
||||
├── utils/ # Feature-scoped utilities (2+ consumers)
|
||||
├── feature.tsx # Main component
|
||||
└── page.tsx # Next.js page entry
|
||||
```
|
||||
|
||||
## Naming Conventions
|
||||
- **Components**: PascalCase (`WorkflowList`)
|
||||
- **Hooks**: `use` prefix (`useWorkflowOperations`)
|
||||
- **Files**: kebab-case (`workflow-list.tsx`)
|
||||
- **Stores**: `stores/feature/store.ts`
|
||||
- **Constants**: SCREAMING_SNAKE_CASE
|
||||
- **Interfaces**: PascalCase with suffix (`WorkflowListProps`)
|
||||
|
||||
## Utils Rules
|
||||
|
||||
- **Never create `utils.ts` for single consumer** - inline it
|
||||
- **Create `utils.ts` when** 2+ files need the same helper
|
||||
- **Check existing sources** before duplicating (`lib/` has many utilities)
|
||||
- **Location**: `lib/` (app-wide) → `feature/utils/` (feature-scoped) → inline (single-use)
|
||||
@@ -1,48 +0,0 @@
|
||||
---
|
||||
description: Component patterns and structure for React components
|
||||
globs: ["apps/sim/**/*.tsx"]
|
||||
---
|
||||
|
||||
# Component Patterns
|
||||
|
||||
## Structure Order
|
||||
|
||||
```typescript
|
||||
'use client' // Only if using hooks
|
||||
|
||||
// Imports (external → internal)
|
||||
// Constants at module level
|
||||
const CONFIG = { SPACING: 8 } as const
|
||||
|
||||
// Props interface
|
||||
interface ComponentProps {
|
||||
requiredProp: string
|
||||
optionalProp?: boolean
|
||||
}
|
||||
|
||||
export function Component({ requiredProp, optionalProp = false }: ComponentProps) {
|
||||
// a. Refs
|
||||
// b. External hooks (useParams, useRouter)
|
||||
// c. Store hooks
|
||||
// d. Custom hooks
|
||||
// e. Local state
|
||||
// f. useMemo
|
||||
// g. useCallback
|
||||
// h. useEffect
|
||||
// i. Return JSX
|
||||
}
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
1. `'use client'` only when using React hooks
|
||||
2. Always define props interface
|
||||
3. Extract constants with `as const`
|
||||
4. Semantic HTML (`aside`, `nav`, `article`)
|
||||
5. Optional chain callbacks: `onAction?.(id)`
|
||||
|
||||
## Component Extraction
|
||||
|
||||
**Extract when:** 50+ lines, used in 2+ files, or has own state/logic
|
||||
|
||||
**Keep inline when:** < 10 lines, single use, purely presentational
|
||||
@@ -1,54 +0,0 @@
|
||||
---
|
||||
description: Custom hook patterns and best practices
|
||||
globs: ["apps/sim/**/use-*.ts", "apps/sim/**/hooks/**/*.ts"]
|
||||
---
|
||||
|
||||
# Hook Patterns
|
||||
|
||||
## Structure
|
||||
|
||||
```typescript
|
||||
interface UseFeatureProps {
|
||||
id: string
|
||||
onSuccess?: (result: Result) => void
|
||||
}
|
||||
|
||||
export function useFeature({ id, onSuccess }: UseFeatureProps) {
|
||||
// 1. Refs for stable dependencies
|
||||
const idRef = useRef(id)
|
||||
const onSuccessRef = useRef(onSuccess)
|
||||
|
||||
// 2. State
|
||||
const [data, setData] = useState<Data | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
// 3. Sync refs
|
||||
useEffect(() => {
|
||||
idRef.current = id
|
||||
onSuccessRef.current = onSuccess
|
||||
}, [id, onSuccess])
|
||||
|
||||
// 4. Operations (useCallback with empty deps when using refs)
|
||||
const fetchData = useCallback(async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const result = await fetch(`/api/${idRef.current}`).then(r => r.json())
|
||||
setData(result)
|
||||
onSuccessRef.current?.(result)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return { data, isLoading, fetchData }
|
||||
}
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
1. Single responsibility per hook
|
||||
2. Props interface required
|
||||
3. Refs for stable callback dependencies
|
||||
4. Wrap returned functions in useCallback
|
||||
5. Always try/catch async operations
|
||||
6. Track loading/error states
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
description: Import patterns for the Sim application
|
||||
globs: ["apps/sim/**/*.ts", "apps/sim/**/*.tsx"]
|
||||
---
|
||||
|
||||
# Import Patterns
|
||||
|
||||
## Absolute Imports
|
||||
|
||||
**Always use absolute imports.** Never use relative imports.
|
||||
|
||||
```typescript
|
||||
// ✓ Good
|
||||
import { useWorkflowStore } from '@/stores/workflows/store'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
// ✗ Bad
|
||||
import { useWorkflowStore } from '../../../stores/workflows/store'
|
||||
```
|
||||
|
||||
## Barrel Exports
|
||||
|
||||
Use barrel exports (`index.ts`) when a folder has 3+ exports. Import from barrel, not individual files.
|
||||
|
||||
```typescript
|
||||
// ✓ Good
|
||||
import { Dashboard, Sidebar } from '@/app/workspace/[workspaceId]/logs/components'
|
||||
|
||||
// ✗ Bad
|
||||
import { Dashboard } from '@/app/workspace/[workspaceId]/logs/components/dashboard/dashboard'
|
||||
```
|
||||
|
||||
## Import Order
|
||||
|
||||
1. React/core libraries
|
||||
2. External libraries
|
||||
3. UI components (`@/components/emcn`, `@/components/ui`)
|
||||
4. Utilities (`@/lib/...`)
|
||||
5. Stores (`@/stores/...`)
|
||||
6. Feature imports
|
||||
7. CSS imports
|
||||
|
||||
## Type Imports
|
||||
|
||||
Use `type` keyword for type-only imports:
|
||||
|
||||
```typescript
|
||||
import type { WorkflowLog } from '@/stores/logs/types'
|
||||
```
|
||||
@@ -1,207 +0,0 @@
|
||||
---
|
||||
description: Adding new integrations (tools, blocks, triggers)
|
||||
globs: ["apps/sim/tools/**", "apps/sim/blocks/**", "apps/sim/triggers/**"]
|
||||
---
|
||||
|
||||
# Adding Integrations
|
||||
|
||||
## Overview
|
||||
|
||||
Adding a new integration typically requires:
|
||||
1. **Tools** - API operations (`tools/{service}/`)
|
||||
2. **Block** - UI component (`blocks/blocks/{service}.ts`)
|
||||
3. **Icon** - SVG icon (`components/icons.tsx`)
|
||||
4. **Trigger** (optional) - Webhooks/polling (`triggers/{service}/`)
|
||||
|
||||
Always look up the service's API docs first.
|
||||
|
||||
## 1. Tools (`tools/{service}/`)
|
||||
|
||||
```
|
||||
tools/{service}/
|
||||
├── index.ts # Export all tools
|
||||
├── types.ts # Params/response types
|
||||
├── {action}.ts # Individual tool (e.g., send_message.ts)
|
||||
└── ...
|
||||
```
|
||||
|
||||
**Tool file structure:**
|
||||
|
||||
```typescript
|
||||
// tools/{service}/{action}.ts
|
||||
import type { {Service}Params, {Service}Response } from '@/tools/{service}/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const {service}{Action}Tool: ToolConfig<{Service}Params, {Service}Response> = {
|
||||
id: '{service}_{action}',
|
||||
name: '{Service} {Action}',
|
||||
description: 'What this tool does',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: '{service}' }, // if OAuth
|
||||
params: { /* param definitions */ },
|
||||
request: {
|
||||
url: '/api/tools/{service}/{action}',
|
||||
method: 'POST',
|
||||
headers: () => ({ 'Content-Type': 'application/json' }),
|
||||
body: (params) => ({ ...params }),
|
||||
},
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
if (!data.success) throw new Error(data.error)
|
||||
return { success: true, output: data.output }
|
||||
},
|
||||
outputs: { /* output definitions */ },
|
||||
}
|
||||
```
|
||||
|
||||
**Register in `tools/registry.ts`:**
|
||||
|
||||
```typescript
|
||||
import { {service}{Action}Tool } from '@/tools/{service}'
|
||||
// Add to registry object
|
||||
{service}_{action}: {service}{Action}Tool,
|
||||
```
|
||||
|
||||
## 2. Block (`blocks/blocks/{service}.ts`)
|
||||
|
||||
```typescript
|
||||
import { {Service}Icon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import type { {Service}Response } from '@/tools/{service}/types'
|
||||
|
||||
export const {Service}Block: BlockConfig<{Service}Response> = {
|
||||
type: '{service}',
|
||||
name: '{Service}',
|
||||
description: 'Short description',
|
||||
longDescription: 'Detailed description',
|
||||
category: 'tools',
|
||||
bgColor: '#hexcolor',
|
||||
icon: {Service}Icon,
|
||||
subBlocks: [ /* see SubBlock Properties below */ ],
|
||||
tools: {
|
||||
access: ['{service}_{action}', ...],
|
||||
config: {
|
||||
tool: (params) => `{service}_${params.operation}`,
|
||||
params: (params) => ({ ...params }),
|
||||
},
|
||||
},
|
||||
inputs: { /* input definitions */ },
|
||||
outputs: { /* output definitions */ },
|
||||
}
|
||||
```
|
||||
|
||||
### SubBlock Properties
|
||||
|
||||
```typescript
|
||||
{
|
||||
id: 'fieldName', // Unique identifier
|
||||
title: 'Field Label', // UI label
|
||||
type: 'short-input', // See SubBlock Types below
|
||||
placeholder: 'Hint text',
|
||||
required: true, // See Required below
|
||||
condition: { ... }, // See Condition below
|
||||
dependsOn: ['otherField'], // See DependsOn below
|
||||
mode: 'basic', // 'basic' | 'advanced' | 'both' | 'trigger'
|
||||
}
|
||||
```
|
||||
|
||||
**SubBlock Types:** `short-input`, `long-input`, `dropdown`, `code`, `switch`, `slider`, `oauth-input`, `channel-selector`, `user-selector`, `file-upload`, etc.
|
||||
|
||||
### `condition` - Show/hide based on another field
|
||||
|
||||
```typescript
|
||||
// Show when operation === 'send'
|
||||
condition: { field: 'operation', value: 'send' }
|
||||
|
||||
// Show when operation is 'send' OR 'read'
|
||||
condition: { field: 'operation', value: ['send', 'read'] }
|
||||
|
||||
// Show when operation !== 'send'
|
||||
condition: { field: 'operation', value: 'send', not: true }
|
||||
|
||||
// Complex: NOT in list AND another condition
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['list_channels', 'list_users'],
|
||||
not: true,
|
||||
and: { field: 'destinationType', value: 'dm', not: true }
|
||||
}
|
||||
```
|
||||
|
||||
### `required` - Field validation
|
||||
|
||||
```typescript
|
||||
// Always required
|
||||
required: true
|
||||
|
||||
// Conditionally required (same syntax as condition)
|
||||
required: { field: 'operation', value: 'send' }
|
||||
```
|
||||
|
||||
### `dependsOn` - Clear field when dependencies change
|
||||
|
||||
```typescript
|
||||
// Clear when credential changes
|
||||
dependsOn: ['credential']
|
||||
|
||||
// Clear when authMethod changes AND (credential OR botToken) changes
|
||||
dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }
|
||||
```
|
||||
|
||||
### `mode` - When to show field
|
||||
|
||||
- `'basic'` - Only in basic mode (default UI)
|
||||
- `'advanced'` - Only in advanced mode (manual input)
|
||||
- `'both'` - Show in both modes (default)
|
||||
- `'trigger'` - Only when block is used as trigger
|
||||
|
||||
**Register in `blocks/registry.ts`:**
|
||||
|
||||
```typescript
|
||||
import { {Service}Block } from '@/blocks/blocks/{service}'
|
||||
// Add to registry object (alphabetically)
|
||||
{service}: {Service}Block,
|
||||
```
|
||||
|
||||
## 3. Icon (`components/icons.tsx`)
|
||||
|
||||
```typescript
|
||||
export function {Service}Icon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
{/* SVG path from service's brand assets */}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Trigger (`triggers/{service}/`) - Optional
|
||||
|
||||
```
|
||||
triggers/{service}/
|
||||
├── index.ts # Export all triggers
|
||||
├── webhook.ts # Webhook handler
|
||||
├── utils.ts # Shared utilities
|
||||
└── {event}.ts # Specific event handlers
|
||||
```
|
||||
|
||||
**Register in `triggers/registry.ts`:**
|
||||
|
||||
```typescript
|
||||
import { {service}WebhookTrigger } from '@/triggers/{service}'
|
||||
// Add to TRIGGER_REGISTRY
|
||||
{service}_webhook: {service}WebhookTrigger,
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Look up API docs for the service
|
||||
- [ ] Create `tools/{service}/types.ts` with proper types
|
||||
- [ ] Create tool files for each operation
|
||||
- [ ] Create `tools/{service}/index.ts` barrel export
|
||||
- [ ] Register tools in `tools/registry.ts`
|
||||
- [ ] Add icon to `components/icons.tsx`
|
||||
- [ ] Create block in `blocks/blocks/{service}.ts`
|
||||
- [ ] Register block in `blocks/registry.ts`
|
||||
- [ ] (Optional) Create triggers in `triggers/{service}/`
|
||||
- [ ] (Optional) Register triggers in `triggers/registry.ts`
|
||||
@@ -1,66 +0,0 @@
|
||||
---
|
||||
description: React Query patterns for the Sim application
|
||||
globs: ["apps/sim/hooks/queries/**/*.ts"]
|
||||
---
|
||||
|
||||
# React Query Patterns
|
||||
|
||||
All React Query hooks live in `hooks/queries/`.
|
||||
|
||||
## Query Key Factory
|
||||
|
||||
Every query file defines a keys factory:
|
||||
|
||||
```typescript
|
||||
export const entityKeys = {
|
||||
all: ['entity'] as const,
|
||||
list: (workspaceId?: string) => [...entityKeys.all, 'list', workspaceId ?? ''] as const,
|
||||
detail: (id?: string) => [...entityKeys.all, 'detail', id ?? ''] as const,
|
||||
}
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```typescript
|
||||
// 1. Query keys factory
|
||||
// 2. Types (if needed)
|
||||
// 3. Private fetch functions
|
||||
// 4. Exported hooks
|
||||
```
|
||||
|
||||
## Query Hook
|
||||
|
||||
```typescript
|
||||
export function useEntityList(workspaceId?: string, options?: { enabled?: boolean }) {
|
||||
return useQuery({
|
||||
queryKey: entityKeys.list(workspaceId),
|
||||
queryFn: () => fetchEntities(workspaceId as string),
|
||||
enabled: Boolean(workspaceId) && (options?.enabled ?? true),
|
||||
staleTime: 60 * 1000,
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Mutation Hook
|
||||
|
||||
```typescript
|
||||
export function useCreateEntity() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: async (variables) => { /* fetch POST */ },
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: entityKeys.all }),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Optimistic Updates
|
||||
|
||||
For optimistic mutations syncing with Zustand, use `createOptimisticMutationHandlers` from `@/hooks/queries/utils/optimistic-mutation`.
|
||||
|
||||
## Naming
|
||||
|
||||
- **Keys**: `entityKeys`
|
||||
- **Query hooks**: `useEntity`, `useEntityList`
|
||||
- **Mutation hooks**: `useCreateEntity`, `useUpdateEntity`
|
||||
- **Fetch functions**: `fetchEntity` (private)
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
description: Zustand store patterns
|
||||
globs: ["apps/sim/**/store.ts", "apps/sim/**/stores/**/*.ts"]
|
||||
---
|
||||
|
||||
# Zustand Store Patterns
|
||||
|
||||
Stores live in `stores/`. Complex stores split into `store.ts` + `types.ts`.
|
||||
|
||||
## Basic Store
|
||||
|
||||
```typescript
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
import type { FeatureState } from '@/stores/feature/types'
|
||||
|
||||
const initialState = { items: [] as Item[], activeId: null as string | null }
|
||||
|
||||
export const useFeatureStore = create<FeatureState>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
...initialState,
|
||||
setItems: (items) => set({ items }),
|
||||
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
|
||||
reset: () => set(initialState),
|
||||
}),
|
||||
{ name: 'feature-store' }
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
## Persisted Store
|
||||
|
||||
```typescript
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
export const useFeatureStore = create<FeatureState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
width: 300,
|
||||
setWidth: (width) => set({ width }),
|
||||
_hasHydrated: false,
|
||||
setHasHydrated: (v) => set({ _hasHydrated: v }),
|
||||
}),
|
||||
{
|
||||
name: 'feature-state',
|
||||
partialize: (state) => ({ width: state.width }),
|
||||
onRehydrateStorage: () => (state) => state?.setHasHydrated(true),
|
||||
}
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
1. Use `devtools` middleware (named stores)
|
||||
2. Use `persist` only when data should survive reload
|
||||
3. `partialize` to persist only necessary state
|
||||
4. `_hasHydrated` pattern for persisted stores needing hydration tracking
|
||||
5. Immutable updates only
|
||||
6. `set((state) => ...)` when depending on previous state
|
||||
7. Provide `reset()` action
|
||||
|
||||
## Outside React
|
||||
|
||||
```typescript
|
||||
const items = useFeatureStore.getState().items
|
||||
useFeatureStore.setState({ items: newItems })
|
||||
```
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
description: Tailwind CSS and styling conventions
|
||||
globs: ["apps/sim/**/*.tsx", "apps/sim/**/*.css"]
|
||||
---
|
||||
|
||||
# Styling Rules
|
||||
|
||||
## Tailwind
|
||||
|
||||
1. **No inline styles** - Use Tailwind classes
|
||||
2. **No duplicate dark classes** - Skip `dark:` when value matches light mode
|
||||
3. **Exact values** - `text-[14px]`, `h-[25px]`
|
||||
4. **Transitions** - `transition-colors` for interactive states
|
||||
|
||||
## Conditional Classes
|
||||
|
||||
```typescript
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
<div className={cn(
|
||||
'base-classes',
|
||||
isActive && 'active-classes',
|
||||
disabled ? 'opacity-60' : 'hover:bg-accent'
|
||||
)} />
|
||||
```
|
||||
|
||||
## CSS Variables
|
||||
|
||||
For dynamic values (widths, heights) synced with stores:
|
||||
|
||||
```typescript
|
||||
// In store
|
||||
setWidth: (width) => {
|
||||
set({ width })
|
||||
document.documentElement.style.setProperty('--sidebar-width', `${width}px`)
|
||||
}
|
||||
|
||||
// In component
|
||||
<aside style={{ width: 'var(--sidebar-width)' }} />
|
||||
```
|
||||
@@ -1,60 +0,0 @@
|
||||
---
|
||||
description: Testing patterns with Vitest
|
||||
globs: ["apps/sim/**/*.test.ts", "apps/sim/**/*.test.tsx"]
|
||||
---
|
||||
|
||||
# Testing Patterns
|
||||
|
||||
Use Vitest. Test files live next to source: `feature.ts` → `feature.test.ts`
|
||||
|
||||
## Structure
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Tests for [feature name]
|
||||
*
|
||||
* @vitest-environment node
|
||||
*/
|
||||
|
||||
// 1. Mocks BEFORE imports
|
||||
vi.mock('@sim/db', () => ({ db: { select: vi.fn() } }))
|
||||
vi.mock('@sim/logger', () => loggerMock)
|
||||
|
||||
// 2. Imports AFTER mocks
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { createSession, loggerMock } from '@sim/testing'
|
||||
import { myFunction } from '@/lib/feature'
|
||||
|
||||
describe('myFunction', () => {
|
||||
beforeEach(() => vi.clearAllMocks())
|
||||
|
||||
it('should do something', () => {
|
||||
expect(myFunction()).toBe(expected)
|
||||
})
|
||||
|
||||
it.concurrent('runs in parallel', () => { ... })
|
||||
})
|
||||
```
|
||||
|
||||
## @sim/testing Package
|
||||
|
||||
```typescript
|
||||
// Factories - create test data
|
||||
import { createBlock, createWorkflow, createSession } from '@sim/testing'
|
||||
|
||||
// Mocks - pre-configured mocks
|
||||
import { loggerMock, databaseMock, fetchMock } from '@sim/testing'
|
||||
|
||||
// Builders - fluent API for complex objects
|
||||
import { ExecutionBuilder, WorkflowBuilder } from '@sim/testing'
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
1. `@vitest-environment node` directive at file top
|
||||
2. **Mocks before imports** - `vi.mock()` calls must come first
|
||||
3. Use `@sim/testing` factories over manual test data
|
||||
4. `it.concurrent` for independent tests (faster)
|
||||
5. `beforeEach(() => vi.clearAllMocks())` to reset state
|
||||
6. Group related tests with nested `describe` blocks
|
||||
7. Test file naming: `*.test.ts` (not `*.spec.ts`)
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
description: TypeScript conventions and type safety
|
||||
globs: ["apps/sim/**/*.ts", "apps/sim/**/*.tsx"]
|
||||
---
|
||||
|
||||
# TypeScript Rules
|
||||
|
||||
1. **No `any`** - Use proper types or `unknown` with type guards
|
||||
2. **Props interface** - Always define for components
|
||||
3. **Const assertions** - `as const` for constant objects/arrays
|
||||
4. **Ref types** - Explicit: `useRef<HTMLDivElement>(null)`
|
||||
5. **Type imports** - `import type { X }` for type-only imports
|
||||
|
||||
```typescript
|
||||
// ✗ Bad
|
||||
const handleClick = (e: any) => {}
|
||||
|
||||
// ✓ Good
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {}
|
||||
```
|
||||
69
.devcontainer/.bashrc
Normal file
69
.devcontainer/.bashrc
Normal file
@@ -0,0 +1,69 @@
|
||||
# Sim Studio Development Environment Bashrc
|
||||
# This gets sourced by post-create.sh
|
||||
|
||||
# Enhanced prompt with git branch info
|
||||
parse_git_branch() {
|
||||
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
|
||||
}
|
||||
|
||||
export PS1="\[\033[01;32m\]\u@simstudio\[\033[00m\]:\[\033[01;34m\]\w\[\033[33m\]\$(parse_git_branch)\[\033[00m\]\$ "
|
||||
|
||||
# Helpful aliases
|
||||
alias ll="ls -la"
|
||||
alias ..="cd .."
|
||||
alias ...="cd ../.."
|
||||
|
||||
# Database aliases
|
||||
alias pgc="PGPASSWORD=postgres psql -h db -U postgres -d simstudio"
|
||||
alias check-db="PGPASSWORD=postgres psql -h db -U postgres -c '\l'"
|
||||
|
||||
# Sim Studio specific aliases
|
||||
alias logs="cd /workspace/apps/sim && tail -f logs/*.log 2>/dev/null || echo 'No log files found'"
|
||||
alias sim-start="cd /workspace && bun run dev"
|
||||
alias sim-migrate="cd /workspace/apps/sim && bunx drizzle-kit push"
|
||||
alias sim-generate="cd /workspace/apps/sim && bunx drizzle-kit generate"
|
||||
alias sim-rebuild="cd /workspace && bun run build && bun run start"
|
||||
alias docs-dev="cd /workspace/apps/docs && bun run dev"
|
||||
|
||||
# Turbo related commands
|
||||
alias turbo-build="cd /workspace && bunx turbo run build"
|
||||
alias turbo-dev="cd /workspace && bunx turbo run dev"
|
||||
alias turbo-test="cd /workspace && bunx turbo run test"
|
||||
|
||||
# Bun specific commands
|
||||
alias bun-update="cd /workspace && bun update"
|
||||
alias bun-add="cd /workspace && bun add"
|
||||
alias bun-pm="cd /workspace && bun pm"
|
||||
alias bun-canary="bun upgrade --canary"
|
||||
|
||||
# Default to workspace directory
|
||||
cd /workspace 2>/dev/null || true
|
||||
|
||||
# Welcome message - only show once per session
|
||||
if [ -z "$SIM_WELCOME_SHOWN" ]; then
|
||||
export SIM_WELCOME_SHOWN=1
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🚀 Welcome to Sim Studio development environment!"
|
||||
echo ""
|
||||
echo "Available commands:"
|
||||
echo " sim-start - Start all apps in development mode"
|
||||
echo " sim-migrate - Push schema changes to the database for sim app"
|
||||
echo " sim-generate - Generate new migrations for sim app"
|
||||
echo " sim-rebuild - Build and start all apps"
|
||||
echo " docs-dev - Start only the docs app in development mode"
|
||||
echo ""
|
||||
echo "Turbo commands:"
|
||||
echo " turbo-build - Build all apps using Turborepo"
|
||||
echo " turbo-dev - Start development mode for all apps"
|
||||
echo " turbo-test - Run tests for all packages"
|
||||
echo ""
|
||||
echo "Bun commands:"
|
||||
echo " bun-update - Update dependencies"
|
||||
echo " bun-add - Add a new dependency"
|
||||
echo " bun-pm - Manage dependencies"
|
||||
echo " bun-canary - Upgrade to the latest canary version of Bun"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
fi
|
||||
@@ -1,43 +1,38 @@
|
||||
FROM oven/bun:1.3.3-alpine
|
||||
# Use the latest Bun canary image for development
|
||||
FROM oven/bun:canary
|
||||
|
||||
# Avoid warnings by switching to noninteractive
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install necessary packages for development
|
||||
RUN apk add --no-cache \
|
||||
git \
|
||||
curl \
|
||||
wget \
|
||||
jq \
|
||||
sudo \
|
||||
postgresql-client \
|
||||
vim \
|
||||
nano \
|
||||
bash \
|
||||
bash-completion \
|
||||
zsh \
|
||||
zsh-vcs \
|
||||
ca-certificates \
|
||||
shadow
|
||||
RUN apt-get update \
|
||||
&& apt-get -y install --no-install-recommends \
|
||||
git curl wget jq sudo postgresql-client vim nano \
|
||||
bash-completion ca-certificates lsb-release gnupg \
|
||||
&& apt-get clean -y \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create a non-root user with matching UID/GID
|
||||
# Create a non-root user
|
||||
ARG USERNAME=bun
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=$USER_UID
|
||||
|
||||
# Create user group if it doesn't exist
|
||||
RUN if ! getent group $USER_GID >/dev/null; then \
|
||||
addgroup -g $USER_GID $USERNAME; \
|
||||
fi
|
||||
|
||||
# Create user if it doesn't exist
|
||||
RUN if ! getent passwd $USER_UID >/dev/null; then \
|
||||
adduser -D -u $USER_UID -G $(getent group $USER_GID | cut -d: -f1) $USERNAME; \
|
||||
fi
|
||||
|
||||
# Add sudo support
|
||||
RUN echo "$USERNAME ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USERNAME \
|
||||
&& chmod 0440 /etc/sudoers.d/$USERNAME
|
||||
|
||||
# Install global packages for development
|
||||
RUN bun install -g turbo drizzle-kit typescript @types/node
|
||||
|
||||
# Install bun completions
|
||||
RUN bun completions > /etc/bash_completion.d/bun
|
||||
|
||||
# Set up shell environment
|
||||
RUN echo "export PATH=\$PATH:/home/$USERNAME/.bun/bin" >> /etc/profile
|
||||
RUN echo "export PATH=$PATH:/home/$USERNAME/.bun/bin" >> /etc/profile
|
||||
RUN echo "source /etc/profile" >> /etc/bash.bashrc
|
||||
|
||||
# Switch back to dialog for any ad-hoc use of apt-get
|
||||
ENV DEBIAN_FRONTEND=dialog
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
|
||||
@@ -1,75 +1,78 @@
|
||||
# Sim Development Container
|
||||
# Sim Studio Development Container
|
||||
|
||||
Development container configuration for VS Code Dev Containers and GitHub Codespaces.
|
||||
This directory contains configuration files for Visual Studio Code Dev Containers / GitHub Codespaces. Dev containers provide a consistent, isolated development environment for this project.
|
||||
|
||||
## Prerequisites
|
||||
## Contents
|
||||
|
||||
- `devcontainer.json` - The main configuration file that defines the development container settings
|
||||
- `Dockerfile` - Defines the container image and development environment
|
||||
- `docker-compose.yml` - Sets up the application and database containers
|
||||
- `post-create.sh` - Script that runs when the container is created
|
||||
- `.bashrc` - Custom shell configuration with helpful aliases
|
||||
|
||||
## Usage
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Visual Studio Code
|
||||
- Docker Desktop or Podman Desktop
|
||||
- VS Code Dev Containers extension
|
||||
- Docker installation:
|
||||
- Docker Desktop (Windows/macOS)
|
||||
- Docker Engine (Linux)
|
||||
- VS Code Remote - Containers extension
|
||||
|
||||
## Getting Started
|
||||
### Getting Started
|
||||
|
||||
1. Open this project in VS Code
|
||||
2. Click "Reopen in Container" when prompted (or press `F1` → "Dev Containers: Reopen in Container")
|
||||
1. Open this project in Visual Studio Code
|
||||
2. When prompted, click "Reopen in Container"
|
||||
- Alternatively, press `F1` and select "Remote-Containers: Reopen in Container"
|
||||
3. Wait for the container to build and initialize
|
||||
4. Start developing with `sim-start`
|
||||
4. The post-creation script will automatically:
|
||||
|
||||
The setup script will automatically install dependencies and run migrations.
|
||||
- Install dependencies
|
||||
- Set up environment variables
|
||||
- Run database migrations
|
||||
- Configure helpful aliases
|
||||
|
||||
## Development Commands
|
||||
5. Start the application with `sim-start` (alias for `bun run dev`)
|
||||
|
||||
### Running Services
|
||||
### Development Commands
|
||||
|
||||
You have two options for running the development environment:
|
||||
|
||||
**Option 1: Run everything together (recommended for most development)**
|
||||
```bash
|
||||
sim-start # Runs both app and socket server using concurrently
|
||||
```
|
||||
|
||||
**Option 2: Run services separately (useful for debugging individual services)**
|
||||
- In the **app** container terminal: `sim-app` (starts Next.js app on port 3000)
|
||||
- In the **realtime** container terminal: `sim-sockets` (starts socket server on port 3002)
|
||||
|
||||
### Other Commands
|
||||
The development environment includes these helpful aliases:
|
||||
|
||||
- `sim-start` - Start the development server
|
||||
- `sim-migrate` - Push schema changes to the database
|
||||
- `sim-generate` - Generate new migrations
|
||||
- `build` - Build the application
|
||||
- `pgc` - Connect to PostgreSQL database
|
||||
- `sim-rebuild` - Build and start the production version
|
||||
- `pgc` - Connect to the PostgreSQL database
|
||||
- `check-db` - List all databases
|
||||
|
||||
### Using GitHub Codespaces
|
||||
|
||||
This project is also configured for GitHub Codespaces. To use it:
|
||||
|
||||
1. Go to the GitHub repository
|
||||
2. Click the "Code" button
|
||||
3. Select the "Codespaces" tab
|
||||
4. Click "Create codespace on main"
|
||||
|
||||
This will start a new Codespace with the development environment already set up.
|
||||
|
||||
## Customization
|
||||
|
||||
You can customize the development environment by:
|
||||
|
||||
- Modifying `devcontainer.json` to add VS Code extensions or settings
|
||||
- Updating the `Dockerfile` to install additional packages
|
||||
- Editing `docker-compose.yml` to add services or change configuration
|
||||
- Modifying `.bashrc` to add custom aliases or configurations
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Build errors**: Rebuild the container with `F1` → "Dev Containers: Rebuild Container"
|
||||
If you encounter issues:
|
||||
|
||||
**Port conflicts**: Ensure ports 3000, 3002, and 5432 are available
|
||||
1. Rebuild the container: `F1` → "Remote-Containers: Rebuild Container"
|
||||
2. Check Docker logs for build errors
|
||||
3. Verify Docker Desktop is running
|
||||
4. Ensure all prerequisites are installed
|
||||
|
||||
**Container runtime issues**: Verify Docker Desktop or Podman Desktop is running
|
||||
|
||||
## Technical Details
|
||||
|
||||
Services:
|
||||
- **App container** (8GB memory limit) - Main Next.js application
|
||||
- **Realtime container** (4GB memory limit) - Socket.io server for real-time features
|
||||
- **Database** - PostgreSQL with pgvector extension
|
||||
- **Migrations** - Runs automatically on container creation
|
||||
|
||||
You can develop with services running together or independently.
|
||||
|
||||
### Personalization
|
||||
|
||||
**Project commands** (`sim-start`, `sim-app`, etc.) are automatically available via `/workspace/.devcontainer/sim-commands.sh`.
|
||||
|
||||
**Personal shell customization** (aliases, prompts, etc.) should use VS Code's dotfiles feature:
|
||||
1. Create a dotfiles repository (e.g., `github.com/youruser/dotfiles`)
|
||||
2. Add your `.bashrc`, `.zshrc`, or other configs
|
||||
3. Configure in VS Code Settings:
|
||||
```json
|
||||
{
|
||||
"dotfiles.repository": "youruser/dotfiles",
|
||||
"dotfiles.installCommand": "install.sh"
|
||||
}
|
||||
```
|
||||
|
||||
This separates project-specific commands from personal preferences, following VS Code best practices.
|
||||
For more information, see the [VS Code Remote Development documentation](https://code.visualstudio.com/docs/remote/containers).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "Sim Dev Environment",
|
||||
"name": "Sim Studio Dev Environment",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspace",
|
||||
@@ -13,6 +13,13 @@
|
||||
"source.fixAll.biome": "explicit",
|
||||
"source.organizeImports.biome": "explicit"
|
||||
},
|
||||
"terminal.integrated.defaultProfile.linux": "bash",
|
||||
"terminal.integrated.profiles.linux": {
|
||||
"bash": {
|
||||
"path": "/bin/bash",
|
||||
"args": ["--login"]
|
||||
}
|
||||
},
|
||||
"terminal.integrated.shellIntegration.enabled": true
|
||||
},
|
||||
"extensions": [
|
||||
@@ -29,9 +36,18 @@
|
||||
}
|
||||
},
|
||||
|
||||
"forwardPorts": [3000, 3002, 5432],
|
||||
"forwardPorts": [3000, 5432],
|
||||
|
||||
"postCreateCommand": "bash -c 'bash .devcontainer/post-create.sh || true'",
|
||||
|
||||
"remoteUser": "bun"
|
||||
"postStartCommand": "bash -c 'if [ ! -f ~/.bashrc ] || ! grep -q \"sim-start\" ~/.bashrc; then cp .devcontainer/.bashrc ~/.bashrc; fi'",
|
||||
|
||||
"remoteUser": "bun",
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/git:1": {},
|
||||
"ghcr.io/prulloac/devcontainer-features/bun:1": {
|
||||
"version": "latest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,56 +7,53 @@ services:
|
||||
- ..:/workspace:cached
|
||||
- bun-cache:/home/bun/.bun/cache:delegated
|
||||
command: sleep infinity
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 8G
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
|
||||
- POSTGRES_URL=postgresql://postgres:postgres@db:5432/simstudio
|
||||
- BETTER_AUTH_URL=http://localhost:3000
|
||||
- NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your_auth_secret_here}
|
||||
- ENCRYPTION_KEY=${ENCRYPTION_KEY:-your_encryption_key_here}
|
||||
- COPILOT_API_KEY=${COPILOT_API_KEY}
|
||||
- SIM_AGENT_API_URL=${SIM_AGENT_API_URL}
|
||||
- OLLAMA_URL=${OLLAMA_URL:-http://localhost:11434}
|
||||
- NEXT_PUBLIC_SOCKET_URL=${NEXT_PUBLIC_SOCKET_URL:-http://localhost:3002}
|
||||
- BUN_INSTALL_CACHE_DIR=/home/bun/.bun/cache
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
realtime:
|
||||
condition: service_healthy
|
||||
migrations:
|
||||
condition: service_completed_successfully
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "3001:3001"
|
||||
working_dir: /workspace
|
||||
healthcheck:
|
||||
test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3000']
|
||||
interval: 90s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
realtime:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: .devcontainer/Dockerfile
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
- bun-cache:/home/bun/.bun/cache:delegated
|
||||
command: sleep infinity
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio
|
||||
- BETTER_AUTH_URL=http://localhost:3000
|
||||
- NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
- BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your_auth_secret_here}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "3002:3002"
|
||||
working_dir: /workspace
|
||||
healthcheck:
|
||||
test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3002']
|
||||
interval: 90s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
migrations:
|
||||
build:
|
||||
@@ -80,7 +77,7 @@ services:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=simstudio
|
||||
ports:
|
||||
- "${POSTGRES_PORT:-5432}:5432"
|
||||
- "5432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
|
||||
@@ -3,48 +3,16 @@
|
||||
# Exit on error, but with some error handling
|
||||
set -e
|
||||
|
||||
echo "🔧 Setting up Sim development environment..."
|
||||
echo "🔧 Setting up Sim Studio development environment..."
|
||||
|
||||
# Change to the workspace root directory
|
||||
cd /workspace
|
||||
|
||||
# Install global packages for development (done at runtime, not build time)
|
||||
echo "📦 Installing global development tools..."
|
||||
bun install -g turbo drizzle-kit typescript @types/node 2>/dev/null || {
|
||||
echo "⚠️ Some global packages may already be installed, continuing..."
|
||||
}
|
||||
|
||||
# Set up bun completions (with proper shell detection)
|
||||
echo "🔧 Setting up shell completions..."
|
||||
if [ -n "$SHELL" ] && [ -f "$SHELL" ]; then
|
||||
SHELL=/bin/bash bun completions 2>/dev/null | sudo tee /etc/bash_completion.d/bun > /dev/null || {
|
||||
echo "⚠️ Could not install bun completions, but continuing..."
|
||||
}
|
||||
fi
|
||||
|
||||
# Add project commands to shell profile
|
||||
echo "📄 Setting up project commands..."
|
||||
# Add sourcing of sim-commands.sh to user's shell config files if they exist
|
||||
for rcfile in ~/.bashrc ~/.zshrc; do
|
||||
if [ -f "$rcfile" ]; then
|
||||
# Check if already added
|
||||
if ! grep -q "sim-commands.sh" "$rcfile"; then
|
||||
echo "" >> "$rcfile"
|
||||
echo "# Sim project commands" >> "$rcfile"
|
||||
echo "if [ -f /workspace/.devcontainer/sim-commands.sh ]; then" >> "$rcfile"
|
||||
echo " source /workspace/.devcontainer/sim-commands.sh" >> "$rcfile"
|
||||
echo "fi" >> "$rcfile"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# If no rc files exist yet, create a minimal one
|
||||
if [ ! -f ~/.bashrc ] && [ ! -f ~/.zshrc ]; then
|
||||
echo "# Source Sim project commands" > ~/.bashrc
|
||||
echo "if [ -f /workspace/.devcontainer/sim-commands.sh ]; then" >> ~/.bashrc
|
||||
echo " source /workspace/.devcontainer/sim-commands.sh" >> ~/.bashrc
|
||||
echo "fi" >> ~/.bashrc
|
||||
fi
|
||||
# Setup .bashrc
|
||||
echo "📄 Setting up .bashrc with aliases..."
|
||||
cp /workspace/.devcontainer/.bashrc ~/.bashrc
|
||||
# Add to .profile to ensure .bashrc is sourced in non-interactive shells
|
||||
echo 'if [ -f ~/.bashrc ]; then . ~/.bashrc; fi' >> ~/.profile
|
||||
|
||||
# Clean and reinstall dependencies to ensure platform compatibility
|
||||
echo "📦 Cleaning and reinstalling dependencies..."
|
||||
@@ -61,12 +29,18 @@ chmod 700 ~/.bun ~/.bun/cache
|
||||
|
||||
# Install dependencies with platform-specific binaries
|
||||
echo "Installing dependencies with Bun..."
|
||||
bun install
|
||||
bun install || {
|
||||
echo "⚠️ bun install had issues but continuing setup..."
|
||||
}
|
||||
|
||||
# Check for native dependencies
|
||||
echo "Checking for native dependencies compatibility..."
|
||||
if grep -q '"trustedDependencies"' apps/sim/package.json 2>/dev/null; then
|
||||
echo "⚠️ Native dependencies detected. Bun will handle compatibility during install."
|
||||
NATIVE_DEPS=$(grep '"trustedDependencies"' apps/sim/package.json || echo "")
|
||||
if [ ! -z "$NATIVE_DEPS" ]; then
|
||||
echo "⚠️ Native dependencies detected. Ensuring compatibility with Bun..."
|
||||
for pkg in $(echo $NATIVE_DEPS | grep -oP '"[^"]*"' | tr -d '"' | grep -v "trustedDependencies"); do
|
||||
echo "Checking compatibility for $pkg..."
|
||||
done
|
||||
fi
|
||||
|
||||
# Set up environment variables if .env doesn't exist for the sim app
|
||||
@@ -108,12 +82,29 @@ echo "Waiting for database to be ready..."
|
||||
fi
|
||||
) || echo "⚠️ Database setup had issues but continuing..."
|
||||
|
||||
# Add additional helpful aliases to .bashrc
|
||||
cat << EOF >> ~/.bashrc
|
||||
|
||||
# Additional Sim Studio Development Aliases
|
||||
alias migrate="cd /workspace/apps/sim && DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio bunx drizzle-kit push"
|
||||
alias generate="cd /workspace/apps/sim && bunx drizzle-kit generate"
|
||||
alias dev="cd /workspace && bun run dev"
|
||||
alias build="cd /workspace && bun run build"
|
||||
alias start="cd /workspace && bun run dev"
|
||||
alias lint="cd /workspace/apps/sim && bun run lint"
|
||||
alias test="cd /workspace && bun run test"
|
||||
alias bun-update="cd /workspace && bun update"
|
||||
EOF
|
||||
|
||||
# Source the .bashrc to make aliases available immediately
|
||||
. ~/.bashrc
|
||||
|
||||
# Clear the welcome message flag to ensure it shows after setup
|
||||
unset SIM_WELCOME_SHOWN
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "✅ Sim development environment setup complete!"
|
||||
echo "✅ Sim Studio development environment setup complete!"
|
||||
echo ""
|
||||
echo "Your environment is now ready. A new terminal session will show"
|
||||
echo "available commands. You can start the development server with:"
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Sim Project Commands
|
||||
# Source this file to add project-specific commands to your shell
|
||||
# Add to your ~/.bashrc or ~/.zshrc: source /workspace/.devcontainer/sim-commands.sh
|
||||
|
||||
# Project-specific aliases for Sim development
|
||||
alias sim-start="cd /workspace && bun run dev:full"
|
||||
alias sim-app="cd /workspace && bun run dev"
|
||||
alias sim-sockets="cd /workspace && bun run dev:sockets"
|
||||
alias sim-migrate="cd /workspace/apps/sim && bunx drizzle-kit push"
|
||||
alias sim-generate="cd /workspace/apps/sim && bunx drizzle-kit generate"
|
||||
alias sim-rebuild="cd /workspace && bun run build && bun run start"
|
||||
alias docs-dev="cd /workspace/apps/docs && bun run dev"
|
||||
|
||||
# Database connection helpers
|
||||
alias pgc="PGPASSWORD=postgres psql -h db -U postgres -d simstudio"
|
||||
alias check-db="PGPASSWORD=postgres psql -h db -U postgres -c '\l'"
|
||||
|
||||
# Default to workspace directory
|
||||
cd /workspace 2>/dev/null || true
|
||||
|
||||
# Welcome message - show once per session
|
||||
if [ -z "$SIM_WELCOME_SHOWN" ]; then
|
||||
export SIM_WELCOME_SHOWN=1
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🚀 Sim Development Environment"
|
||||
echo ""
|
||||
echo "Project commands:"
|
||||
echo " sim-start - Start app + socket server"
|
||||
echo " sim-app - Start only main app"
|
||||
echo " sim-sockets - Start only socket server"
|
||||
echo " sim-migrate - Push schema changes"
|
||||
echo " sim-generate - Generate migrations"
|
||||
echo ""
|
||||
echo "Database:"
|
||||
echo " pgc - Connect to PostgreSQL"
|
||||
echo " check-db - List databases"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
fi
|
||||
@@ -1,67 +1,11 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Documentation
|
||||
LICENSE
|
||||
NOTICE
|
||||
README.md
|
||||
*.md
|
||||
docs/
|
||||
|
||||
# IDE and editor
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Environment and config
|
||||
.env*
|
||||
!.env.example
|
||||
.prettierrc
|
||||
.prettierignore
|
||||
.eslintrc*
|
||||
.eslintignore
|
||||
|
||||
# CI/CD and DevOps
|
||||
README.md
|
||||
.gitignore
|
||||
.husky
|
||||
.github
|
||||
.devcontainer
|
||||
.husky
|
||||
docker-compose*.yml
|
||||
Dockerfile*
|
||||
|
||||
# Build artifacts and caches
|
||||
.next
|
||||
.turbo
|
||||
.cache
|
||||
dist
|
||||
build
|
||||
out
|
||||
coverage
|
||||
*.log
|
||||
|
||||
# Dependencies (will be installed fresh in container)
|
||||
node_modules
|
||||
.bun
|
||||
|
||||
# Test files
|
||||
**/*.test.ts
|
||||
**/*.test.tsx
|
||||
**/*.spec.ts
|
||||
**/*.spec.tsx
|
||||
__tests__
|
||||
__mocks__
|
||||
jest.config.*
|
||||
vitest.config.*
|
||||
|
||||
# TypeScript build info
|
||||
*.tsbuildinfo
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Temporary files
|
||||
tmp
|
||||
temp
|
||||
*.tmp
|
||||
.env.example
|
||||
node_modules
|
||||
34
.gitattributes
vendored
34
.gitattributes
vendored
@@ -1,34 +0,0 @@
|
||||
# Set default behavior to automatically normalize line endings
|
||||
* text=auto eol=lf
|
||||
|
||||
# Explicitly declare text files you want to always be normalized and converted
|
||||
# to native line endings on checkout
|
||||
*.ts text eol=lf
|
||||
*.tsx text eol=lf
|
||||
*.js text eol=lf
|
||||
*.jsx text eol=lf
|
||||
*.json text eol=lf
|
||||
*.md text eol=lf
|
||||
*.yml text eol=lf
|
||||
*.yaml text eol=lf
|
||||
*.toml text eol=lf
|
||||
*.css text eol=lf
|
||||
*.scss text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.bash text eol=lf
|
||||
Dockerfile* text eol=lf
|
||||
.dockerignore text eol=lf
|
||||
.gitignore text eol=lf
|
||||
.gitattributes text eol=lf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.ttf binary
|
||||
*.eot binary
|
||||
*.pdf binary
|
||||
4
.github/CODE_OF_CONDUCT.md
vendored
4
.github/CODE_OF_CONDUCT.md
vendored
@@ -1,4 +1,4 @@
|
||||
# Code of Conduct - Sim
|
||||
# Code of Conduct - Sim Studio
|
||||
|
||||
## Our Pledge
|
||||
|
||||
@@ -55,7 +55,7 @@ representative at an online or offline event.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behaviour may be
|
||||
reported to the community leaders responsible for enforcement at <waleed@sim.ai>.
|
||||
reported to the community leaders responsible for enforcement at <waleed@simstudio.ai>.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
|
||||
174
.github/CONTRIBUTING.md
vendored
174
.github/CONTRIBUTING.md
vendored
@@ -1,9 +1,9 @@
|
||||
# Contributing to Sim
|
||||
# Contributing to Sim Studio
|
||||
|
||||
Thank you for your interest in contributing to Sim! Our goal is to provide developers with a powerful, user-friendly platform for building, testing, and optimizing agentic workflows. We welcome contributions in all forms—from bug fixes and design improvements to brand-new features.
|
||||
Thank you for your interest in contributing to Sim Studio! Our goal is to provide developers with a powerful, user-friendly platform for building, testing, and optimizing agentic workflows. We welcome contributions in all forms—from bug fixes and design improvements to brand-new features.
|
||||
|
||||
> **Project Overview:**
|
||||
> Sim is a monorepo using Turborepo, containing the main application (`apps/sim/`), documentation (`apps/docs/`), and shared packages (`packages/`). The main application is built with Next.js (app router), ReactFlow, Zustand, Shadcn, and Tailwind CSS. Please ensure your contributions follow our best practices for clarity, maintainability, and consistency.
|
||||
> Sim Studio is a monorepo using Turborepo, containing the main application (`apps/sim/`), documentation (`apps/docs/`), and shared packages (`packages/`). The main application is built with Next.js (app router), ReactFlow, Zustand, Shadcn, and Tailwind CSS. Please ensure your contributions follow our best practices for clarity, maintainability, and consistency.
|
||||
|
||||
---
|
||||
|
||||
@@ -15,6 +15,8 @@ Thank you for your interest in contributing to Sim! Our goal is to provide devel
|
||||
- [Commit Message Guidelines](#commit-message-guidelines)
|
||||
- [Local Development Setup](#local-development-setup)
|
||||
- [Adding New Blocks and Tools](#adding-new-blocks-and-tools)
|
||||
- [Local Storage Mode](#local-storage-mode)
|
||||
- [Standalone Build](#standalone-build)
|
||||
- [License](#license)
|
||||
- [Contributor License Agreement (CLA)](#contributor-license-agreement-cla)
|
||||
|
||||
@@ -55,7 +57,7 @@ We strive to keep our workflow as simple as possible. To contribute:
|
||||
```
|
||||
|
||||
7. **Create a Pull Request**
|
||||
Open a pull request against the `staging` branch on GitHub. Please provide a clear description of the changes and reference any relevant issues (e.g., `fixes #123`).
|
||||
Open a pull request against the `main` branch on GitHub. Please provide a clear description of the changes and reference any relevant issues (e.g., `fixes #123`).
|
||||
|
||||
---
|
||||
|
||||
@@ -83,7 +85,7 @@ If you discover a bug or have a feature request, please open an issue in our Git
|
||||
Before creating a pull request:
|
||||
|
||||
- **Ensure Your Branch Is Up-to-Date:**
|
||||
Rebase your branch onto the latest `staging` branch to prevent merge conflicts.
|
||||
Rebase your branch onto the latest `main` branch to prevent merge conflicts.
|
||||
- **Follow the Guidelines:**
|
||||
Make sure your changes are well-tested, follow our coding standards, and include relevant documentation if necessary.
|
||||
|
||||
@@ -130,7 +132,7 @@ To set up your local development environment:
|
||||
|
||||
### Option 1: Using NPM Package (Simplest)
|
||||
|
||||
The easiest way to run Sim locally is using our NPM package:
|
||||
The easiest way to run Sim Studio locally is using our NPM package:
|
||||
|
||||
```bash
|
||||
npx simstudio
|
||||
@@ -140,7 +142,7 @@ After running this command, open [http://localhost:3000/](http://localhost:3000/
|
||||
|
||||
#### Options
|
||||
|
||||
- `-p, --port <port>`: Specify the port to run Sim on (default: 3000)
|
||||
- `-p, --port <port>`: Specify the port to run Sim Studio on (default: 3000)
|
||||
- `--no-pull`: Skip pulling the latest Docker images
|
||||
|
||||
#### Requirements
|
||||
@@ -154,7 +156,7 @@ After running this command, open [http://localhost:3000/](http://localhost:3000/
|
||||
git clone https://github.com/<your-username>/sim.git
|
||||
cd sim
|
||||
|
||||
# Start Sim
|
||||
# Start Sim Studio
|
||||
docker compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
@@ -162,19 +164,15 @@ Access the application at [http://localhost:3000/](http://localhost:3000/)
|
||||
|
||||
#### Using Local Models
|
||||
|
||||
To use local models with Sim:
|
||||
To use local models with Sim Studio:
|
||||
|
||||
1. Install Ollama and pull models:
|
||||
1. Pull models using our helper script:
|
||||
|
||||
```bash
|
||||
# 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
|
||||
./apps/sim/scripts/ollama_docker.sh pull <model_name>
|
||||
```
|
||||
|
||||
2. Start Sim with local model support:
|
||||
2. Start Sim Studio with local model support:
|
||||
|
||||
```bash
|
||||
# With NVIDIA GPU support
|
||||
@@ -211,14 +209,13 @@ Dev Containers provide a consistent and easy-to-use development environment:
|
||||
|
||||
3. **Start Developing:**
|
||||
|
||||
- Run `bun run dev:full` in the terminal or use the `sim-start` alias
|
||||
- This starts both the main application and the realtime socket server
|
||||
- Run `bun run dev` in the terminal or use the `sim-start` alias
|
||||
- All dependencies and configurations are automatically set up
|
||||
- Your changes will be automatically hot-reloaded
|
||||
|
||||
4. **GitHub Codespaces:**
|
||||
- This setup also works with GitHub Codespaces if you prefer development in the browser
|
||||
- Just click "Code" → "Codespaces" → "Create codespace on staging"
|
||||
- Just click "Code" → "Codespaces" → "Create codespace on main"
|
||||
|
||||
### Option 4: Manual Setup
|
||||
|
||||
@@ -249,11 +246,9 @@ If you prefer not to use Docker or Dev Containers:
|
||||
4. **Run the Development Server:**
|
||||
|
||||
```bash
|
||||
bun run dev:full
|
||||
bun run dev
|
||||
```
|
||||
|
||||
This command starts both the main application and the realtime socket server required for full functionality.
|
||||
|
||||
5. **Make Your Changes and Test Locally.**
|
||||
|
||||
### Email Template Development
|
||||
@@ -280,7 +275,7 @@ When working on email templates, you can preview them using a local email previe
|
||||
|
||||
## Adding New Blocks and Tools
|
||||
|
||||
Sim is built in a modular fashion where blocks and tools extend the platform's functionality. To maintain consistency and quality, please follow the guidelines below when adding a new block or tool.
|
||||
Sim Studio is built in a modular fashion where blocks and tools extend the platform's functionality. To maintain consistency and quality, please follow the guidelines below when adding a new block or tool.
|
||||
|
||||
### Where to Add Your Code
|
||||
|
||||
@@ -305,8 +300,8 @@ In addition, you will need to update the registries:
|
||||
|
||||
```typescript:/apps/sim/blocks/blocks/pinecone.ts
|
||||
import { PineconeIcon } from '@/components/icons'
|
||||
import type { BlockConfig } from '@/blocks/types'
|
||||
import type { PineconeResponse } from '@/tools/pinecone/types'
|
||||
import { PineconeResponse } from '@/tools/pinecone/types'
|
||||
import { BlockConfig } from '../types'
|
||||
|
||||
export const PineconeBlock: BlockConfig<PineconeResponse> = {
|
||||
type: 'pinecone',
|
||||
@@ -317,56 +312,13 @@ In addition, you will need to update the registries:
|
||||
bgColor: '#123456',
|
||||
icon: PineconeIcon,
|
||||
|
||||
// If this block requires OAuth authentication
|
||||
provider: 'pinecone',
|
||||
|
||||
// Define subBlocks for the UI configuration
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown'
|
||||
required: true,
|
||||
options: [
|
||||
{ label: 'Generate Embeddings', id: 'generate' },
|
||||
{ label: 'Search Text', id: 'search_text' },
|
||||
],
|
||||
value: () => 'generate',
|
||||
},
|
||||
{
|
||||
id: 'apiKey',
|
||||
title: 'API Key',
|
||||
type: 'short-input'
|
||||
placeholder: 'Your Pinecone API key',
|
||||
password: true,
|
||||
required: true,
|
||||
},
|
||||
// Block configuration options
|
||||
],
|
||||
|
||||
tools: {
|
||||
access: ['pinecone_generate_embeddings', 'pinecone_search_text'],
|
||||
config: {
|
||||
tool: (params: Record<string, any>) => {
|
||||
switch (params.operation) {
|
||||
case 'generate':
|
||||
return 'pinecone_generate_embeddings'
|
||||
case 'search_text':
|
||||
return 'pinecone_search_text'
|
||||
default:
|
||||
throw new Error('Invalid operation selected')
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
inputs: {
|
||||
operation: { type: 'string', description: 'Operation to perform' },
|
||||
apiKey: { type: 'string', description: 'Pinecone API key' },
|
||||
searchQuery: { type: 'string', description: 'Search query text' },
|
||||
topK: { type: 'string', description: 'Number of results to return' },
|
||||
},
|
||||
|
||||
outputs: {
|
||||
matches: { type: 'any', description: 'Search results or generated embeddings' },
|
||||
data: { type: 'any', description: 'Response data from Pinecone' },
|
||||
usage: { type: 'any', description: 'API usage statistics' },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -374,7 +326,7 @@ In addition, you will need to update the registries:
|
||||
Add your block to the blocks registry (`/apps/sim/blocks/registry.ts`):
|
||||
|
||||
```typescript:/apps/sim/blocks/registry.ts
|
||||
import { PineconeBlock } from '@/blocks/blocks/pinecone'
|
||||
import { PineconeBlock } from './blocks/pinecone'
|
||||
|
||||
// Registry of all available blocks
|
||||
export const registry: Record<string, BlockConfig> = {
|
||||
@@ -414,8 +366,8 @@ In addition, you will need to update the registries:
|
||||
Your tool should export a constant with a naming convention of `{toolName}Tool`. The tool ID should follow the format `{provider}_{tool_name}`. For example:
|
||||
|
||||
```typescript:/apps/sim/tools/pinecone/fetch.ts
|
||||
import { ToolConfig, ToolResponse } from '@/tools/types'
|
||||
import { PineconeParams, PineconeResponse } from '@/tools/pinecone/types'
|
||||
import { ToolConfig, ToolResponse } from '../types'
|
||||
import { PineconeParams, PineconeResponse } from './types'
|
||||
|
||||
export const fetchTool: ToolConfig<PineconeParams, PineconeResponse> = {
|
||||
id: 'pinecone_fetch', // Follow the {provider}_{tool_name} format
|
||||
@@ -427,18 +379,7 @@ In addition, you will need to update the registries:
|
||||
provider: 'pinecone', // ID of the OAuth provider
|
||||
|
||||
params: {
|
||||
parameterName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm', // Controls parameter visibility
|
||||
description: 'Description of the parameter',
|
||||
},
|
||||
optionalParam: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Optional parameter only user can set',
|
||||
},
|
||||
// Tool parameters
|
||||
},
|
||||
request: {
|
||||
// Request configuration
|
||||
@@ -446,6 +387,9 @@ In addition, you will need to update the registries:
|
||||
transformResponse: async (response: Response) => {
|
||||
// Transform response
|
||||
},
|
||||
transformError: (error) => {
|
||||
// Handle errors
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -453,7 +397,7 @@ In addition, you will need to update the registries:
|
||||
Update the tools registry in `/apps/sim/tools/index.ts` to include your new tool:
|
||||
|
||||
```typescript:/apps/sim/tools/index.ts
|
||||
import { fetchTool, generateEmbeddingsTool, searchTextTool } from '/@tools/pinecone'
|
||||
import { fetchTool, generateEmbeddingsTool, searchTextTool } from './pinecone'
|
||||
// ... other imports
|
||||
|
||||
export const tools: Record<string, ToolConfig> = {
|
||||
@@ -485,57 +429,11 @@ Maintaining consistent naming across the codebase is critical for auto-generatio
|
||||
- **Tool Exports:** Should be named `{toolName}Tool` (e.g., `fetchTool`)
|
||||
- **Tool IDs:** Should follow the format `{provider}_{tool_name}` (e.g., `pinecone_fetch`)
|
||||
|
||||
### Parameter Visibility System
|
||||
|
||||
Sim implements a sophisticated parameter visibility system that controls how parameters are exposed to users and LLMs in agent workflows. Each parameter can have one of four visibility levels:
|
||||
|
||||
| Visibility | User Sees | LLM Sees | How It Gets Set |
|
||||
|-------------|-----------|----------|--------------------------------|
|
||||
| `user-only` | ✅ Yes | ❌ No | User provides in UI |
|
||||
| `user-or-llm` | ✅ Yes | ✅ Yes | User provides OR LLM generates |
|
||||
| `llm-only` | ❌ No | ✅ Yes | LLM generates only |
|
||||
| `hidden` | ❌ No | ❌ No | Application injects at runtime |
|
||||
|
||||
#### Visibility Guidelines
|
||||
|
||||
- **`user-or-llm`**: Use for core parameters that can be provided by users or intelligently filled by the LLM (e.g., search queries, email subjects)
|
||||
- **`user-only`**: Use for configuration parameters, API keys, and settings that only users should control (e.g., number of results, authentication credentials)
|
||||
- **`llm-only`**: Use for computed values that the LLM should handle internally (e.g., dynamic calculations, contextual data)
|
||||
- **`hidden`**: Use for system-level parameters injected at runtime (e.g., OAuth tokens, internal identifiers)
|
||||
|
||||
#### Example Implementation
|
||||
|
||||
```typescript
|
||||
params: {
|
||||
query: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm', // User can provide or LLM can generate
|
||||
description: 'Search query to execute',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only', // Only user provides this
|
||||
description: 'API key for authentication',
|
||||
},
|
||||
internalId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden', // System provides this at runtime
|
||||
description: 'Internal tracking identifier',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This visibility system ensures clean user interfaces while maintaining full flexibility for LLM-driven workflows.
|
||||
|
||||
### Guidelines & Best Practices
|
||||
|
||||
- **Code Style:** Follow the project's Biome configurations. Use meaningful variable names and small, focused functions.
|
||||
- **Code Style:** Follow the project's ESLint and Prettier configurations. Use meaningful variable names and small, focused functions.
|
||||
- **Documentation:** Clearly document the purpose, inputs, outputs, and any special behavior for your block/tool.
|
||||
- **Error Handling:** Implement robust error handling and provide user-friendly error messages.
|
||||
- **Parameter Visibility:** Always specify the appropriate visibility level for each parameter to ensure proper UI behavior and LLM integration.
|
||||
- **Testing:** Add unit or integration tests to verify your changes when possible.
|
||||
- **Commit Changes:** Update all related components and registries, and describe your changes in your pull request.
|
||||
|
||||
@@ -553,7 +451,7 @@ This project is licensed under the Apache License 2.0. By contributing, you agre
|
||||
|
||||
By contributing to this repository, you agree that your contributions are provided under the terms of the Apache License Version 2.0, as included in the LICENSE file of this repository.
|
||||
|
||||
In addition, by submitting your contributions, you grant Sim, Inc. ("The Licensor") a perpetual, irrevocable, worldwide, royalty-free, sublicensable right and license to:
|
||||
In addition, by submitting your contributions, you grant Sim Studio, Inc. ("The Licensor") a perpetual, irrevocable, worldwide, royalty-free, sublicensable right and license to:
|
||||
|
||||
- Use, copy, modify, distribute, publicly display, publicly perform, and prepare derivative works of your contributions.
|
||||
- Incorporate your contributions into other works or products.
|
||||
@@ -565,4 +463,4 @@ If you do not agree with these terms, you must not contribute your work to this
|
||||
|
||||
---
|
||||
|
||||
Thank you for taking the time to contribute to Sim. We truly appreciate your efforts and look forward to collaborating with you!
|
||||
Thank you for taking the time to contribute to Sim Studio. We truly appreciate your efforts and look forward to collaborating with you!
|
||||
|
||||
55
.github/PULL_REQUEST_TEMPLATE.md
vendored
55
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,25 +1,42 @@
|
||||
## Summary
|
||||
Brief description of what this PR does and why.
|
||||
## Description
|
||||
|
||||
Fixes #(issue)
|
||||
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.
|
||||
|
||||
## Type of Change
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature
|
||||
- [ ] Breaking change
|
||||
- [ ] Documentation
|
||||
- [ ] Other: ___________
|
||||
Fixes # (issue)
|
||||
|
||||
## Testing
|
||||
How has this been tested? What should reviewers focus on?
|
||||
## Type of change
|
||||
|
||||
## Checklist
|
||||
- [ ] Code follows project style guidelines
|
||||
- [ ] Self-reviewed my changes
|
||||
- [ ] Tests added/updated and passing
|
||||
- [ ] No new warnings introduced
|
||||
Please delete options that are not relevant.
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Documentation update
|
||||
- [ ] Security enhancement
|
||||
- [ ] Performance improvement
|
||||
- [ ] Code refactoring (no functional changes)
|
||||
|
||||
## How Has This Been Tested?
|
||||
|
||||
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.
|
||||
|
||||
## Checklist:
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] All tests pass locally and in CI (`bun run test`)
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
||||
- [ ] I have updated version numbers as needed (if needed)
|
||||
- [ ] I confirm that I have read and agree to the terms outlined in the [Contributor License Agreement (CLA)](./CONTRIBUTING.md#contributor-license-agreement-cla)
|
||||
|
||||
## Screenshots/Videos
|
||||
<!-- If applicable, add screenshots or videos to help explain your changes -->
|
||||
<!-- For UI changes, before/after screenshots are especially helpful -->
|
||||
## Security Considerations:
|
||||
|
||||
- [ ] My changes do not introduce any new security vulnerabilities
|
||||
- [ ] I have considered the security implications of my changes
|
||||
|
||||
## Additional Information:
|
||||
|
||||
Any additional information, configuration or data that might be necessary to reproduce the issue or use the feature.
|
||||
|
||||
4
.github/SECURITY.md
vendored
4
.github/SECURITY.md
vendored
@@ -8,11 +8,11 @@
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We take the security of Sim seriously. If you believe you've found a security vulnerability, please follow these steps:
|
||||
We take the security of Sim Studio seriously. If you believe you've found a security vulnerability, please follow these steps:
|
||||
|
||||
1. **Do not disclose the vulnerability publicly** or to any third parties.
|
||||
|
||||
2. **Email us directly** at security@sim.ai with details of the vulnerability.
|
||||
2. **Email us directly** at security@simstudio.ai with details of the vulnerability.
|
||||
|
||||
3. **Include the following information** in your report:
|
||||
|
||||
|
||||
66
.github/workflows/build.yml
vendored
Normal file
66
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Build and Publish Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ['v*']
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- dockerfile: ./docker/app.Dockerfile
|
||||
image: ghcr.io/simstudioai/simstudio
|
||||
- dockerfile: ./docker/db.Dockerfile
|
||||
image: ghcr.io/simstudioai/migrations
|
||||
- dockerfile: ./docker/realtime.Dockerfile
|
||||
image: ghcr.io/simstudioai/realtime
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ matrix.image }}
|
||||
tags: |
|
||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}.{{minor}}.{{patch}}
|
||||
type=sha,format=long
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
308
.github/workflows/ci.yml
vendored
308
.github/workflows/ci.yml
vendored
@@ -6,274 +6,72 @@ on:
|
||||
pull_request:
|
||||
branches: [main, staging]
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
test-build:
|
||||
test:
|
||||
name: Test and Build
|
||||
uses: ./.github/workflows/test-build.yml
|
||||
secrets: inherit
|
||||
|
||||
# Detect if this is a version release commit (e.g., "v0.5.24: ...")
|
||||
detect-version:
|
||||
name: Detect Version
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
|
||||
outputs:
|
||||
version: ${{ steps.extract.outputs.version }}
|
||||
is_release: ${{ steps.extract.outputs.is_release }}
|
||||
steps:
|
||||
- name: Extract version from commit message
|
||||
id: extract
|
||||
run: |
|
||||
COMMIT_MSG="${{ github.event.head_commit.message }}"
|
||||
# Only tag versions on main branch
|
||||
if [ "${{ github.ref }}" = "refs/heads/main" ] && [[ "$COMMIT_MSG" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+): ]]; then
|
||||
VERSION="${BASH_REMATCH[1]}"
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "is_release=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ Detected release commit: ${VERSION}"
|
||||
else
|
||||
echo "version=" >> $GITHUB_OUTPUT
|
||||
echo "is_release=false" >> $GITHUB_OUTPUT
|
||||
echo "ℹ️ Not a release commit"
|
||||
fi
|
||||
|
||||
# Build AMD64 images and push to ECR immediately (+ GHCR for main)
|
||||
build-amd64:
|
||||
name: Build AMD64
|
||||
needs: [test-build, detect-version]
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- dockerfile: ./docker/app.Dockerfile
|
||||
ghcr_image: ghcr.io/simstudioai/simstudio
|
||||
ecr_repo_secret: ECR_APP
|
||||
- dockerfile: ./docker/db.Dockerfile
|
||||
ghcr_image: ghcr.io/simstudioai/migrations
|
||||
ecr_repo_secret: ECR_MIGRATIONS
|
||||
- dockerfile: ./docker/realtime.Dockerfile
|
||||
ghcr_image: ghcr.io/simstudioai/realtime
|
||||
ecr_repo_secret: ECR_REALTIME
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ github.ref == 'refs/heads/main' && secrets.AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }}
|
||||
aws-region: ${{ github.ref == 'refs/heads/main' && secrets.AWS_REGION || secrets.STAGING_AWS_REGION }}
|
||||
|
||||
- name: Login to Amazon ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@v2
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: useblacksmith/setup-docker-builder@v1
|
||||
|
||||
- name: Generate tags
|
||||
id: meta
|
||||
run: |
|
||||
ECR_REGISTRY="${{ steps.login-ecr.outputs.registry }}"
|
||||
ECR_REPO="${{ secrets[matrix.ecr_repo_secret] }}"
|
||||
GHCR_IMAGE="${{ matrix.ghcr_image }}"
|
||||
|
||||
# ECR tags (always build for ECR)
|
||||
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||
ECR_TAG="latest"
|
||||
else
|
||||
ECR_TAG="staging"
|
||||
fi
|
||||
ECR_IMAGE="${ECR_REGISTRY}/${ECR_REPO}:${ECR_TAG}"
|
||||
|
||||
# Build tags list
|
||||
TAGS="${ECR_IMAGE}"
|
||||
|
||||
# Add GHCR tags only for main branch
|
||||
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||
GHCR_AMD64="${GHCR_IMAGE}:latest-amd64"
|
||||
GHCR_SHA="${GHCR_IMAGE}:${{ github.sha }}-amd64"
|
||||
TAGS="${TAGS},$GHCR_AMD64,$GHCR_SHA"
|
||||
|
||||
# Add version tag if this is a release commit
|
||||
if [ "${{ needs.detect-version.outputs.is_release }}" = "true" ]; then
|
||||
VERSION="${{ needs.detect-version.outputs.version }}"
|
||||
GHCR_VERSION="${GHCR_IMAGE}:${VERSION}-amd64"
|
||||
TAGS="${TAGS},$GHCR_VERSION"
|
||||
echo "📦 Adding version tag: ${VERSION}-amd64"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push images
|
||||
uses: useblacksmith/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
provenance: false
|
||||
sbom: false
|
||||
|
||||
# Build ARM64 images for GHCR (main branch only, runs in parallel)
|
||||
build-ghcr-arm64:
|
||||
name: Build ARM64 (GHCR Only)
|
||||
needs: [test-build, detect-version]
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404-arm
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- dockerfile: ./docker/app.Dockerfile
|
||||
image: ghcr.io/simstudioai/simstudio
|
||||
- dockerfile: ./docker/db.Dockerfile
|
||||
image: ghcr.io/simstudioai/migrations
|
||||
- dockerfile: ./docker/realtime.Dockerfile
|
||||
image: ghcr.io/simstudioai/realtime
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
bun-version: latest
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: useblacksmith/setup-docker-builder@v1
|
||||
|
||||
- name: Generate ARM64 tags
|
||||
id: meta
|
||||
run: |
|
||||
IMAGE="${{ matrix.image }}"
|
||||
TAGS="${IMAGE}:latest-arm64,${IMAGE}:${{ github.sha }}-arm64"
|
||||
|
||||
# Add version tag if this is a release commit
|
||||
if [ "${{ needs.detect-version.outputs.is_release }}" = "true" ]; then
|
||||
VERSION="${{ needs.detect-version.outputs.version }}"
|
||||
TAGS="${TAGS},${IMAGE}:${VERSION}-arm64"
|
||||
echo "📦 Adding version tag: ${VERSION}-arm64"
|
||||
fi
|
||||
|
||||
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push ARM64 to GHCR
|
||||
uses: useblacksmith/build-push-action@v2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
provenance: false
|
||||
sbom: false
|
||||
node-version: latest
|
||||
|
||||
# Create GHCR multi-arch manifests (only for main, after both builds)
|
||||
create-ghcr-manifests:
|
||||
name: Create GHCR Manifests
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
needs: [build-amd64, build-ghcr-arm64, detect-version]
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
permissions:
|
||||
packages: write
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- image: ghcr.io/simstudioai/simstudio
|
||||
- image: ghcr.io/simstudioai/migrations
|
||||
- image: ghcr.io/simstudioai/realtime
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Run tests with coverage
|
||||
env:
|
||||
NODE_OPTIONS: '--no-warnings'
|
||||
NEXT_PUBLIC_APP_URL: 'https://www.simstudio.ai'
|
||||
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
|
||||
run: bun run test
|
||||
|
||||
- name: Build application
|
||||
env:
|
||||
NODE_OPTIONS: '--no-warnings'
|
||||
NEXT_PUBLIC_APP_URL: 'https://www.simstudio.ai'
|
||||
STRIPE_SECRET_KEY: 'dummy_key_for_ci_only'
|
||||
STRIPE_WEBHOOK_SECRET: 'dummy_secret_for_ci_only'
|
||||
RESEND_API_KEY: 'dummy_key_for_ci_only'
|
||||
AWS_REGION: 'us-west-2'
|
||||
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
|
||||
run: bun run build
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
directory: ./apps/sim/coverage
|
||||
fail_ci_if_error: false
|
||||
verbose: true
|
||||
|
||||
migrations:
|
||||
name: Apply Database Migrations
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
|
||||
needs: test
|
||||
steps:
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
bun-version: latest
|
||||
|
||||
- name: Create and push manifests
|
||||
run: |
|
||||
IMAGE_BASE="${{ matrix.image }}"
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
# Create latest manifest
|
||||
docker manifest create "${IMAGE_BASE}:latest" \
|
||||
"${IMAGE_BASE}:latest-amd64" \
|
||||
"${IMAGE_BASE}:latest-arm64"
|
||||
docker manifest push "${IMAGE_BASE}:latest"
|
||||
|
||||
# Create SHA manifest
|
||||
docker manifest create "${IMAGE_BASE}:${{ github.sha }}" \
|
||||
"${IMAGE_BASE}:${{ github.sha }}-amd64" \
|
||||
"${IMAGE_BASE}:${{ github.sha }}-arm64"
|
||||
docker manifest push "${IMAGE_BASE}:${{ github.sha }}"
|
||||
|
||||
# Create version manifest if this is a release commit
|
||||
if [ "${{ needs.detect-version.outputs.is_release }}" = "true" ]; then
|
||||
VERSION="${{ needs.detect-version.outputs.version }}"
|
||||
echo "📦 Creating version manifest: ${VERSION}"
|
||||
docker manifest create "${IMAGE_BASE}:${VERSION}" \
|
||||
"${IMAGE_BASE}:${VERSION}-amd64" \
|
||||
"${IMAGE_BASE}:${VERSION}-arm64"
|
||||
docker manifest push "${IMAGE_BASE}:${VERSION}"
|
||||
fi
|
||||
|
||||
# Check if docs changed
|
||||
check-docs-changes:
|
||||
name: Check Docs Changes
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
outputs:
|
||||
docs_changed: ${{ steps.filter.outputs.docs }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2 # Need at least 2 commits to detect changes
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
docs:
|
||||
- 'apps/docs/content/docs/en/**'
|
||||
- 'apps/sim/scripts/process-docs.ts'
|
||||
- 'apps/sim/lib/chunkers/**'
|
||||
|
||||
# Process docs embeddings (only when docs change, after ECR images are pushed)
|
||||
process-docs:
|
||||
name: Process Docs
|
||||
needs: [build-amd64, check-docs-changes]
|
||||
if: needs.check-docs-changes.outputs.docs_changed == 'true'
|
||||
uses: ./.github/workflows/docs-embeddings.yml
|
||||
secrets: inherit
|
||||
- name: Apply migrations
|
||||
working-directory: ./apps/sim
|
||||
env:
|
||||
DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || secrets.STAGING_DATABASE_URL }}
|
||||
run: bunx drizzle-kit push
|
||||
|
||||
46
.github/workflows/docs-embeddings.yml
vendored
46
.github/workflows/docs-embeddings.yml
vendored
@@ -1,46 +0,0 @@
|
||||
name: Process Docs Embeddings
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
|
||||
jobs:
|
||||
process-docs-embeddings:
|
||||
name: Process Documentation Embeddings
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
if: github.ref == 'refs/heads/main'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.3
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Process docs embeddings
|
||||
working-directory: ./apps/sim
|
||||
env:
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
run: bun run scripts/process-docs.ts --clear
|
||||
180
.github/workflows/i18n.yml
vendored
180
.github/workflows/i18n.yml
vendored
@@ -1,180 +0,0 @@
|
||||
name: 'Auto-translate Documentation'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ staging ]
|
||||
paths:
|
||||
- 'apps/docs/content/docs/en/**'
|
||||
- 'apps/docs/i18n.json'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
translate:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: github.actor != 'github-actions[bot]' # Prevent infinite loops
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.3
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Run Lingo.dev translations
|
||||
env:
|
||||
LINGODOTDEV_API_KEY: ${{ secrets.LINGODOTDEV_API_KEY }}
|
||||
run: |
|
||||
cd apps/docs
|
||||
bunx lingo.dev@latest i18n
|
||||
|
||||
- name: Check for translation changes
|
||||
id: changes
|
||||
run: |
|
||||
cd apps/docs
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
|
||||
if [ -n "$(git status --porcelain content/docs)" ]; then
|
||||
echo "changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Create Pull Request with translations
|
||||
if: steps.changes.outputs.changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
commit-message: "feat(i18n): update translations"
|
||||
title: "feat(i18n): update translations"
|
||||
body: |
|
||||
## Summary
|
||||
Automated translation updates triggered by changes to documentation.
|
||||
|
||||
This PR was automatically created after content changes were made, updating translations for all supported languages using Lingo.dev AI translation engine.
|
||||
|
||||
**Original trigger**: ${{ github.event.head_commit.message }}
|
||||
**Commit**: ${{ github.sha }}
|
||||
**Workflow**: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
## Type of Change
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature
|
||||
- [ ] Breaking change
|
||||
- [x] Documentation
|
||||
- [ ] Other: ___________
|
||||
|
||||
## Testing
|
||||
This PR includes automated translations for modified English documentation content:
|
||||
- 🇪🇸 Spanish (es) translations
|
||||
- 🇫🇷 French (fr) translations
|
||||
- 🇨🇳 Chinese (zh) translations
|
||||
- 🇯🇵 Japanese (ja) translations
|
||||
- 🇩🇪 German (de) translations
|
||||
|
||||
**What reviewers should focus on:**
|
||||
- Verify translated content accuracy and context
|
||||
- Check that all links and references work correctly in translated versions
|
||||
- Ensure formatting, code blocks, and structure are preserved
|
||||
- Validate that technical terms are appropriately translated
|
||||
|
||||
## Checklist
|
||||
- [x] Code follows project style guidelines (automated translation)
|
||||
- [x] Self-reviewed my changes (automated process)
|
||||
- [ ] Tests added/updated and passing
|
||||
- [x] No new warnings introduced
|
||||
- [x] I confirm that I have read and agree to the terms outlined in the [Contributor License Agreement (CLA)](./CONTRIBUTING.md#contributor-license-agreement-cla)
|
||||
|
||||
## Screenshots/Videos
|
||||
<!-- Translation changes are text-based - no visual changes expected -->
|
||||
<!-- Reviewers should check the documentation site renders correctly for all languages -->
|
||||
branch: auto-translate/staging-merge-${{ github.run_id }}
|
||||
base: staging
|
||||
labels: |
|
||||
i18n
|
||||
|
||||
verify-translations:
|
||||
needs: translate
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: always() # Run even if translation fails
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: staging
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.3
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd apps/docs
|
||||
bun install --frozen-lockfile
|
||||
|
||||
- name: Build documentation to verify translations
|
||||
run: |
|
||||
cd apps/docs
|
||||
bun run build
|
||||
|
||||
- name: Report translation status
|
||||
run: |
|
||||
cd apps/docs
|
||||
echo "## Translation Status Report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Triggered by merge to staging branch**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
en_count=$(find content/docs/en -name "*.mdx" | wc -l)
|
||||
es_count=$(find content/docs/es -name "*.mdx" 2>/dev/null | wc -l || echo 0)
|
||||
fr_count=$(find content/docs/fr -name "*.mdx" 2>/dev/null | wc -l || echo 0)
|
||||
zh_count=$(find content/docs/zh -name "*.mdx" 2>/dev/null | wc -l || echo 0)
|
||||
ja_count=$(find content/docs/ja -name "*.mdx" 2>/dev/null | wc -l || echo 0)
|
||||
de_count=$(find content/docs/de -name "*.mdx" 2>/dev/null | wc -l || echo 0)
|
||||
|
||||
es_percentage=$((es_count * 100 / en_count))
|
||||
fr_percentage=$((fr_count * 100 / en_count))
|
||||
zh_percentage=$((zh_count * 100 / en_count))
|
||||
ja_percentage=$((ja_count * 100 / en_count))
|
||||
de_percentage=$((de_count * 100 / en_count))
|
||||
|
||||
echo "### Coverage Statistics" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **🇬🇧 English**: $en_count files (source)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **🇪🇸 Spanish**: $es_count/$en_count files ($es_percentage%)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **🇫🇷 French**: $fr_count/$en_count files ($fr_percentage%)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **🇨🇳 Chinese**: $zh_count/$en_count files ($zh_percentage%)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **🇯🇵 Japanese**: $ja_count/$en_count files ($ja_percentage%)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **🇩🇪 German**: $de_count/$en_count files ($de_percentage%)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "🔄 **Auto-translation PR**: Check for new pull request with updated translations" >> $GITHUB_STEP_SUMMARY
|
||||
181
.github/workflows/images.yml
vendored
181
.github/workflows/images.yml
vendored
@@ -1,181 +0,0 @@
|
||||
name: Build and Push Images
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
build-amd64:
|
||||
name: Build AMD64
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- dockerfile: ./docker/app.Dockerfile
|
||||
ghcr_image: ghcr.io/simstudioai/simstudio
|
||||
ecr_repo_secret: ECR_APP
|
||||
- dockerfile: ./docker/db.Dockerfile
|
||||
ghcr_image: ghcr.io/simstudioai/migrations
|
||||
ecr_repo_secret: ECR_MIGRATIONS
|
||||
- dockerfile: ./docker/realtime.Dockerfile
|
||||
ghcr_image: ghcr.io/simstudioai/realtime
|
||||
ecr_repo_secret: ECR_REALTIME
|
||||
outputs:
|
||||
registry: ${{ steps.login-ecr.outputs.registry }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ github.ref == 'refs/heads/main' && secrets.AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }}
|
||||
aws-region: ${{ github.ref == 'refs/heads/main' && secrets.AWS_REGION || secrets.STAGING_AWS_REGION }}
|
||||
|
||||
- name: Login to Amazon ECR
|
||||
id: login-ecr
|
||||
uses: aws-actions/amazon-ecr-login@v2
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GHCR
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: useblacksmith/setup-docker-builder@v1
|
||||
|
||||
- name: Generate tags
|
||||
id: meta
|
||||
run: |
|
||||
ECR_REGISTRY="${{ steps.login-ecr.outputs.registry }}"
|
||||
ECR_REPO="${{ secrets[matrix.ecr_repo_secret] }}"
|
||||
GHCR_IMAGE="${{ matrix.ghcr_image }}"
|
||||
|
||||
# ECR tags (always build for ECR)
|
||||
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||
ECR_TAG="latest"
|
||||
else
|
||||
ECR_TAG="staging"
|
||||
fi
|
||||
ECR_IMAGE="${ECR_REGISTRY}/${ECR_REPO}:${ECR_TAG}"
|
||||
|
||||
# Build tags list
|
||||
TAGS="${ECR_IMAGE}"
|
||||
|
||||
# Add GHCR tags only for main branch
|
||||
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||
GHCR_AMD64="${GHCR_IMAGE}:latest-amd64"
|
||||
GHCR_SHA="${GHCR_IMAGE}:${{ github.sha }}-amd64"
|
||||
TAGS="${TAGS},$GHCR_AMD64,$GHCR_SHA"
|
||||
fi
|
||||
|
||||
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push images
|
||||
uses: useblacksmith/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
provenance: false
|
||||
sbom: false
|
||||
|
||||
build-ghcr-arm64:
|
||||
name: Build ARM64 (GHCR Only)
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404-arm
|
||||
if: github.ref == 'refs/heads/main'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- dockerfile: ./docker/app.Dockerfile
|
||||
image: ghcr.io/simstudioai/simstudio
|
||||
- dockerfile: ./docker/db.Dockerfile
|
||||
image: ghcr.io/simstudioai/migrations
|
||||
- dockerfile: ./docker/realtime.Dockerfile
|
||||
image: ghcr.io/simstudioai/realtime
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: useblacksmith/setup-docker-builder@v1
|
||||
|
||||
- name: Generate ARM64 tags
|
||||
id: meta
|
||||
run: |
|
||||
IMAGE="${{ matrix.image }}"
|
||||
echo "tags=${IMAGE}:latest-arm64,${IMAGE}:${{ github.sha }}-arm64" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push ARM64 to GHCR
|
||||
uses: useblacksmith/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
provenance: false
|
||||
sbom: false
|
||||
|
||||
create-ghcr-manifests:
|
||||
name: Create GHCR Manifests
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
needs: [build-amd64, build-ghcr-arm64]
|
||||
if: github.ref == 'refs/heads/main'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- image: ghcr.io/simstudioai/simstudio
|
||||
- image: ghcr.io/simstudioai/migrations
|
||||
- image: ghcr.io/simstudioai/realtime
|
||||
|
||||
steps:
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create and push manifests
|
||||
run: |
|
||||
IMAGE_BASE="${{ matrix.image }}"
|
||||
|
||||
# Create latest manifest
|
||||
docker manifest create "${IMAGE_BASE}:latest" \
|
||||
"${IMAGE_BASE}:latest-amd64" \
|
||||
"${IMAGE_BASE}:latest-arm64"
|
||||
docker manifest push "${IMAGE_BASE}:latest"
|
||||
|
||||
# Create SHA manifest
|
||||
docker manifest create "${IMAGE_BASE}:${{ github.sha }}" \
|
||||
"${IMAGE_BASE}:${{ github.sha }}-amd64" \
|
||||
"${IMAGE_BASE}:${{ github.sha }}-arm64"
|
||||
docker manifest push "${IMAGE_BASE}:${{ github.sha }}"
|
||||
39
.github/workflows/migrations.yml
vendored
39
.github/workflows/migrations.yml
vendored
@@ -1,39 +0,0 @@
|
||||
name: Database Migrations
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
migrate:
|
||||
name: Apply Database Migrations
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.3
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Apply migrations
|
||||
working-directory: ./packages/db
|
||||
env:
|
||||
DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || secrets.STAGING_DATABASE_URL }}
|
||||
run: bunx drizzle-kit migrate --config=./drizzle.config.ts
|
||||
17
.github/workflows/publish-cli.yml
vendored
17
.github/workflows/publish-cli.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
publish-npm:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.3
|
||||
bun-version: latest
|
||||
|
||||
- name: Setup Node.js for npm publishing
|
||||
uses: actions/setup-node@v4
|
||||
@@ -24,20 +24,9 @@ jobs:
|
||||
node-version: '18'
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: packages/cli
|
||||
run: bun install --frozen-lockfile
|
||||
run: bun install
|
||||
|
||||
- name: Build package
|
||||
working-directory: packages/cli
|
||||
|
||||
4
.github/workflows/publish-python-sdk.yml
vendored
4
.github/workflows/publish-python-sdk.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
publish-pypi:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -84,6 +84,6 @@ jobs:
|
||||
```
|
||||
|
||||
### Documentation
|
||||
See the [README](https://github.com/simstudioai/sim/tree/main/packages/python-sdk) or the [docs](https://docs.sim.ai/sdks/python) for more information.
|
||||
See the [README](https://github.com/simstudio/sim/tree/main/packages/python-sdk) for usage instructions.
|
||||
draft: false
|
||||
prerelease: false
|
||||
22
.github/workflows/publish-ts-sdk.yml
vendored
22
.github/workflows/publish-ts-sdk.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
publish-npm:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -16,27 +16,17 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.3
|
||||
bun-version: latest
|
||||
|
||||
- name: Setup Node.js for npm publishing
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version: '18'
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
|
||||
- name: Cache Bun dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
**/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
working-directory: packages/ts-sdk
|
||||
run: bun install
|
||||
|
||||
- name: Run tests
|
||||
working-directory: packages/ts-sdk
|
||||
@@ -90,6 +80,6 @@ jobs:
|
||||
```
|
||||
|
||||
### Documentation
|
||||
See the [README](https://github.com/simstudioai/sim/tree/main/packages/ts-sdk) or the [docs](https://docs.sim.ai/sdks/typescript) for more information.
|
||||
See the [README](https://github.com/simstudio/sim/tree/main/packages/ts-sdk) for usage instructions.
|
||||
draft: false
|
||||
prerelease: false
|
||||
82
.github/workflows/test-build.yml
vendored
82
.github/workflows/test-build.yml
vendored
@@ -1,82 +0,0 @@
|
||||
name: Test and Build
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test-build:
|
||||
name: Test and Build
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.3
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- name: Mount Bun cache (Sticky Disk)
|
||||
uses: useblacksmith/stickydisk@v1
|
||||
with:
|
||||
key: ${{ github.repository }}-bun-cache
|
||||
path: ~/.bun/install/cache
|
||||
|
||||
- name: Mount node_modules (Sticky Disk)
|
||||
uses: useblacksmith/stickydisk@v1
|
||||
with:
|
||||
key: ${{ github.repository }}-node-modules
|
||||
path: ./node_modules
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Lint code
|
||||
run: bun run lint:check
|
||||
|
||||
- name: Run tests with coverage
|
||||
env:
|
||||
NODE_OPTIONS: '--no-warnings'
|
||||
NEXT_PUBLIC_APP_URL: 'https://www.sim.ai'
|
||||
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/simstudio'
|
||||
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
|
||||
run: bun run test
|
||||
|
||||
- name: Check schema and migrations are in sync
|
||||
working-directory: packages/db
|
||||
run: |
|
||||
bunx drizzle-kit generate --config=./drizzle.config.ts
|
||||
if [ -n "$(git status --porcelain ./migrations)" ]; then
|
||||
echo "❌ Schema and migrations are out of sync!"
|
||||
echo "Run 'cd packages/db && bunx drizzle-kit generate' and commit the new migrations."
|
||||
git status --porcelain ./migrations
|
||||
git diff ./migrations
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Schema and migrations are in sync"
|
||||
|
||||
- name: Build application
|
||||
env:
|
||||
NODE_OPTIONS: '--no-warnings'
|
||||
NEXT_PUBLIC_APP_URL: 'https://www.sim.ai'
|
||||
DATABASE_URL: 'postgresql://postgres:postgres@localhost:5432/simstudio'
|
||||
STRIPE_SECRET_KEY: 'dummy_key_for_ci_only'
|
||||
STRIPE_WEBHOOK_SECRET: 'dummy_secret_for_ci_only'
|
||||
RESEND_API_KEY: 'dummy_key_for_ci_only'
|
||||
AWS_REGION: 'us-west-2'
|
||||
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
|
||||
run: bun run build
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
directory: ./apps/sim/coverage
|
||||
fail_ci_if_error: false
|
||||
verbose: true
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -46,7 +46,7 @@ sim-standalone.tar.gz
|
||||
next-env.d.ts
|
||||
|
||||
# cursorrules
|
||||
# .cursorrules
|
||||
.cursorrules
|
||||
|
||||
# docs
|
||||
/apps/docs/.source
|
||||
@@ -65,11 +65,4 @@ start-collector.sh
|
||||
.turbo
|
||||
|
||||
# VSCode
|
||||
.vscode
|
||||
|
||||
# IntelliJ
|
||||
.idea
|
||||
|
||||
## Helm Chart Tests
|
||||
helm/sim/test
|
||||
i18n.cache
|
||||
.vscode
|
||||
@@ -1 +1 @@
|
||||
bunx lint-staged
|
||||
bun lint
|
||||
295
CLAUDE.md
295
CLAUDE.md
@@ -1,295 +0,0 @@
|
||||
# Sim Studio Development Guidelines
|
||||
|
||||
You are a professional software engineer. All code must follow best practices: accurate, readable, clean, and efficient.
|
||||
|
||||
## Global Standards
|
||||
|
||||
- **Logging**: Import `createLogger` from `@sim/logger`. Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`
|
||||
- **Comments**: Use TSDoc for documentation. No `====` separators. No non-TSDoc comments
|
||||
- **Styling**: Never update global styles. Keep all styling local to components
|
||||
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Principles
|
||||
1. Single Responsibility: Each component, hook, store has one clear purpose
|
||||
2. Composition Over Complexity: Break down complex logic into smaller pieces
|
||||
3. Type Safety First: TypeScript interfaces for all props, state, return types
|
||||
4. Predictable State: Zustand for global state, useState for UI-only concerns
|
||||
|
||||
### Root Structure
|
||||
```
|
||||
apps/sim/
|
||||
├── app/ # Next.js app router (pages, API routes)
|
||||
├── blocks/ # Block definitions and registry
|
||||
├── components/ # Shared UI (emcn/, ui/)
|
||||
├── executor/ # Workflow execution engine
|
||||
├── hooks/ # Shared hooks (queries/, selectors/)
|
||||
├── lib/ # App-wide utilities
|
||||
├── providers/ # LLM provider integrations
|
||||
├── stores/ # Zustand stores
|
||||
├── tools/ # Tool definitions
|
||||
└── triggers/ # Trigger definitions
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
- Components: PascalCase (`WorkflowList`)
|
||||
- Hooks: `use` prefix (`useWorkflowOperations`)
|
||||
- Files: kebab-case (`workflow-list.tsx`)
|
||||
- Stores: `stores/feature/store.ts`
|
||||
- Constants: SCREAMING_SNAKE_CASE
|
||||
- Interfaces: PascalCase with suffix (`WorkflowListProps`)
|
||||
|
||||
## Imports
|
||||
|
||||
**Always use absolute imports.** Never use relative imports.
|
||||
|
||||
```typescript
|
||||
// ✓ Good
|
||||
import { useWorkflowStore } from '@/stores/workflows/store'
|
||||
|
||||
// ✗ Bad
|
||||
import { useWorkflowStore } from '../../../stores/workflows/store'
|
||||
```
|
||||
|
||||
Use barrel exports (`index.ts`) when a folder has 3+ exports.
|
||||
|
||||
### Import Order
|
||||
1. React/core libraries
|
||||
2. External libraries
|
||||
3. UI components (`@/components/emcn`, `@/components/ui`)
|
||||
4. Utilities (`@/lib/...`)
|
||||
5. Stores (`@/stores/...`)
|
||||
6. Feature imports
|
||||
7. CSS imports
|
||||
|
||||
Use `import type { X }` for type-only imports.
|
||||
|
||||
## TypeScript
|
||||
|
||||
1. No `any` - Use proper types or `unknown` with type guards
|
||||
2. Always define props interface for components
|
||||
3. `as const` for constant objects/arrays
|
||||
4. Explicit ref types: `useRef<HTMLDivElement>(null)`
|
||||
|
||||
## Components
|
||||
|
||||
```typescript
|
||||
'use client' // Only if using hooks
|
||||
|
||||
const CONFIG = { SPACING: 8 } as const
|
||||
|
||||
interface ComponentProps {
|
||||
requiredProp: string
|
||||
optionalProp?: boolean
|
||||
}
|
||||
|
||||
export function Component({ requiredProp, optionalProp = false }: ComponentProps) {
|
||||
// Order: refs → external hooks → store hooks → custom hooks → state → useMemo → useCallback → useEffect → return
|
||||
}
|
||||
```
|
||||
|
||||
Extract when: 50+ lines, used in 2+ files, or has own state/logic. Keep inline when: < 10 lines, single use, purely presentational.
|
||||
|
||||
## Hooks
|
||||
|
||||
```typescript
|
||||
interface UseFeatureProps { id: string }
|
||||
|
||||
export function useFeature({ id }: UseFeatureProps) {
|
||||
const idRef = useRef(id)
|
||||
const [data, setData] = useState<Data | null>(null)
|
||||
|
||||
useEffect(() => { idRef.current = id }, [id])
|
||||
|
||||
const fetchData = useCallback(async () => { ... }, []) // Empty deps when using refs
|
||||
|
||||
return { data, fetchData }
|
||||
}
|
||||
```
|
||||
|
||||
## Zustand Stores
|
||||
|
||||
Stores live in `stores/`. Complex stores split into `store.ts` + `types.ts`.
|
||||
|
||||
```typescript
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
|
||||
const initialState = { items: [] as Item[] }
|
||||
|
||||
export const useFeatureStore = create<FeatureState>()(
|
||||
devtools(
|
||||
(set, get) => ({
|
||||
...initialState,
|
||||
setItems: (items) => set({ items }),
|
||||
reset: () => set(initialState),
|
||||
}),
|
||||
{ name: 'feature-store' }
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Use `devtools` middleware. Use `persist` only when data should survive reload with `partialize` to persist only necessary state.
|
||||
|
||||
## React Query
|
||||
|
||||
All React Query hooks live in `hooks/queries/`.
|
||||
|
||||
```typescript
|
||||
export const entityKeys = {
|
||||
all: ['entity'] as const,
|
||||
list: (workspaceId?: string) => [...entityKeys.all, 'list', workspaceId ?? ''] as const,
|
||||
}
|
||||
|
||||
export function useEntityList(workspaceId?: string) {
|
||||
return useQuery({
|
||||
queryKey: entityKeys.list(workspaceId),
|
||||
queryFn: () => fetchEntities(workspaceId as string),
|
||||
enabled: Boolean(workspaceId),
|
||||
staleTime: 60 * 1000,
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
Use Tailwind only, no inline styles. Use `cn()` from `@/lib/utils` for conditional classes.
|
||||
|
||||
```typescript
|
||||
<div className={cn('base-classes', isActive && 'active-classes')} />
|
||||
```
|
||||
|
||||
## EMCN Components
|
||||
|
||||
Import from `@/components/emcn`, never from subpaths (except CSS files). Use CVA when 2+ variants exist.
|
||||
|
||||
## Testing
|
||||
|
||||
Use Vitest. Test files: `feature.ts` → `feature.test.ts`
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
|
||||
// Mocks BEFORE imports
|
||||
vi.mock('@sim/db', () => ({ db: { select: vi.fn() } }))
|
||||
|
||||
// Imports AFTER mocks
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { createSession, loggerMock } from '@sim/testing'
|
||||
|
||||
describe('feature', () => {
|
||||
beforeEach(() => vi.clearAllMocks())
|
||||
it.concurrent('runs in parallel', () => { ... })
|
||||
})
|
||||
```
|
||||
|
||||
Use `@sim/testing` factories over manual test data.
|
||||
|
||||
## Utils Rules
|
||||
|
||||
- Never create `utils.ts` for single consumer - inline it
|
||||
- Create `utils.ts` when 2+ files need the same helper
|
||||
- Check existing sources in `lib/` before duplicating
|
||||
|
||||
## Adding Integrations
|
||||
|
||||
New integrations require: **Tools** → **Block** → **Icon** → (optional) **Trigger**
|
||||
|
||||
Always look up the service's API docs first.
|
||||
|
||||
### 1. Tools (`tools/{service}/`)
|
||||
|
||||
```
|
||||
tools/{service}/
|
||||
├── index.ts # Barrel export
|
||||
├── types.ts # Params/response types
|
||||
└── {action}.ts # Tool implementation
|
||||
```
|
||||
|
||||
**Tool structure:**
|
||||
```typescript
|
||||
export const serviceTool: ToolConfig<Params, Response> = {
|
||||
id: 'service_action',
|
||||
name: 'Service Action',
|
||||
description: '...',
|
||||
version: '1.0.0',
|
||||
oauth: { required: true, provider: 'service' },
|
||||
params: { /* ... */ },
|
||||
request: { url: '/api/tools/service/action', method: 'POST', ... },
|
||||
transformResponse: async (response) => { /* ... */ },
|
||||
outputs: { /* ... */ },
|
||||
}
|
||||
```
|
||||
|
||||
Register in `tools/registry.ts`.
|
||||
|
||||
### 2. Block (`blocks/blocks/{service}.ts`)
|
||||
|
||||
```typescript
|
||||
export const ServiceBlock: BlockConfig = {
|
||||
type: 'service',
|
||||
name: 'Service',
|
||||
description: '...',
|
||||
category: 'tools',
|
||||
bgColor: '#hexcolor',
|
||||
icon: ServiceIcon,
|
||||
subBlocks: [ /* see SubBlock Properties */ ],
|
||||
tools: { access: ['service_action'], config: { tool: (p) => `service_${p.operation}` } },
|
||||
inputs: { /* ... */ },
|
||||
outputs: { /* ... */ },
|
||||
}
|
||||
```
|
||||
|
||||
Register in `blocks/registry.ts` (alphabetically).
|
||||
|
||||
**SubBlock Properties:**
|
||||
```typescript
|
||||
{
|
||||
id: 'field', title: 'Label', type: 'short-input', placeholder: '...',
|
||||
required: true, // or condition object
|
||||
condition: { field: 'op', value: 'send' }, // show/hide
|
||||
dependsOn: ['credential'], // clear when dep changes
|
||||
mode: 'basic', // 'basic' | 'advanced' | 'both' | 'trigger'
|
||||
}
|
||||
```
|
||||
|
||||
**condition examples:**
|
||||
- `{ field: 'op', value: 'send' }` - show when op === 'send'
|
||||
- `{ field: 'op', value: ['a','b'] }` - show when op is 'a' OR 'b'
|
||||
- `{ field: 'op', value: 'x', not: true }` - show when op !== 'x'
|
||||
- `{ field: 'op', value: 'x', not: true, and: { field: 'type', value: 'dm', not: true } }` - complex
|
||||
|
||||
**dependsOn:** `['field']` or `{ all: ['a'], any: ['b', 'c'] }`
|
||||
|
||||
### 3. Icon (`components/icons.tsx`)
|
||||
|
||||
```typescript
|
||||
export function ServiceIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return <svg {...props}>/* SVG from brand assets */</svg>
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Trigger (`triggers/{service}/`) - Optional
|
||||
|
||||
```
|
||||
triggers/{service}/
|
||||
├── index.ts # Barrel export
|
||||
├── webhook.ts # Webhook handler
|
||||
└── {event}.ts # Event-specific handlers
|
||||
```
|
||||
|
||||
Register in `triggers/registry.ts`.
|
||||
|
||||
### Integration Checklist
|
||||
|
||||
- [ ] Look up API docs
|
||||
- [ ] Create `tools/{service}/` with types and tools
|
||||
- [ ] Register tools in `tools/registry.ts`
|
||||
- [ ] Add icon to `components/icons.tsx`
|
||||
- [ ] Create block in `blocks/blocks/{service}.ts`
|
||||
- [ ] Register block in `blocks/registry.ts`
|
||||
- [ ] (Optional) Create and register triggers
|
||||
2
NOTICE
2
NOTICE
@@ -1,4 +1,4 @@
|
||||
Sim Studio
|
||||
Copyright 2025 Sim Studio
|
||||
|
||||
This product includes software developed for the Sim project.
|
||||
This product includes software developed for the Sim Studio project.
|
||||
229
README.md
229
README.md
@@ -1,63 +1,50 @@
|
||||
<p align="center">
|
||||
<a href="https://sim.ai" target="_blank" rel="noopener noreferrer">
|
||||
<img src="apps/sim/public/logo/reverse/text/large.png" alt="Sim Logo" width="500"/>
|
||||
</a>
|
||||
<img src="apps/sim/public/static/sim.png" alt="Sim Studio Logo" width="500"/>
|
||||
</p>
|
||||
|
||||
<p align="center">Build and deploy AI agent workflows in minutes.</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/sim.ai-6F3DFA" alt="Sim.ai"></a>
|
||||
<a href="https://discord.gg/Hr4UWYEcTT" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
|
||||
<a href="https://x.com/simdotai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/twitter/follow/simstudioai?style=social" alt="Twitter"></a>
|
||||
<a href="https://docs.sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Docs-6F3DFA.svg" alt="Documentation"></a>
|
||||
<a href="https://www.apache.org/licenses/LICENSE-2.0"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License: Apache-2.0"></a>
|
||||
<a href="https://discord.gg/Hr4UWYEcTT"><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/simstudioai?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://docs.simstudio.ai"><img src="https://img.shields.io/badge/Docs-visit%20documentation-blue.svg" alt="Documentation"></a>
|
||||
</p>
|
||||
|
||||
### Build Workflows with Ease
|
||||
Design agent workflows visually on a canvas—connect agents, tools, and blocks, then run them instantly.
|
||||
|
||||
<p align="center">
|
||||
<img src="apps/sim/public/static/workflow.gif" alt="Workflow Builder Demo" width="800"/>
|
||||
<strong>Sim Studio</strong> is a lightweight, user-friendly platform for building AI agent workflows.
|
||||
</p>
|
||||
|
||||
### Supercharge with Copilot
|
||||
Leverage Copilot to generate nodes, fix errors, and iterate on flows directly from natural language.
|
||||
|
||||
<p align="center">
|
||||
<img src="apps/sim/public/static/copilot.gif" alt="Copilot Demo" width="800"/>
|
||||
<img src="apps/sim/public/static/demo.gif" alt="Sim Studio Demo" width="800"/>
|
||||
</p>
|
||||
|
||||
### Integrate Vector Databases
|
||||
Upload documents to a vector store and let agents answer questions grounded in your specific content.
|
||||
## Getting Started
|
||||
|
||||
<p align="center">
|
||||
<img src="apps/sim/public/static/knowledge.gif" alt="Knowledge Uploads and Retrieval Demo" width="800"/>
|
||||
</p>
|
||||
1. Use our [cloud-hosted version](https://simstudio.ai)
|
||||
2. Self-host using one of the methods below
|
||||
|
||||
## Quickstart
|
||||
## Self-Hosting Options
|
||||
|
||||
### Cloud-hosted: [sim.ai](https://sim.ai)
|
||||
### Option 1: NPM Package (Simplest)
|
||||
|
||||
<a href="https://sim.ai" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/sim.ai-6F3DFA?logo=data:image/svg%2bxml;base64,PHN2ZyB3aWR0aD0iNjE2IiBoZWlnaHQ9IjYxNiIgdmlld0JveD0iMCAwIDYxNiA2MTYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMTU5XzMxMykiPgo8cGF0aCBkPSJNNjE2IDBIMFY2MTZINjE2VjBaIiBmaWxsPSIjNkYzREZBIi8+CjxwYXRoIGQ9Ik04MyAzNjUuNTY3SDExM0MxMTMgMzczLjgwNSAxMTYgMzgwLjM3MyAxMjIgMzg1LjI3MkMxMjggMzg5Ljk0OCAxMzYuMTExIDM5Mi4yODUgMTQ2LjMzMyAzOTIuMjg1QzE1Ny40NDQgMzkyLjI4NSAxNjYgMzkwLjE3MSAxNzIgMzg1LjkzOUMxNzcuOTk5IDM4MS40ODcgMTgxIDM3NS41ODYgMTgxIDM2OC4yMzlDMTgxIDM2Mi44OTUgMTc5LjMzMyAzNTguNDQyIDE3NiAzNTQuODhDMTcyLjg4OSAzNTEuMzE4IDE2Ny4xMTEgMzQ4LjQyMiAxNTguNjY3IDM0Ni4xOTZMMTMwIDMzOS41MTdDMTE1LjU1NSAzMzUuOTU1IDEwNC43NzggMzMwLjQ5OSA5Ny42NjY1IDMyMy4xNTFDOTAuNzc3NSAzMTUuODA0IDg3LjMzMzQgMzA2LjExOSA4Ny4zMzM0IDI5NC4wOTZDODcuMzMzNCAyODQuMDc2IDg5Ljg4OSAyNzUuMzkyIDk0Ljk5OTYgMjY4LjA0NUMxMDAuMzMzIDI2MC42OTcgMTA3LjU1NSAyNTUuMDIgMTE2LjY2NiAyNTEuMDEyQzEyNiAyNDcuMDA0IDEzNi42NjcgMjQ1IDE0OC42NjYgMjQ1QzE2MC42NjcgMjQ1IDE3MSAyNDcuMTE2IDE3OS42NjcgMjUxLjM0NkMxODguNTU1IDI1NS41NzYgMTk1LjQ0NCAyNjEuNDc3IDIwMC4zMzMgMjY5LjA0N0MyMDUuNDQ0IDI3Ni42MTcgMjA4LjExMSAyODUuNjM0IDIwOC4zMzMgMjk2LjA5OUgxNzguMzMzQzE3OC4xMTEgMjg3LjYzOCAxNzUuMzMzIDI4MS4wNyAxNjkuOTk5IDI3Ni4zOTRDMTY0LjY2NiAyNzEuNzE5IDE1Ny4yMjIgMjY5LjM4MSAxNDcuNjY3IDI2OS4zODFDMTM3Ljg4OSAyNjkuMzgxIDEzMC4zMzMgMjcxLjQ5NiAxMjUgMjc1LjcyNkMxMTkuNjY2IDI3OS45NTcgMTE3IDI4NS43NDYgMTE3IDI5My4wOTNDMTE3IDMwNC4wMDMgMTI1IDMxMS40NjIgMTQxIDMxNS40N0wxNjkuNjY3IDMyMi40ODNDMTgzLjQ0NSAzMjUuNiAxOTMuNzc4IDMzMC43MjIgMjAwLjY2NyAzMzcuODQ3QzIwNy41NTUgMzQ0Ljc0OSAyMTEgMzU0LjIxMiAyMTEgMzY2LjIzNUMyMTEgMzc2LjQ3NyAyMDguMjIyIDM4NS40OTQgMjAyLjY2NiAzOTMuMjg3QzE5Ny4xMTEgNDAwLjg1NyAxODkuNDQ0IDQwNi43NTggMTc5LjY2NyA0MTAuOTg5QzE3MC4xMTEgNDE0Ljk5NiAxNTguNzc4IDQxNyAxNDUuNjY3IDQxN0MxMjYuNTU1IDQxNyAxMTEuMzMzIDQxMi4zMjUgOTkuOTk5NyA0MDIuOTczQzg4LjY2NjggMzkzLjYyMSA4MyAzODEuMTUzIDgzIDM2NS41NjdaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjMyLjI5MSA0MTNWMjUwLjA4MkMyNDQuNjg0IDI1NC42MTQgMjUwLjE0OCAyNTQuNjE0IDI2My4zNzEgMjUwLjA4MlY0MTNIMjMyLjI5MVpNMjQ3LjUgMjM5LjMxM0MyNDEuOTkgMjM5LjMxMyAyMzcuMTQgMjM3LjMxMyAyMzIuOTUyIDIzMy4zMTZDMjI4Ljk4NCAyMjkuMDk1IDIyNyAyMjQuMjA5IDIyNyAyMTguNjU2QzIyNyAyMTIuODgyIDIyOC45ODQgMjA3Ljk5NSAyMzIuOTUyIDIwMy45OTdDMjM3LjE0IDE5OS45OTkgMjQxLjk5IDE5OCAyNDcuNSAxOThDMjUzLjIzMSAxOTggMjU4LjA4IDE5OS45OTkgMjYyLjA0OSAyMDMuOTk3QzI2Ni4wMTYgMjA3Ljk5NSAyNjggMjEyLjg4MiAyNjggMjE4LjY1NkMyNjggMjI0LjIwOSAyNjYuMDE2IDIyOS4wOTUgMjYyLjA0OSAyMzMuMzE2QzI1OC4wOCAyMzcuMzEzIDI1My4yMzEgMjM5LjMxMyAyNDcuNSAyMzkuMzEzWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTMxOS4zMzMgNDEzSDI4OFYyNDkuNjc2SDMxNlYyNzcuMjMzQzMxOS4zMzMgMjY4LjEwNCAzMjUuNzc4IDI2MC4zNjQgMzM0LjY2NyAyNTQuMzUyQzM0My43NzggMjQ4LjExNyAzNTQuNzc4IDI0NSAzNjcuNjY3IDI0NUMzODIuMTExIDI0NSAzOTQuMTEyIDI0OC44OTcgNDAzLjY2NyAyNTYuNjlDNDEzLjIyMiAyNjQuNDg0IDQxOS40NDQgMjc0LjgzNyA0MjIuMzM0IDI4Ny43NTJINDE2LjY2N0M0MTguODg5IDI3NC44MzcgNDI1IDI2NC40ODQgNDM1IDI1Ni42OUM0NDUgMjQ4Ljg5NyA0NTcuMzM0IDI0NSA0NzIgMjQ1QzQ5MC42NjYgMjQ1IDUwNS4zMzQgMjUwLjQ1NSA1MTYgMjYxLjM2NkM1MjYuNjY3IDI3Mi4yNzYgNTMyIDI4Ny4xOTUgNTMyIDMwNi4xMjFWNDEzSDUwMS4zMzNWMzEzLjgwNEM1MDEuMzMzIDMwMC44ODkgNDk4IDI5MC45ODEgNDkxLjMzMyAyODQuMDc4QzQ4NC44ODkgMjc2Ljk1MiA0NzYuMTExIDI3My4zOSA0NjUgMjczLjM5QzQ1Ny4yMjIgMjczLjM5IDQ1MC4zMzMgMjc1LjE3MSA0NDQuMzM0IDI3OC43MzRDNDM4LjU1NiAyODIuMDc0IDQzNCAyODYuOTcyIDQzMC42NjcgMjkzLjQzQzQyNy4zMzMgMjk5Ljg4NyA0MjUuNjY3IDMwNy40NTcgNDI1LjY2NyAzMTYuMTQxVjQxM0gzOTQuNjY3VjMxMy40NjlDMzk0LjY2NyAzMDAuNTU1IDM5MS40NDUgMjkwLjc1OCAzODUgMjg0LjA3OEMzNzguNTU2IDI3Ny4xNzUgMzY5Ljc3OCAyNzMuNzI0IDM1OC42NjcgMjczLjcyNEMzNTAuODg5IDI3My43MjQgMzQ0IDI3NS41MDUgMzM4IDI3OS4wNjhDMzMyLjIyMiAyODIuNDA4IDMyNy42NjcgMjg3LjMwNyAzMjQuMzMzIDI5My43NjNDMzIxIDI5OS45OTggMzE5LjMzMyAzMDcuNDU3IDMxOS4zMzMgMzE2LjE0MVY0MTNaIiBmaWxsPSJ3aGl0ZSIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzExNTlfMzEzIj4KPHJlY3Qgd2lkdGg9IjYxNiIgaGVpZ2h0PSI2MTYiIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg==&logoColor=white" alt="Sim.ai"></a>
|
||||
|
||||
### Self-hosted: NPM Package
|
||||
The easiest way to run Sim Studio locally is using our [NPM package](https://www.npmjs.com/package/simstudio?activeTab=readme):
|
||||
|
||||
```bash
|
||||
npx simstudio
|
||||
```
|
||||
→ http://localhost:3000
|
||||
|
||||
#### Note
|
||||
Docker must be installed and running on your machine.
|
||||
After running these commands, open [http://localhost:3000/](http://localhost:3000/) in your browser.
|
||||
|
||||
#### Options
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-p, --port <port>` | Port to run Sim on (default `3000`) |
|
||||
| `--no-pull` | Skip pulling latest Docker images |
|
||||
- `-p, --port <port>`: Specify the port to run Sim Studio on (default: 3000)
|
||||
- `--no-pull`: Skip pulling the latest Docker images
|
||||
|
||||
### Self-hosted: Docker Compose
|
||||
#### Requirements
|
||||
|
||||
- Docker must be installed and running on your machine
|
||||
|
||||
### Option 2: Docker Compose
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
@@ -66,74 +53,42 @@ git clone https://github.com/simstudioai/sim.git
|
||||
# Navigate to the project directory
|
||||
cd sim
|
||||
|
||||
# Start Sim
|
||||
# Start Sim Studio
|
||||
docker compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
Access the application at [http://localhost:3000/](http://localhost:3000/)
|
||||
|
||||
#### Using Local Models with Ollama
|
||||
#### Using Local Models
|
||||
|
||||
Run Sim with local AI models using [Ollama](https://ollama.ai) - no external APIs required:
|
||||
To use local models with Sim Studio:
|
||||
|
||||
1. Pull models using our helper script:
|
||||
|
||||
```bash
|
||||
# Start with GPU support (automatically downloads gemma3:4b model)
|
||||
docker compose -f docker-compose.ollama.yml --profile setup up -d
|
||||
|
||||
# For CPU-only systems:
|
||||
docker compose -f docker-compose.ollama.yml --profile cpu --profile setup up -d
|
||||
./apps/sim/scripts/ollama_docker.sh pull <model_name>
|
||||
```
|
||||
|
||||
Wait for the model to download, then visit [http://localhost:3000](http://localhost:3000). Add more models with:
|
||||
```bash
|
||||
docker compose -f docker-compose.ollama.yml exec ollama ollama pull llama3.1:8b
|
||||
```
|
||||
|
||||
#### Using an External Ollama Instance
|
||||
|
||||
If you already have Ollama running on your host machine (outside Docker), you need to configure the `OLLAMA_URL` to use `host.docker.internal` instead of `localhost`:
|
||||
2. Start Sim Studio with local model support:
|
||||
|
||||
```bash
|
||||
# Docker Desktop (macOS/Windows)
|
||||
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
|
||||
# With NVIDIA GPU support
|
||||
docker compose --profile local-gpu -f docker-compose.ollama.yml up -d
|
||||
|
||||
# Linux (add extra_hosts or use host IP)
|
||||
docker compose -f docker-compose.prod.yml up -d # Then set OLLAMA_URL to your host's IP
|
||||
# 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
|
||||
```
|
||||
|
||||
**Why?** When running inside Docker, `localhost` refers to the container itself, not your host machine. `host.docker.internal` is a special DNS name that resolves to the host.
|
||||
|
||||
For Linux users, you can either:
|
||||
- Use your host machine's actual IP address (e.g., `http://192.168.1.100:11434`)
|
||||
- Add `extra_hosts: ["host.docker.internal:host-gateway"]` to the simstudio service in your compose file
|
||||
|
||||
#### Using vLLM
|
||||
|
||||
Sim also supports [vLLM](https://docs.vllm.ai/) for self-hosted models with OpenAI-compatible API:
|
||||
|
||||
```bash
|
||||
# Set these environment variables
|
||||
VLLM_BASE_URL=http://your-vllm-server:8000
|
||||
VLLM_API_KEY=your_optional_api_key # Only if your vLLM instance requires auth
|
||||
```
|
||||
|
||||
When running with Docker, use `host.docker.internal` if vLLM is on your host machine (same as Ollama above).
|
||||
|
||||
### Self-hosted: Dev Containers
|
||||
### Option 3: Dev Containers
|
||||
|
||||
1. Open VS Code with the [Remote - Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
|
||||
2. Open the project and click "Reopen in Container" when prompted
|
||||
3. Run `bun run dev:full` in the terminal or use the `sim-start` alias
|
||||
- This starts both the main application and the realtime socket server
|
||||
|
||||
### Self-hosted: Manual Setup
|
||||
|
||||
**Requirements:**
|
||||
- [Bun](https://bun.sh/) runtime
|
||||
- [Node.js](https://nodejs.org/) v20+ (required for sandboxed code execution)
|
||||
- PostgreSQL 12+ with [pgvector extension](https://github.com/pgvector/pgvector) (required for AI embeddings)
|
||||
|
||||
**Note:** Sim uses vector embeddings for AI features like knowledge bases and semantic search, which requires the `pgvector` PostgreSQL extension.
|
||||
### Option 4: Manual Setup
|
||||
|
||||
1. Clone and install dependencies:
|
||||
|
||||
@@ -143,123 +98,37 @@ cd sim
|
||||
bun install
|
||||
```
|
||||
|
||||
2. Set up PostgreSQL with pgvector:
|
||||
|
||||
You need PostgreSQL with the `vector` extension for embedding support. Choose one option:
|
||||
|
||||
**Option A: Using Docker (Recommended)**
|
||||
```bash
|
||||
# Start PostgreSQL with pgvector extension
|
||||
docker run --name simstudio-db \
|
||||
-e POSTGRES_PASSWORD=your_password \
|
||||
-e POSTGRES_DB=simstudio \
|
||||
-p 5432:5432 -d \
|
||||
pgvector/pgvector:pg17
|
||||
```
|
||||
|
||||
**Option B: Manual Installation**
|
||||
- Install PostgreSQL 12+ and the pgvector extension
|
||||
- See [pgvector installation guide](https://github.com/pgvector/pgvector#installation)
|
||||
|
||||
3. Set up environment:
|
||||
2. Set up environment:
|
||||
|
||||
```bash
|
||||
cd apps/sim
|
||||
cp .env.example .env # Configure with required variables (DATABASE_URL, BETTER_AUTH_SECRET, BETTER_AUTH_URL)
|
||||
```
|
||||
|
||||
Update your `.env` file with the database URL:
|
||||
```bash
|
||||
DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"
|
||||
```
|
||||
|
||||
4. Set up the database:
|
||||
|
||||
First, configure the database package environment:
|
||||
```bash
|
||||
cd packages/db
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Update your `packages/db/.env` file with the database URL:
|
||||
```bash
|
||||
DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"
|
||||
```
|
||||
|
||||
Then run the migrations:
|
||||
```bash
|
||||
cd packages/db # Required so drizzle picks correct .env file
|
||||
bunx drizzle-kit migrate --config=./drizzle.config.ts
|
||||
```
|
||||
|
||||
5. Start the development servers:
|
||||
|
||||
**Recommended approach - run both servers together (from project root):**
|
||||
3. Set up the database:
|
||||
|
||||
```bash
|
||||
bun run dev:full
|
||||
bunx drizzle-kit push
|
||||
```
|
||||
|
||||
This starts both the main Next.js application and the realtime socket server required for full functionality.
|
||||
4. Start the development servers:
|
||||
|
||||
**Alternative - run servers separately:**
|
||||
Next.js app:
|
||||
|
||||
Next.js app (from project root):
|
||||
```bash
|
||||
bun run dev
|
||||
```
|
||||
|
||||
Realtime socket server (from `apps/sim` directory in a separate terminal):
|
||||
Start the realtime server:
|
||||
|
||||
```bash
|
||||
cd apps/sim
|
||||
bun run dev:sockets
|
||||
```
|
||||
|
||||
## Copilot API Keys
|
||||
|
||||
Copilot is a Sim-managed service. To use Copilot on a self-hosted instance:
|
||||
|
||||
- Go to https://sim.ai → Settings → Copilot and generate a Copilot API key
|
||||
- Set `COPILOT_API_KEY` environment variable in your self-hosted apps/sim/.env file to that value
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Key environment variables for self-hosted deployments (see `apps/sim/.env.example` for full list):
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `DATABASE_URL` | Yes | PostgreSQL connection string with pgvector |
|
||||
| `BETTER_AUTH_SECRET` | Yes | Auth secret (`openssl rand -hex 32`) |
|
||||
| `BETTER_AUTH_URL` | Yes | Your app URL (e.g., `http://localhost:3000`) |
|
||||
| `NEXT_PUBLIC_APP_URL` | Yes | Public app URL (same as above) |
|
||||
| `ENCRYPTION_KEY` | Yes | Encryption key (`openssl rand -hex 32`) |
|
||||
| `OLLAMA_URL` | No | Ollama server URL (default: `http://localhost:11434`) |
|
||||
| `VLLM_BASE_URL` | No | vLLM server URL for self-hosted models |
|
||||
| `COPILOT_API_KEY` | No | API key from sim.ai for Copilot features |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Ollama models not showing in dropdown (Docker)
|
||||
|
||||
If you're running Ollama on your host machine and Sim in Docker, change `OLLAMA_URL` from `localhost` to `host.docker.internal`:
|
||||
Run both together (recommended):
|
||||
|
||||
```bash
|
||||
OLLAMA_URL=http://host.docker.internal:11434 docker compose -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
See [Using an External Ollama Instance](#using-an-external-ollama-instance) for details.
|
||||
|
||||
### Database connection issues
|
||||
|
||||
Ensure PostgreSQL has the pgvector extension installed. When using Docker, wait for the database to be healthy before running migrations.
|
||||
|
||||
### Port conflicts
|
||||
|
||||
If ports 3000, 3002, or 5432 are in use, configure alternatives:
|
||||
|
||||
```bash
|
||||
# Custom ports
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3100 POSTGRES_PORT=5433 docker compose up -d
|
||||
bun run dev:full
|
||||
```
|
||||
|
||||
## Tech Stack
|
||||
@@ -274,8 +143,6 @@ NEXT_PUBLIC_APP_URL=http://localhost:3100 POSTGRES_PORT=5433 docker compose up -
|
||||
- **Docs**: [Fumadocs](https://fumadocs.vercel.app/)
|
||||
- **Monorepo**: [Turborepo](https://turborepo.org/)
|
||||
- **Realtime**: [Socket.io](https://socket.io/)
|
||||
- **Background Jobs**: [Trigger.dev](https://trigger.dev/)
|
||||
- **Remote Code Execution**: [E2B](https://www.e2b.dev/)
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -285,4 +152,4 @@ We welcome contributions! Please see our [Contributing Guide](.github/CONTRIBUTI
|
||||
|
||||
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
<p align="center">Made with ❤️ by the Sim Team</p>
|
||||
<p align="center">Made with ❤️ by the Sim Studio Team</p>
|
||||
5
apps/docs/app/(docs)/[[...slug]]/layout.tsx
Normal file
5
apps/docs/app/(docs)/[[...slug]]/layout.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
export default function SlugLayout({ children }: { children: ReactNode }) {
|
||||
return children
|
||||
}
|
||||
58
apps/docs/app/(docs)/[[...slug]]/page.tsx
Normal file
58
apps/docs/app/(docs)/[[...slug]]/page.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page'
|
||||
import { notFound } from 'next/navigation'
|
||||
import mdxComponents from '@/components/mdx-components'
|
||||
import { source } from '@/lib/source'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export default async function Page(props: { params: Promise<{ slug?: string[] }> }) {
|
||||
const params = await props.params
|
||||
const page = source.getPage(params.slug)
|
||||
if (!page) notFound()
|
||||
|
||||
const MDX = page.data.body
|
||||
|
||||
return (
|
||||
<DocsPage
|
||||
toc={page.data.toc}
|
||||
full={page.data.full}
|
||||
tableOfContent={{
|
||||
style: 'clerk',
|
||||
enabled: true,
|
||||
header: <div className='mb-2 font-medium text-sm'>On this page</div>,
|
||||
single: false,
|
||||
}}
|
||||
article={{
|
||||
className: 'scroll-smooth max-sm:pb-16',
|
||||
}}
|
||||
tableOfContentPopover={{
|
||||
style: 'clerk',
|
||||
enabled: true,
|
||||
}}
|
||||
footer={{
|
||||
enabled: false,
|
||||
}}
|
||||
>
|
||||
<DocsTitle>{page.data.title}</DocsTitle>
|
||||
<DocsDescription>{page.data.description}</DocsDescription>
|
||||
<DocsBody>
|
||||
<MDX components={mdxComponents} />
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
)
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return source.generateParams()
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: { params: Promise<{ slug?: string[] }> }) {
|
||||
const params = await props.params
|
||||
const page = source.getPage(params.slug)
|
||||
if (!page) notFound()
|
||||
|
||||
return {
|
||||
title: page.data.title,
|
||||
description: page.data.description,
|
||||
}
|
||||
}
|
||||
46
apps/docs/app/(docs)/layout.tsx
Normal file
46
apps/docs/app/(docs)/layout.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
|
||||
import { ExternalLink, GithubIcon } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { source } from '@/lib/source'
|
||||
|
||||
const GitHubLink = () => (
|
||||
<div className='fixed right-4 bottom-4 z-50'>
|
||||
<Link
|
||||
href='https://github.com/simstudioai/sim'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='flex h-8 w-8 items-center justify-center rounded-full border border-border bg-background transition-colors hover:bg-muted'
|
||||
>
|
||||
<GithubIcon className='h-4 w-4' />
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<DocsLayout
|
||||
tree={source.pageTree}
|
||||
nav={{
|
||||
title: <div className='flex items-center font-medium'>Sim Studio</div>,
|
||||
}}
|
||||
links={[
|
||||
{
|
||||
text: 'Visit Sim Studio',
|
||||
url: 'https://simstudio.ai',
|
||||
icon: <ExternalLink className='h-4 w-4' />,
|
||||
},
|
||||
]}
|
||||
sidebar={{
|
||||
defaultOpenLevel: 1,
|
||||
collapsible: true,
|
||||
footer: null,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</DocsLayout>
|
||||
<GitHubLink />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,335 +0,0 @@
|
||||
import type React from 'react'
|
||||
import { findNeighbour } from 'fumadocs-core/page-tree'
|
||||
import defaultMdxComponents from 'fumadocs-ui/mdx'
|
||||
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page'
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { PageNavigationArrows } from '@/components/docs-layout/page-navigation-arrows'
|
||||
import { TOCFooter } from '@/components/docs-layout/toc-footer'
|
||||
import { LLMCopyButton } from '@/components/page-actions'
|
||||
import { StructuredData } from '@/components/structured-data'
|
||||
import { CodeBlock } from '@/components/ui/code-block'
|
||||
import { Heading } from '@/components/ui/heading'
|
||||
import { type PageData, source } from '@/lib/source'
|
||||
|
||||
export default async function Page(props: { params: Promise<{ slug?: string[]; lang: string }> }) {
|
||||
const params = await props.params
|
||||
const page = source.getPage(params.slug, params.lang)
|
||||
if (!page) notFound()
|
||||
|
||||
const data = page.data as PageData
|
||||
const MDX = data.body
|
||||
const baseUrl = 'https://docs.sim.ai'
|
||||
|
||||
const pageTreeRecord = source.pageTree as Record<string, any>
|
||||
const pageTree =
|
||||
pageTreeRecord[params.lang] ?? pageTreeRecord.en ?? Object.values(pageTreeRecord)[0]
|
||||
const neighbours = pageTree ? findNeighbour(pageTree, page.url) : null
|
||||
|
||||
const generateBreadcrumbs = () => {
|
||||
const breadcrumbs: Array<{ name: string; url: string }> = [
|
||||
{
|
||||
name: 'Home',
|
||||
url: baseUrl,
|
||||
},
|
||||
]
|
||||
|
||||
const urlParts = page.url.split('/').filter(Boolean)
|
||||
let currentPath = ''
|
||||
|
||||
urlParts.forEach((part, index) => {
|
||||
if (index === 0 && ['en', 'es', 'fr', 'de', 'ja', 'zh'].includes(part)) {
|
||||
currentPath = `/${part}`
|
||||
return
|
||||
}
|
||||
|
||||
currentPath += `/${part}`
|
||||
|
||||
const name = part
|
||||
.split('-')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ')
|
||||
|
||||
if (index === urlParts.length - 1) {
|
||||
breadcrumbs.push({
|
||||
name: data.title,
|
||||
url: `${baseUrl}${page.url}`,
|
||||
})
|
||||
} else {
|
||||
breadcrumbs.push({
|
||||
name: name,
|
||||
url: `${baseUrl}${currentPath}`,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return breadcrumbs
|
||||
}
|
||||
|
||||
const breadcrumbs = generateBreadcrumbs()
|
||||
|
||||
const CustomFooter = () => (
|
||||
<div className='mt-12'>
|
||||
{/* Navigation links */}
|
||||
<div className='flex items-center justify-between py-8'>
|
||||
{neighbours?.previous ? (
|
||||
<Link
|
||||
href={neighbours.previous.url}
|
||||
className='group flex items-center gap-2 text-muted-foreground transition-colors hover:text-foreground'
|
||||
>
|
||||
<ChevronLeft className='group-hover:-translate-x-1 h-4 w-4 transition-transform' />
|
||||
<span className='font-medium'>{neighbours.previous.name}</span>
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
|
||||
{neighbours?.next ? (
|
||||
<Link
|
||||
href={neighbours.next.url}
|
||||
className='group flex items-center gap-2 text-muted-foreground transition-colors hover:text-foreground'
|
||||
>
|
||||
<span className='font-medium'>{neighbours.next.name}</span>
|
||||
<ChevronRight className='h-4 w-4 transition-transform group-hover:translate-x-1' />
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Divider line */}
|
||||
<div className='border-border border-t' />
|
||||
|
||||
{/* Social icons */}
|
||||
<div className='flex items-center gap-4 py-6'>
|
||||
<Link
|
||||
href='https://x.com/simdotai'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
aria-label='X (Twitter)'
|
||||
>
|
||||
<div
|
||||
className='h-5 w-5 bg-gray-400 transition-colors hover:bg-gray-500 dark:bg-gray-500 dark:hover:bg-gray-400'
|
||||
style={{
|
||||
maskImage:
|
||||
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z%22/%3E%3C/svg%3E')",
|
||||
maskRepeat: 'no-repeat',
|
||||
maskPosition: 'center center',
|
||||
WebkitMaskImage:
|
||||
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z%22/%3E%3C/svg%3E')",
|
||||
WebkitMaskRepeat: 'no-repeat',
|
||||
WebkitMaskPosition: 'center center',
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
<Link
|
||||
href='https://github.com/simstudioai/sim'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
aria-label='GitHub'
|
||||
>
|
||||
<div
|
||||
className='h-5 w-5 bg-gray-400 transition-colors hover:bg-gray-500 dark:bg-gray-500 dark:hover:bg-gray-400'
|
||||
style={{
|
||||
maskImage:
|
||||
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z%22/%3E%3C/svg%3E')",
|
||||
maskRepeat: 'no-repeat',
|
||||
maskPosition: 'center center',
|
||||
WebkitMaskImage:
|
||||
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z%22/%3E%3C/svg%3E')",
|
||||
WebkitMaskRepeat: 'no-repeat',
|
||||
WebkitMaskPosition: 'center center',
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
<Link
|
||||
href='https://discord.gg/Hr4UWYEcTT'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
aria-label='Discord'
|
||||
>
|
||||
<div
|
||||
className='h-5 w-5 bg-gray-400 transition-colors hover:bg-gray-500 dark:bg-gray-500 dark:hover:bg-gray-400'
|
||||
style={{
|
||||
maskImage:
|
||||
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z%22/%3E%3C/svg%3E')",
|
||||
maskRepeat: 'no-repeat',
|
||||
maskPosition: 'center center',
|
||||
WebkitMaskImage:
|
||||
"url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath d=%22M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z%22/%3E%3C/svg%3E')",
|
||||
WebkitMaskRepeat: 'no-repeat',
|
||||
WebkitMaskPosition: 'center center',
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<StructuredData
|
||||
title={data.title}
|
||||
description={data.description || ''}
|
||||
url={`${baseUrl}${page.url}`}
|
||||
lang={params.lang}
|
||||
breadcrumb={breadcrumbs}
|
||||
/>
|
||||
<DocsPage
|
||||
toc={data.toc}
|
||||
full={data.full}
|
||||
breadcrumb={{
|
||||
enabled: false,
|
||||
}}
|
||||
tableOfContent={{
|
||||
style: 'clerk',
|
||||
enabled: true,
|
||||
header: (
|
||||
<div key='toc-header' className='mb-2 font-medium text-sm'>
|
||||
On this page
|
||||
</div>
|
||||
),
|
||||
footer: <TOCFooter />,
|
||||
single: false,
|
||||
}}
|
||||
tableOfContentPopover={{
|
||||
style: 'clerk',
|
||||
enabled: true,
|
||||
}}
|
||||
footer={{
|
||||
enabled: true,
|
||||
component: <CustomFooter />,
|
||||
}}
|
||||
>
|
||||
<div className='relative mt-6 sm:mt-0'>
|
||||
<div className='absolute top-1 right-0 flex items-center gap-2'>
|
||||
<div className='hidden sm:flex'>
|
||||
<LLMCopyButton markdownUrl={`${page.url}.mdx`} />
|
||||
</div>
|
||||
<PageNavigationArrows previous={neighbours?.previous} next={neighbours?.next} />
|
||||
</div>
|
||||
<DocsTitle>{data.title}</DocsTitle>
|
||||
<DocsDescription>{data.description}</DocsDescription>
|
||||
</div>
|
||||
<DocsBody>
|
||||
<MDX
|
||||
components={{
|
||||
...defaultMdxComponents,
|
||||
CodeBlock,
|
||||
h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||
<Heading as='h1' {...props} />
|
||||
),
|
||||
h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||
<Heading as='h2' {...props} />
|
||||
),
|
||||
h3: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||
<Heading as='h3' {...props} />
|
||||
),
|
||||
h4: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||
<Heading as='h4' {...props} />
|
||||
),
|
||||
h5: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||
<Heading as='h5' {...props} />
|
||||
),
|
||||
h6: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||
<Heading as='h6' {...props} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return source.generateParams()
|
||||
}
|
||||
|
||||
export async function generateMetadata(props: {
|
||||
params: Promise<{ slug?: string[]; lang: string }>
|
||||
}) {
|
||||
const params = await props.params
|
||||
const page = source.getPage(params.slug, params.lang)
|
||||
if (!page) notFound()
|
||||
|
||||
const data = page.data as PageData
|
||||
const baseUrl = 'https://docs.sim.ai'
|
||||
const fullUrl = `${baseUrl}${page.url}`
|
||||
|
||||
const ogImageUrl = `${baseUrl}/api/og?title=${encodeURIComponent(data.title)}`
|
||||
|
||||
return {
|
||||
title: data.title,
|
||||
description:
|
||||
data.description || 'Sim visual workflow builder for AI applications documentation',
|
||||
keywords: [
|
||||
'AI workflow builder',
|
||||
'visual workflow editor',
|
||||
'AI automation',
|
||||
'workflow automation',
|
||||
'AI agents',
|
||||
'no-code AI',
|
||||
'drag and drop workflows',
|
||||
data.title?.toLowerCase().split(' '),
|
||||
]
|
||||
.flat()
|
||||
.filter(Boolean),
|
||||
authors: [{ name: 'Sim Team' }],
|
||||
category: 'Developer Tools',
|
||||
openGraph: {
|
||||
title: data.title,
|
||||
description:
|
||||
data.description || 'Sim visual workflow builder for AI applications documentation',
|
||||
url: fullUrl,
|
||||
siteName: 'Sim Documentation',
|
||||
type: 'article',
|
||||
locale: params.lang === 'en' ? 'en_US' : `${params.lang}_${params.lang.toUpperCase()}`,
|
||||
alternateLocale: ['en', 'es', 'fr', 'de', 'ja', 'zh']
|
||||
.filter((lang) => lang !== params.lang)
|
||||
.map((lang) => (lang === 'en' ? 'en_US' : `${lang}_${lang.toUpperCase()}`)),
|
||||
images: [
|
||||
{
|
||||
url: ogImageUrl,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: data.title,
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: data.title,
|
||||
description:
|
||||
data.description || 'Sim visual workflow builder for AI applications documentation',
|
||||
images: [ogImageUrl],
|
||||
creator: '@simdotai',
|
||||
site: '@simdotai',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
'max-video-preview': -1,
|
||||
'max-image-preview': 'large',
|
||||
'max-snippet': -1,
|
||||
},
|
||||
},
|
||||
canonical: fullUrl,
|
||||
alternates: {
|
||||
canonical: fullUrl,
|
||||
languages: {
|
||||
'x-default': `${baseUrl}${page.url.replace(`/${params.lang}`, '')}`,
|
||||
en: `${baseUrl}${page.url.replace(`/${params.lang}`, '')}`,
|
||||
es: `${baseUrl}/es${page.url.replace(`/${params.lang}`, '')}`,
|
||||
fr: `${baseUrl}/fr${page.url.replace(`/${params.lang}`, '')}`,
|
||||
de: `${baseUrl}/de${page.url.replace(`/${params.lang}`, '')}`,
|
||||
ja: `${baseUrl}/ja${page.url.replace(`/${params.lang}`, '')}`,
|
||||
zh: `${baseUrl}/zh${page.url.replace(`/${params.lang}`, '')}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { defineI18nUI } from 'fumadocs-ui/i18n'
|
||||
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
|
||||
import { RootProvider } from 'fumadocs-ui/provider/next'
|
||||
import { Geist_Mono, Inter } from 'next/font/google'
|
||||
import Image from 'next/image'
|
||||
import {
|
||||
SidebarFolder,
|
||||
SidebarItem,
|
||||
SidebarSeparator,
|
||||
} from '@/components/docs-layout/sidebar-components'
|
||||
import { Navbar } from '@/components/navbar/navbar'
|
||||
import { i18n } from '@/lib/i18n'
|
||||
import { source } from '@/lib/source'
|
||||
import '../global.css'
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-geist-sans',
|
||||
})
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
subsets: ['latin'],
|
||||
variable: '--font-geist-mono',
|
||||
})
|
||||
|
||||
const { provider } = defineI18nUI(i18n, {
|
||||
translations: {
|
||||
en: {
|
||||
displayName: 'English',
|
||||
},
|
||||
es: {
|
||||
displayName: 'Español',
|
||||
},
|
||||
fr: {
|
||||
displayName: 'Français',
|
||||
},
|
||||
de: {
|
||||
displayName: 'Deutsch',
|
||||
},
|
||||
ja: {
|
||||
displayName: '日本語',
|
||||
},
|
||||
zh: {
|
||||
displayName: '简体中文',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
type LayoutProps = {
|
||||
children: ReactNode
|
||||
params: Promise<{ lang: string }>
|
||||
}
|
||||
|
||||
export default async function Layout({ children, params }: LayoutProps) {
|
||||
const { lang } = await params
|
||||
|
||||
const structuredData = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
name: 'Sim Documentation',
|
||||
description:
|
||||
'Comprehensive documentation for Sim - the visual workflow builder for AI Agent Workflows.',
|
||||
url: 'https://docs.sim.ai',
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'Sim',
|
||||
url: 'https://sim.ai',
|
||||
logo: {
|
||||
'@type': 'ImageObject',
|
||||
url: 'https://docs.sim.ai/static/logo.png',
|
||||
},
|
||||
},
|
||||
inLanguage: lang,
|
||||
potentialAction: {
|
||||
'@type': 'SearchAction',
|
||||
target: {
|
||||
'@type': 'EntryPoint',
|
||||
urlTemplate: 'https://docs.sim.ai/api/search?q={search_term_string}',
|
||||
},
|
||||
'query-input': 'required name=search_term_string',
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<html
|
||||
lang={lang}
|
||||
className={`${inter.variable} ${geistMono.variable}`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<head>
|
||||
<script
|
||||
type='application/ld+json'
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
|
||||
/>
|
||||
{/* OneDollarStats Analytics - CDN script handles everything automatically */}
|
||||
<script defer src='https://assets.onedollarstats.com/stonks.js' />
|
||||
</head>
|
||||
<body className='flex min-h-screen flex-col font-sans'>
|
||||
<RootProvider i18n={provider(lang)}>
|
||||
<Navbar />
|
||||
<DocsLayout
|
||||
tree={source.pageTree[lang]}
|
||||
nav={{
|
||||
title: (
|
||||
<Image
|
||||
src='/static/logo.png'
|
||||
alt='Sim'
|
||||
width={72}
|
||||
height={28}
|
||||
className='h-7 w-auto'
|
||||
priority
|
||||
/>
|
||||
),
|
||||
}}
|
||||
sidebar={{
|
||||
defaultOpenLevel: 0,
|
||||
collapsible: false,
|
||||
footer: null,
|
||||
banner: null,
|
||||
components: {
|
||||
Item: SidebarItem,
|
||||
Folder: SidebarFolder,
|
||||
Separator: SidebarSeparator,
|
||||
},
|
||||
}}
|
||||
containerProps={{
|
||||
className: '!pt-0',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</DocsLayout>
|
||||
</RootProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { DocsBody, DocsPage } from 'fumadocs-ui/page'
|
||||
|
||||
export const metadata = {
|
||||
title: 'Page Not Found',
|
||||
}
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<DocsPage>
|
||||
<DocsBody>
|
||||
<div className='flex min-h-[60vh] flex-col items-center justify-center text-center'>
|
||||
<h1 className='mb-4 bg-gradient-to-b from-[#8357FF] to-[#6F3DFA] bg-clip-text font-bold text-8xl text-transparent'>
|
||||
404
|
||||
</h1>
|
||||
<h2 className='mb-2 font-semibold text-2xl text-foreground'>Page Not Found</h2>
|
||||
<p className='text-muted-foreground'>
|
||||
The page you're looking for doesn't exist or has been moved.
|
||||
</p>
|
||||
</div>
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
)
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
import { ImageResponse } from 'next/og'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
export const runtime = 'edge'
|
||||
|
||||
const TITLE_FONT_SIZE = {
|
||||
large: 64,
|
||||
medium: 56,
|
||||
small: 48,
|
||||
} as const
|
||||
|
||||
function getTitleFontSize(title: string): number {
|
||||
if (title.length > 45) return TITLE_FONT_SIZE.small
|
||||
if (title.length > 30) return TITLE_FONT_SIZE.medium
|
||||
return TITLE_FONT_SIZE.large
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a Google Font dynamically by fetching the CSS and extracting the font URL.
|
||||
*/
|
||||
async function loadGoogleFont(font: string, weights: string, text: string): Promise<ArrayBuffer> {
|
||||
const url = `https://fonts.googleapis.com/css2?family=${font}:wght@${weights}&text=${encodeURIComponent(text)}`
|
||||
const css = await (await fetch(url)).text()
|
||||
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/)
|
||||
|
||||
if (resource) {
|
||||
const response = await fetch(resource[1])
|
||||
if (response.status === 200) {
|
||||
return await response.arrayBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Failed to load font data')
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates dynamic Open Graph images for documentation pages.
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const title = searchParams.get('title') || 'Documentation'
|
||||
|
||||
const baseUrl = new URL(request.url).origin
|
||||
|
||||
const allText = `${title}docs.sim.ai`
|
||||
const fontData = await loadGoogleFont('Geist', '400;500;600', allText)
|
||||
|
||||
return new ImageResponse(
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
background: '#0c0c0c',
|
||||
position: 'relative',
|
||||
fontFamily: 'Geist',
|
||||
}}
|
||||
>
|
||||
{/* Base gradient layer - subtle purple tint across the entire image */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background:
|
||||
'radial-gradient(ellipse 150% 100% at 50% 100%, rgba(88, 28, 135, 0.15) 0%, rgba(88, 28, 135, 0.08) 25%, rgba(88, 28, 135, 0.03) 50%, transparent 80%)',
|
||||
display: 'flex',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Secondary glow - adds depth without harsh edges */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background:
|
||||
'radial-gradient(ellipse 100% 80% at 80% 90%, rgba(112, 31, 252, 0.12) 0%, rgba(112, 31, 252, 0.04) 40%, transparent 70%)',
|
||||
display: 'flex',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Top darkening - creates natural vignette */}
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
background:
|
||||
'linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, transparent 40%, transparent 100%)',
|
||||
display: 'flex',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '56px 72px',
|
||||
height: '100%',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
{/* Logo */}
|
||||
<img src={`${baseUrl}/static/logo.png`} alt='sim' height={32} />
|
||||
|
||||
{/* Title */}
|
||||
<span
|
||||
style={{
|
||||
fontSize: getTitleFontSize(title),
|
||||
fontWeight: 600,
|
||||
color: '#ffffff',
|
||||
lineHeight: 1.1,
|
||||
letterSpacing: '-0.02em',
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
|
||||
{/* Footer */}
|
||||
<span
|
||||
style={{
|
||||
fontSize: 20,
|
||||
fontWeight: 500,
|
||||
color: '#71717a',
|
||||
}}
|
||||
>
|
||||
docs.sim.ai
|
||||
</span>
|
||||
</div>
|
||||
</div>,
|
||||
{
|
||||
width: 1200,
|
||||
height: 630,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Geist',
|
||||
data: fontData,
|
||||
style: 'normal',
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,126 +1,4 @@
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { db, docsEmbeddings } from '@/lib/db'
|
||||
import { generateSearchEmbedding } from '@/lib/embeddings'
|
||||
import { createFromSource } from 'fumadocs-core/search/server'
|
||||
import { source } from '@/lib/source'
|
||||
|
||||
export const runtime = 'nodejs'
|
||||
export const revalidate = 0
|
||||
|
||||
/**
|
||||
* Hybrid search API endpoint
|
||||
* - English: Vector embeddings + keyword search
|
||||
* - Other languages: Keyword search only
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const searchParams = request.nextUrl.searchParams
|
||||
const query = searchParams.get('query') || searchParams.get('q') || ''
|
||||
const locale = searchParams.get('locale') || 'en'
|
||||
const limit = Number.parseInt(searchParams.get('limit') || '10', 10)
|
||||
|
||||
if (!query || query.trim().length === 0) {
|
||||
return NextResponse.json([])
|
||||
}
|
||||
|
||||
const candidateLimit = limit * 3
|
||||
const similarityThreshold = 0.6
|
||||
|
||||
const localeMap: Record<string, string> = {
|
||||
en: 'english',
|
||||
es: 'spanish',
|
||||
fr: 'french',
|
||||
de: 'german',
|
||||
ja: 'simple', // PostgreSQL doesn't have Japanese support, use simple
|
||||
zh: 'simple', // PostgreSQL doesn't have Chinese support, use simple
|
||||
}
|
||||
const tsConfig = localeMap[locale] || 'simple'
|
||||
|
||||
const useVectorSearch = locale === 'en'
|
||||
let vectorResults: Array<{
|
||||
chunkId: string
|
||||
chunkText: string
|
||||
sourceDocument: string
|
||||
sourceLink: string
|
||||
headerText: string
|
||||
headerLevel: number
|
||||
similarity: number
|
||||
searchType: string
|
||||
}> = []
|
||||
|
||||
if (useVectorSearch) {
|
||||
const queryEmbedding = await generateSearchEmbedding(query)
|
||||
vectorResults = await db
|
||||
.select({
|
||||
chunkId: docsEmbeddings.chunkId,
|
||||
chunkText: docsEmbeddings.chunkText,
|
||||
sourceDocument: docsEmbeddings.sourceDocument,
|
||||
sourceLink: docsEmbeddings.sourceLink,
|
||||
headerText: docsEmbeddings.headerText,
|
||||
headerLevel: docsEmbeddings.headerLevel,
|
||||
similarity: sql<number>`1 - (${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector)`,
|
||||
searchType: sql<string>`'vector'`,
|
||||
})
|
||||
.from(docsEmbeddings)
|
||||
.where(
|
||||
sql`1 - (${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector) >= ${similarityThreshold}`
|
||||
)
|
||||
.orderBy(sql`${docsEmbeddings.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector`)
|
||||
.limit(candidateLimit)
|
||||
}
|
||||
|
||||
const keywordResults = await db
|
||||
.select({
|
||||
chunkId: docsEmbeddings.chunkId,
|
||||
chunkText: docsEmbeddings.chunkText,
|
||||
sourceDocument: docsEmbeddings.sourceDocument,
|
||||
sourceLink: docsEmbeddings.sourceLink,
|
||||
headerText: docsEmbeddings.headerText,
|
||||
headerLevel: docsEmbeddings.headerLevel,
|
||||
similarity: sql<number>`ts_rank(${docsEmbeddings.chunkTextTsv}, plainto_tsquery(${tsConfig}, ${query}))`,
|
||||
searchType: sql<string>`'keyword'`,
|
||||
})
|
||||
.from(docsEmbeddings)
|
||||
.where(sql`${docsEmbeddings.chunkTextTsv} @@ plainto_tsquery(${tsConfig}, ${query})`)
|
||||
.orderBy(
|
||||
sql`ts_rank(${docsEmbeddings.chunkTextTsv}, plainto_tsquery(${tsConfig}, ${query})) DESC`
|
||||
)
|
||||
.limit(candidateLimit)
|
||||
|
||||
const seenIds = new Set<string>()
|
||||
const mergedResults = []
|
||||
|
||||
for (let i = 0; i < Math.max(vectorResults.length, keywordResults.length); i++) {
|
||||
if (i < vectorResults.length && !seenIds.has(vectorResults[i].chunkId)) {
|
||||
mergedResults.push(vectorResults[i])
|
||||
seenIds.add(vectorResults[i].chunkId)
|
||||
}
|
||||
if (i < keywordResults.length && !seenIds.has(keywordResults[i].chunkId)) {
|
||||
mergedResults.push(keywordResults[i])
|
||||
seenIds.add(keywordResults[i].chunkId)
|
||||
}
|
||||
}
|
||||
|
||||
const filteredResults = mergedResults.slice(0, limit)
|
||||
const searchResults = filteredResults.map((result) => {
|
||||
const title = result.headerText || result.sourceDocument.replace('.mdx', '')
|
||||
const pathParts = result.sourceDocument
|
||||
.replace('.mdx', '')
|
||||
.split('/')
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
|
||||
return {
|
||||
id: result.chunkId,
|
||||
type: 'page' as const,
|
||||
url: result.sourceLink,
|
||||
content: title,
|
||||
breadcrumbs: pathParts,
|
||||
}
|
||||
})
|
||||
|
||||
return NextResponse.json(searchResults)
|
||||
} catch (error) {
|
||||
console.error('Semantic search error:', error)
|
||||
|
||||
return NextResponse.json([])
|
||||
}
|
||||
}
|
||||
export const { GET } = createFromSource(source)
|
||||
|
||||
@@ -1,495 +1,8 @@
|
||||
@import "tailwindcss";
|
||||
@import "fumadocs-ui/css/neutral.css";
|
||||
@import "fumadocs-ui/css/preset.css";
|
||||
|
||||
/* Prevent overscroll bounce effect on the page */
|
||||
html,
|
||||
body {
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
@theme {
|
||||
--color-fd-primary: #802fff; /* Purple from control-bar component */
|
||||
--font-geist-sans: var(--font-geist-sans);
|
||||
--font-geist-mono: var(--font-geist-mono);
|
||||
}
|
||||
|
||||
/* Font family utilities */
|
||||
.font-sans {
|
||||
font-family: var(--font-geist-sans), ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
.font-mono {
|
||||
font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
"Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
/* Target any potential border classes */
|
||||
* {
|
||||
--fd-border-sidebar: transparent !important;
|
||||
}
|
||||
|
||||
/* Override any CSS custom properties for borders */
|
||||
:root {
|
||||
--fd-border: transparent !important;
|
||||
--fd-border-sidebar: transparent !important;
|
||||
--fd-nav-height: 64px; /* Custom navbar height (h-16 = 4rem = 64px) */
|
||||
/* Content container width used to center main content */
|
||||
--spacing-fd-container: 1400px;
|
||||
/* Edge gutter = leftover space on each side of centered container */
|
||||
--edge-gutter: max(1rem, calc((100vw - var(--spacing-fd-container)) / 2));
|
||||
/* Shift the sidebar slightly left from the content edge for extra breathing room */
|
||||
--sidebar-shift: 90px;
|
||||
--sidebar-offset: max(0px, calc(var(--edge-gutter) - var(--sidebar-shift)));
|
||||
/* Shift TOC slightly right to match sidebar spacing for symmetry */
|
||||
--toc-shift: 90px;
|
||||
--toc-offset: max(0px, calc(var(--edge-gutter) - var(--toc-shift)));
|
||||
/* Sidebar and TOC have 20px internal padding - navbar accounts for this directly */
|
||||
/* Extra gap between sidebar/TOC and the main text content */
|
||||
--content-gap: 1.75rem;
|
||||
}
|
||||
|
||||
/* Remove custom layout variable overrides to fallback to fumadocs defaults */
|
||||
|
||||
/* ============================================
|
||||
Navbar Light Mode Styling
|
||||
============================================ */
|
||||
|
||||
/* Light mode navbar and search styling */
|
||||
:root:not(.dark) nav {
|
||||
background-color: hsla(0, 0%, 96%, 0.85) !important;
|
||||
}
|
||||
|
||||
:root:not(.dark) nav button[type="button"] {
|
||||
background-color: hsla(0, 0%, 93%, 0.85) !important;
|
||||
backdrop-filter: blur(33px) saturate(180%) !important;
|
||||
-webkit-backdrop-filter: blur(33px) saturate(180%) !important;
|
||||
color: rgba(0, 0, 0, 0.6) !important;
|
||||
}
|
||||
|
||||
:root:not(.dark) nav button[type="button"] kbd {
|
||||
color: rgba(0, 0, 0, 0.6) !important;
|
||||
}
|
||||
|
||||
/* Dark mode navbar and search styling */
|
||||
:root.dark nav {
|
||||
background-color: hsla(0, 0%, 7.04%, 0.92) !important;
|
||||
backdrop-filter: blur(25px) saturate(180%) brightness(0.6) !important;
|
||||
-webkit-backdrop-filter: blur(25px) saturate(180%) brightness(0.6) !important;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Custom Sidebar Styling (Turborepo-inspired)
|
||||
============================================ */
|
||||
|
||||
/* Floating sidebar appearance - remove background */
|
||||
[data-sidebar-container],
|
||||
#nd-sidebar {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
--color-fd-muted: transparent !important;
|
||||
--color-fd-card: transparent !important;
|
||||
--color-fd-secondary: transparent !important;
|
||||
}
|
||||
|
||||
aside[data-sidebar],
|
||||
aside#nd-sidebar {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
/* Fumadocs v16: Add sidebar placeholder styling for grid area */
|
||||
[data-sidebar-placeholder] {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* Fumadocs v16: Hide sidebar panel (floating collapse button) */
|
||||
[data-sidebar-panel] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Mobile only: Reduce gap between navbar and content */
|
||||
@media (max-width: 1023px) {
|
||||
#nd-docs-layout {
|
||||
margin-top: -25px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop only: Apply custom navbar offset, sidebar width and margin offsets */
|
||||
/* On mobile, let fumadocs handle the layout natively */
|
||||
@media (min-width: 1024px) {
|
||||
:root {
|
||||
--fd-banner-height: 64px !important;
|
||||
}
|
||||
|
||||
#nd-docs-layout {
|
||||
--fd-docs-height: calc(100dvh - 64px) !important;
|
||||
--fd-sidebar-width: 300px !important;
|
||||
margin-left: var(--sidebar-offset) !important;
|
||||
margin-right: var(--toc-offset) !important;
|
||||
}
|
||||
|
||||
/* Hide fumadocs nav on desktop - we use custom navbar there */
|
||||
#nd-docs-layout > header {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sidebar spacing - compact like turborepo */
|
||||
/* Fumadocs v16: [data-sidebar-viewport] doesn't exist, target #nd-sidebar > div instead */
|
||||
[data-sidebar-viewport],
|
||||
#nd-sidebar > div {
|
||||
padding: 0.5rem 12px 12px;
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Override sidebar item styling to match Raindrop */
|
||||
/* Target Link and button elements in sidebar - override Fumadocs itemVariants */
|
||||
/* Exclude the small chevron-only toggle buttons */
|
||||
/* Using html prefix for higher specificity over Tailwind v4 utilities */
|
||||
html #nd-sidebar a,
|
||||
html #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
|
||||
font-size: 0.9375rem !important; /* 15px to match Raindrop */
|
||||
line-height: 1.4 !important;
|
||||
padding: 0.5rem 0.75rem !important; /* More compact like Raindrop */
|
||||
font-weight: 400 !important;
|
||||
border-radius: 0.75rem !important; /* More rounded like Raindrop */
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
|
||||
sans-serif !important;
|
||||
}
|
||||
|
||||
/* Dark mode sidebar text */
|
||||
html.dark #nd-sidebar a,
|
||||
html.dark #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
|
||||
color: rgba(255, 255, 255, 0.6) !important;
|
||||
}
|
||||
|
||||
/* Light mode sidebar text */
|
||||
html:not(.dark) #nd-sidebar a,
|
||||
html:not(.dark) #nd-sidebar button:not([aria-label*="ollapse"]):not([aria-label*="xpand"]) {
|
||||
color: rgba(0, 0, 0, 0.6) !important;
|
||||
}
|
||||
|
||||
/* Make sure chevron icons are visible and properly styled */
|
||||
#nd-sidebar svg {
|
||||
display: inline-block !important;
|
||||
opacity: 0.6 !important;
|
||||
flex-shrink: 0 !important;
|
||||
width: 0.75rem !important;
|
||||
height: 0.75rem !important;
|
||||
}
|
||||
|
||||
/* Ensure the small chevron toggle buttons are visible */
|
||||
#nd-sidebar button[aria-label*="ollapse"],
|
||||
#nd-sidebar button[aria-label*="xpand"] {
|
||||
display: flex !important;
|
||||
opacity: 1 !important;
|
||||
padding: 0.25rem !important;
|
||||
}
|
||||
|
||||
/* Root-level spacing now handled by [data-sidebar-viewport] > * rule below */
|
||||
|
||||
/* Add tiny gap between nested items */
|
||||
#nd-sidebar ul li {
|
||||
margin-bottom: 0.0625rem !important;
|
||||
}
|
||||
|
||||
#nd-sidebar ul li:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* Section headers should be slightly larger */
|
||||
/* Fumadocs v16: Also target #nd-sidebar for compatibility */
|
||||
[data-sidebar-viewport] [data-separator],
|
||||
#nd-sidebar [data-separator],
|
||||
#nd-sidebar p {
|
||||
font-size: 0.75rem !important;
|
||||
font-weight: 600 !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.05em !important;
|
||||
}
|
||||
|
||||
/* Override active state (NO PURPLE) */
|
||||
#nd-sidebar a[data-active="true"],
|
||||
#nd-sidebar button[data-active="true"],
|
||||
#nd-sidebar a.bg-fd-primary\/10,
|
||||
#nd-sidebar a.text-fd-primary,
|
||||
#nd-sidebar a[class*="bg-fd-primary"],
|
||||
#nd-sidebar a[class*="text-fd-primary"],
|
||||
/* Override custom sidebar purple classes */
|
||||
#nd-sidebar
|
||||
a.bg-purple-50\/80,
|
||||
#nd-sidebar a.text-purple-600,
|
||||
#nd-sidebar a[class*="bg-purple"],
|
||||
#nd-sidebar a[class*="text-purple"] {
|
||||
background-image: none !important;
|
||||
}
|
||||
|
||||
/* Dark mode active state */
|
||||
html.dark #nd-sidebar a[data-active="true"],
|
||||
html.dark #nd-sidebar button[data-active="true"],
|
||||
html.dark #nd-sidebar a.bg-fd-primary\/10,
|
||||
html.dark #nd-sidebar a.text-fd-primary,
|
||||
html.dark #nd-sidebar a[class*="bg-fd-primary"],
|
||||
html.dark #nd-sidebar a[class*="text-fd-primary"],
|
||||
html.dark #nd-sidebar a.bg-purple-50\/80,
|
||||
html.dark #nd-sidebar a.text-purple-600,
|
||||
html.dark #nd-sidebar a[class*="bg-purple"],
|
||||
html.dark #nd-sidebar a[class*="text-purple"] {
|
||||
background-color: rgba(255, 255, 255, 0.15) !important;
|
||||
color: rgba(255, 255, 255, 1) !important;
|
||||
}
|
||||
|
||||
/* Light mode active state */
|
||||
html:not(.dark) #nd-sidebar a[data-active="true"],
|
||||
html:not(.dark) #nd-sidebar button[data-active="true"],
|
||||
html:not(.dark) #nd-sidebar a.bg-fd-primary\/10,
|
||||
html:not(.dark) #nd-sidebar a.text-fd-primary,
|
||||
html:not(.dark) #nd-sidebar a[class*="bg-fd-primary"],
|
||||
html:not(.dark) #nd-sidebar a[class*="text-fd-primary"],
|
||||
html:not(.dark) #nd-sidebar a.bg-purple-50\/80,
|
||||
html:not(.dark) #nd-sidebar a.text-purple-600,
|
||||
html:not(.dark) #nd-sidebar a[class*="bg-purple"],
|
||||
html:not(.dark) #nd-sidebar a[class*="text-purple"] {
|
||||
background-color: rgba(0, 0, 0, 0.07) !important;
|
||||
color: rgba(0, 0, 0, 0.9) !important;
|
||||
}
|
||||
|
||||
/* Dark mode hover state */
|
||||
html.dark #nd-sidebar a:hover:not([data-active="true"]),
|
||||
html.dark #nd-sidebar button:hover:not([data-active="true"]) {
|
||||
background-color: rgba(255, 255, 255, 0.08) !important;
|
||||
}
|
||||
|
||||
/* Light mode hover state */
|
||||
html:not(.dark) #nd-sidebar a:hover:not([data-active="true"]),
|
||||
html:not(.dark) #nd-sidebar button:hover:not([data-active="true"]) {
|
||||
background-color: rgba(0, 0, 0, 0.03) !important;
|
||||
}
|
||||
|
||||
/* Dark mode - ensure active/selected items don't change on hover */
|
||||
html.dark #nd-sidebar a.bg-purple-50\/80:hover,
|
||||
html.dark #nd-sidebar a[class*="bg-purple"]:hover,
|
||||
html.dark #nd-sidebar a[data-active="true"]:hover,
|
||||
html.dark #nd-sidebar button[data-active="true"]:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15) !important;
|
||||
color: rgba(255, 255, 255, 1) !important;
|
||||
}
|
||||
|
||||
/* Light mode - ensure active/selected items don't change on hover */
|
||||
html:not(.dark) #nd-sidebar a.bg-purple-50\/80:hover,
|
||||
html:not(.dark) #nd-sidebar a[class*="bg-purple"]:hover,
|
||||
html:not(.dark) #nd-sidebar a[data-active="true"]:hover,
|
||||
html:not(.dark) #nd-sidebar button[data-active="true"]:hover {
|
||||
background-color: rgba(0, 0, 0, 0.07) !important;
|
||||
color: rgba(0, 0, 0, 0.9) !important;
|
||||
}
|
||||
|
||||
/* Hide search, platform, and collapse button from sidebar completely */
|
||||
[data-sidebar] [data-search],
|
||||
[data-sidebar] .search-toggle,
|
||||
#nd-sidebar [data-search],
|
||||
#nd-sidebar .search-toggle,
|
||||
[data-sidebar-viewport] [data-search],
|
||||
[data-sidebar-viewport] button[data-search],
|
||||
aside[data-sidebar] [role="button"]:has([data-search]),
|
||||
aside[data-sidebar] > div > button:first-child,
|
||||
#nd-sidebar > div > button:first-child,
|
||||
[data-sidebar] a[href*="sim.ai"],
|
||||
#nd-sidebar a[href*="sim.ai"],
|
||||
[data-sidebar-viewport] a[href*="sim.ai"],
|
||||
/* Hide search buttons (but NOT folder chevron buttons) */
|
||||
aside[data-sidebar] > div:first-child
|
||||
> button:not([aria-label="Collapse"]):not([aria-label="Expand"]),
|
||||
#nd-sidebar > div:first-child > button:not([aria-label="Collapse"]):not([aria-label="Expand"]),
|
||||
/* Hide sidebar collapse button (panel icon) - direct children only */
|
||||
aside[data-sidebar] > button:first-of-type:not([aria-label="Collapse"]):not([aria-label="Expand"]),
|
||||
[data-sidebar]
|
||||
> button[type="button"]:first-of-type:not([aria-label="Collapse"]):not([aria-label="Expand"]),
|
||||
button[data-collapse]:not([aria-label="Collapse"]):not([aria-label="Expand"]),
|
||||
[data-sidebar-header] button,
|
||||
/* Hide theme toggle from sidebar footer */
|
||||
aside[data-sidebar] [data-theme-toggle],
|
||||
[data-sidebar-footer],
|
||||
[data-sidebar] footer,
|
||||
footer button[aria-label*="heme"],
|
||||
aside[data-sidebar] > div:last-child:has(button[aria-label*="heme"]),
|
||||
aside[data-sidebar] button[aria-label*="heme"],
|
||||
[data-sidebar] button[aria-label*="Theme"],
|
||||
/* Additional theme toggle selectors */
|
||||
aside[data-sidebar] > *:last-child
|
||||
button,
|
||||
[data-sidebar-viewport] ~ *,
|
||||
aside[data-sidebar] > div:not([data-sidebar-viewport]),
|
||||
/* Aggressive theme toggle hiding */
|
||||
aside[data-sidebar] svg[class*="sun"],
|
||||
aside[data-sidebar] svg[class*="moon"],
|
||||
aside[data-sidebar] button[type="button"]:last-child,
|
||||
aside button:has(svg:only-child),
|
||||
[data-sidebar] div:has(> button[type="button"]:only-child:last-child),
|
||||
/* Hide theme toggle and other non-content elements */
|
||||
aside[data-sidebar] > *:not([data-sidebar-viewport]) {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
opacity: 0 !important;
|
||||
height: 0 !important;
|
||||
max-height: 0 !important;
|
||||
overflow: hidden !important;
|
||||
pointer-events: none !important;
|
||||
position: absolute !important;
|
||||
left: -9999px !important;
|
||||
}
|
||||
|
||||
/* Desktop only: Hide sidebar toggle buttons and nav title/logo (keep visible on mobile) */
|
||||
@media (min-width: 1025px) {
|
||||
[data-sidebar-container] > button,
|
||||
[data-sidebar-container] [data-toggle],
|
||||
aside[data-sidebar] [data-sidebar-toggle],
|
||||
button[data-sidebar-toggle],
|
||||
nav button[data-sidebar-toggle],
|
||||
button[aria-label="Toggle Sidebar"],
|
||||
button[aria-label="Collapse Sidebar"],
|
||||
/* Hide nav title/logo in sidebar on desktop - target all possible locations */
|
||||
aside[data-sidebar] a[href="/"],
|
||||
aside[data-sidebar] a[href="/"] img,
|
||||
aside[data-sidebar] > a:first-child,
|
||||
aside[data-sidebar] > div > a:first-child,
|
||||
aside[data-sidebar] img[alt="Sim"],
|
||||
[data-sidebar-header],
|
||||
[data-sidebar] [data-title],
|
||||
#nd-sidebar > a:first-child,
|
||||
#nd-sidebar > div:first-child > a:first-child,
|
||||
#nd-sidebar img[alt="Sim"],
|
||||
/* Hide theme toggle at bottom of sidebar on desktop */
|
||||
#nd-sidebar
|
||||
> footer,
|
||||
#nd-sidebar footer,
|
||||
aside#nd-sidebar > *:last-child:not(div),
|
||||
#nd-sidebar > button:last-child,
|
||||
#nd-sidebar button[aria-label*="theme" i],
|
||||
#nd-sidebar button[aria-label*="Theme"],
|
||||
#nd-sidebar > div:last-child > button {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
height: 0 !important;
|
||||
max-height: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra aggressive - hide everything after the viewport */
|
||||
aside[data-sidebar] [data-sidebar-viewport] ~ * {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Tighter spacing for sidebar content */
|
||||
[data-sidebar-viewport] > * {
|
||||
margin-bottom: 0.0625rem;
|
||||
}
|
||||
|
||||
[data-sidebar-viewport] > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
[data-sidebar-viewport] ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Ensure sidebar starts with content immediately */
|
||||
aside[data-sidebar] > div:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
/* Remove all sidebar borders and backgrounds */
|
||||
[data-sidebar-container],
|
||||
aside[data-sidebar],
|
||||
[data-sidebar],
|
||||
[data-sidebar] *,
|
||||
#nd-sidebar,
|
||||
#nd-sidebar * {
|
||||
border: none !important;
|
||||
border-right: none !important;
|
||||
border-left: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
/* Override fumadocs background colors for sidebar */
|
||||
.dark #nd-sidebar,
|
||||
.dark [data-sidebar-container],
|
||||
.dark aside[data-sidebar] {
|
||||
--color-fd-muted: transparent !important;
|
||||
--color-fd-secondary: transparent !important;
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Force normal text flow in sidebar */
|
||||
[data-sidebar],
|
||||
[data-sidebar] *,
|
||||
[data-sidebar-viewport],
|
||||
[data-sidebar-viewport] * {
|
||||
writing-mode: horizontal-tb !important;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Code Block Styling (Improved)
|
||||
============================================ */
|
||||
|
||||
/* Apply Geist Mono to code elements */
|
||||
code,
|
||||
pre,
|
||||
pre code {
|
||||
font-family: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
"Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code {
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Light mode inline code */
|
||||
:root:not(.dark) :not(pre) > code {
|
||||
background-color: rgb(243 244 246);
|
||||
color: rgb(220 38 38);
|
||||
border: 1px solid rgb(229 231 235);
|
||||
}
|
||||
|
||||
/* Dark mode inline code */
|
||||
.dark :not(pre) > code {
|
||||
background-color: rgb(31 41 55);
|
||||
color: rgb(248 113 113);
|
||||
border: 1px solid rgb(55 65 81);
|
||||
}
|
||||
|
||||
/* Code block container improvements */
|
||||
pre {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.7;
|
||||
tab-size: 2;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
pre code {
|
||||
display: block;
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
/* Syntax highlighting adjustments for better readability */
|
||||
pre code .line {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
--color-fd-primary: #802fff; /* Purple from control-bar component */
|
||||
}
|
||||
|
||||
/* Custom text highlighting styles */
|
||||
@@ -502,66 +15,4 @@ pre code .line {
|
||||
color: var(--color-fd-primary);
|
||||
}
|
||||
|
||||
/* Add bottom spacing to prevent abrupt page endings */
|
||||
[data-content] {
|
||||
padding-top: 1.5rem !important;
|
||||
padding-bottom: 4rem;
|
||||
}
|
||||
|
||||
/* Alternative fallback for different Fumadocs versions */
|
||||
main article,
|
||||
.docs-page main {
|
||||
padding-top: 1.5rem !important;
|
||||
padding-bottom: 4rem;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Center and Constrain Main Content Width
|
||||
============================================ */
|
||||
|
||||
/* Main content area - center and constrain like turborepo/raindrop */
|
||||
/* Note: --sidebar-offset and --toc-offset are now applied at #nd-docs-layout level */
|
||||
main[data-main] {
|
||||
max-width: var(--spacing-fd-container, 1400px);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-top: 1rem;
|
||||
padding-left: var(--content-gap);
|
||||
padding-right: var(--content-gap);
|
||||
order: 1 !important;
|
||||
}
|
||||
|
||||
/* Adjust for smaller screens */
|
||||
@media (max-width: 768px) {
|
||||
main[data-main] {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure docs page content is properly constrained */
|
||||
[data-docs-page] {
|
||||
max-width: 1400px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-top: 1.5rem !important;
|
||||
}
|
||||
|
||||
/* Override Fumadocs default content padding */
|
||||
article[data-content],
|
||||
div[data-content] {
|
||||
padding-top: 1.5rem !important;
|
||||
}
|
||||
|
||||
/* Remove any unwanted borders/outlines from video elements */
|
||||
video {
|
||||
outline: none !important;
|
||||
border-style: solid !important;
|
||||
}
|
||||
|
||||
/* Tailwind v4 content sources */
|
||||
@source '../app/**/*.{js,ts,jsx,tsx,mdx}';
|
||||
@source '../components/**/*.{js,ts,jsx,tsx,mdx}';
|
||||
@source '../content/**/*.{js,ts,jsx,tsx,mdx}';
|
||||
@source '../mdx-components.tsx';
|
||||
@source '../node_modules/fumadocs-ui/dist/**/*.js';
|
||||
|
||||
@@ -1,38 +1,30 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { RootProvider } from 'fumadocs-ui/provider'
|
||||
import { Inter } from 'next/font/google'
|
||||
import './global.css'
|
||||
import { Analytics } from '@vercel/analytics/next'
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
return children
|
||||
const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
})
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<html lang='en' className={inter.className} suppressHydrationWarning>
|
||||
<body className='flex min-h-screen flex-col'>
|
||||
<RootProvider>
|
||||
{children}
|
||||
<Analytics />
|
||||
</RootProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
export const metadata = {
|
||||
metadataBase: new URL('https://docs.sim.ai'),
|
||||
title: {
|
||||
default: 'Sim Documentation - Visual Workflow Builder for AI Applications',
|
||||
template: '%s',
|
||||
},
|
||||
title: 'Sim Studio',
|
||||
description:
|
||||
'Comprehensive documentation for Sim - the visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines by connecting blocks on a canvas—no coding required.',
|
||||
keywords: [
|
||||
'AI workflow builder',
|
||||
'visual workflow editor',
|
||||
'AI automation',
|
||||
'workflow automation',
|
||||
'AI agents',
|
||||
'no-code AI',
|
||||
'drag and drop workflows',
|
||||
'AI integrations',
|
||||
'workflow canvas',
|
||||
'AI Agent Workflow Builder',
|
||||
'workflow orchestration',
|
||||
'agent builder',
|
||||
'AI workflow automation',
|
||||
'visual programming',
|
||||
],
|
||||
authors: [{ name: 'Sim Team', url: 'https://sim.ai' }],
|
||||
creator: 'Sim',
|
||||
publisher: 'Sim',
|
||||
category: 'Developer Tools',
|
||||
classification: 'Developer Documentation',
|
||||
'Build agents in seconds with a drag and drop workflow builder. Access comprehensive documentation to help you create efficient workflows and maximize your automation capabilities.',
|
||||
manifest: '/favicon/site.webmanifest',
|
||||
icons: {
|
||||
icon: [
|
||||
@@ -45,56 +37,6 @@ export const metadata = {
|
||||
appleWebApp: {
|
||||
capable: true,
|
||||
statusBarStyle: 'default',
|
||||
title: 'Sim Docs',
|
||||
},
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
locale: 'en_US',
|
||||
alternateLocale: ['es_ES', 'fr_FR', 'de_DE', 'ja_JP', 'zh_CN'],
|
||||
url: 'https://docs.sim.ai',
|
||||
siteName: 'Sim Documentation',
|
||||
title: 'Sim Documentation - Visual Workflow Builder for AI Applications',
|
||||
description:
|
||||
'Comprehensive documentation for Sim - the visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines.',
|
||||
images: [
|
||||
{
|
||||
url: 'https://docs.sim.ai/api/og?title=Sim%20Documentation',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'Sim Documentation',
|
||||
},
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'Sim Documentation - Visual Workflow Builder for AI Applications',
|
||||
description:
|
||||
'Comprehensive documentation for Sim - the visual workflow builder for AI applications.',
|
||||
creator: '@simdotai',
|
||||
site: '@simdotai',
|
||||
images: ['https://docs.sim.ai/api/og?title=Sim%20Documentation'],
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
'max-video-preview': -1,
|
||||
'max-image-preview': 'large',
|
||||
'max-snippet': -1,
|
||||
},
|
||||
},
|
||||
alternates: {
|
||||
canonical: 'https://docs.sim.ai',
|
||||
languages: {
|
||||
'x-default': 'https://docs.sim.ai',
|
||||
en: 'https://docs.sim.ai',
|
||||
es: 'https://docs.sim.ai/es',
|
||||
fr: 'https://docs.sim.ai/fr',
|
||||
de: 'https://docs.sim.ai/de',
|
||||
ja: 'https://docs.sim.ai/ja',
|
||||
zh: 'https://docs.sim.ai/zh',
|
||||
},
|
||||
title: 'Sim Studio Docs',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { getLLMText } from '@/lib/llms'
|
||||
import { source } from '@/lib/source'
|
||||
|
||||
export const revalidate = false
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const pages = source.getPages().filter((page) => {
|
||||
if (!page || !page.data || !page.url) return false
|
||||
|
||||
const pathParts = page.url.split('/').filter(Boolean)
|
||||
const hasLangPrefix = pathParts[0] && ['es', 'fr', 'de', 'ja', 'zh'].includes(pathParts[0])
|
||||
|
||||
return !hasLangPrefix
|
||||
})
|
||||
|
||||
const scan = pages.map((page) => getLLMText(page))
|
||||
const scanned = await Promise.all(scan)
|
||||
|
||||
const filtered = scanned.filter((text) => text && text.length > 0)
|
||||
|
||||
return new Response(filtered.join('\n\n---\n\n'), {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error generating LLM full text:', error)
|
||||
return new Response('Error generating full documentation text', { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,16 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { i18n } from '@/lib/i18n'
|
||||
import { getLLMText } from '@/lib/llms'
|
||||
import { source } from '@/lib/source'
|
||||
|
||||
export const revalidate = false
|
||||
|
||||
export async function GET(
|
||||
_request: NextRequest,
|
||||
{ params }: { params: Promise<{ slug?: string[] }> }
|
||||
) {
|
||||
export async function GET(_req: NextRequest, { params }: { params: Promise<{ slug?: string[] }> }) {
|
||||
const { slug } = await params
|
||||
|
||||
let lang: (typeof i18n.languages)[number] = i18n.defaultLanguage
|
||||
let pageSlug = slug
|
||||
|
||||
if (slug && slug.length > 0 && i18n.languages.includes(slug[0] as typeof lang)) {
|
||||
lang = slug[0] as typeof lang
|
||||
pageSlug = slug.slice(1)
|
||||
}
|
||||
|
||||
const page = source.getPage(pageSlug, lang)
|
||||
const page = source.getPage(slug)
|
||||
if (!page) notFound()
|
||||
|
||||
return new NextResponse(await getLLMText(page), {
|
||||
headers: {
|
||||
'Content-Type': 'text/markdown',
|
||||
},
|
||||
})
|
||||
return new NextResponse(await getLLMText(page))
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
|
||||
@@ -1,87 +1,12 @@
|
||||
import { getLLMText } from '@/lib/llms'
|
||||
import { source } from '@/lib/source'
|
||||
|
||||
// cached forever
|
||||
export const revalidate = false
|
||||
|
||||
export async function GET() {
|
||||
const baseUrl = 'https://docs.sim.ai'
|
||||
const scan = source.getPages().map(getLLMText)
|
||||
const scanned = await Promise.all(scan)
|
||||
|
||||
try {
|
||||
const pages = source.getPages().filter((page) => {
|
||||
if (!page || !page.data || !page.url) return false
|
||||
|
||||
const pathParts = page.url.split('/').filter(Boolean)
|
||||
const hasLangPrefix = pathParts[0] && ['es', 'fr', 'de', 'ja', 'zh'].includes(pathParts[0])
|
||||
|
||||
return !hasLangPrefix
|
||||
})
|
||||
|
||||
const sections: Record<string, Array<{ title: string; url: string; description?: string }>> = {}
|
||||
|
||||
pages.forEach((page) => {
|
||||
const pathParts = page.url.split('/').filter(Boolean)
|
||||
const section =
|
||||
pathParts[0] && ['en', 'es', 'fr', 'de', 'ja', 'zh'].includes(pathParts[0])
|
||||
? pathParts[1] || 'root'
|
||||
: pathParts[0] || 'root'
|
||||
|
||||
if (!sections[section]) {
|
||||
sections[section] = []
|
||||
}
|
||||
|
||||
sections[section].push({
|
||||
title: page.data.title || 'Untitled',
|
||||
url: `${baseUrl}${page.url}`,
|
||||
description: page.data.description,
|
||||
})
|
||||
})
|
||||
|
||||
const manifest = `# Sim Documentation
|
||||
|
||||
> Visual Workflow Builder for AI Applications
|
||||
|
||||
Sim is a visual workflow builder for AI applications that lets you build AI agent workflows visually. Create powerful AI agents, automation workflows, and data processing pipelines by connecting blocks on a canvas—no coding required.
|
||||
|
||||
## Documentation Overview
|
||||
|
||||
This file provides an overview of our documentation. For full content of all pages, see ${baseUrl}/llms-full.txt
|
||||
|
||||
## Main Sections
|
||||
|
||||
${Object.entries(sections)
|
||||
.map(([section, items]) => {
|
||||
const sectionTitle = section
|
||||
.split('-')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ')
|
||||
return `### ${sectionTitle}\n\n${items.map((item) => `- ${item.title}: ${item.url}${item.description ? `\n ${item.description}` : ''}`).join('\n')}`
|
||||
})
|
||||
.join('\n\n')}
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- Full documentation content: ${baseUrl}/llms-full.txt
|
||||
- Individual page content: ${baseUrl}/llms.mdx/[page-path]
|
||||
- API documentation: ${baseUrl}/sdks/
|
||||
- Tool integrations: ${baseUrl}/tools/
|
||||
|
||||
## Statistics
|
||||
|
||||
- Total pages: ${pages.length} (English only)
|
||||
- Other languages available at: ${baseUrl}/[lang]/ (es, fr, de, ja, zh)
|
||||
|
||||
---
|
||||
|
||||
Generated: ${new Date().toISOString()}
|
||||
Format: llms.txt v0.1.0
|
||||
See: https://llmstxt.org for specification`
|
||||
|
||||
return new Response(manifest, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error generating LLM manifest:', error)
|
||||
return new Response('Error generating documentation manifest', { status: 500 })
|
||||
}
|
||||
return new Response(scanned.join('\n\n'))
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
export const revalidate = false
|
||||
|
||||
export async function GET() {
|
||||
const baseUrl = 'https://docs.sim.ai'
|
||||
|
||||
const robotsTxt = `# Robots.txt for Sim Documentation
|
||||
# Generated on ${new Date().toISOString()}
|
||||
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Search engine crawlers
|
||||
User-agent: Googlebot
|
||||
Allow: /
|
||||
|
||||
User-agent: Bingbot
|
||||
Allow: /
|
||||
|
||||
User-agent: Slurp
|
||||
Allow: /
|
||||
|
||||
User-agent: DuckDuckBot
|
||||
Allow: /
|
||||
|
||||
User-agent: Baiduspider
|
||||
Allow: /
|
||||
|
||||
User-agent: YandexBot
|
||||
Allow: /
|
||||
|
||||
# AI and LLM crawlers - explicitly allowed for documentation indexing
|
||||
User-agent: GPTBot
|
||||
Allow: /
|
||||
|
||||
User-agent: ChatGPT-User
|
||||
Allow: /
|
||||
|
||||
User-agent: CCBot
|
||||
Allow: /
|
||||
|
||||
User-agent: anthropic-ai
|
||||
Allow: /
|
||||
|
||||
User-agent: Claude-Web
|
||||
Allow: /
|
||||
|
||||
User-agent: Applebot
|
||||
Allow: /
|
||||
|
||||
User-agent: PerplexityBot
|
||||
Allow: /
|
||||
|
||||
User-agent: Diffbot
|
||||
Allow: /
|
||||
|
||||
User-agent: FacebookBot
|
||||
Allow: /
|
||||
|
||||
User-agent: cohere-ai
|
||||
Allow: /
|
||||
|
||||
# Disallow admin and internal paths (if any exist)
|
||||
Disallow: /.next/
|
||||
Disallow: /api/internal/
|
||||
Disallow: /_next/static/
|
||||
Disallow: /admin/
|
||||
|
||||
# Allow but don't prioritize these
|
||||
Allow: /api/search
|
||||
Allow: /llms.txt
|
||||
Allow: /llms-full.txt
|
||||
Allow: /llms.mdx/
|
||||
|
||||
# Sitemaps
|
||||
Sitemap: ${baseUrl}/sitemap.xml
|
||||
|
||||
# Crawl delay for aggressive bots (optional)
|
||||
# Crawl-delay: 1
|
||||
|
||||
# Additional resources for AI indexing
|
||||
# See https://github.com/AnswerDotAI/llms-txt for more info
|
||||
# LLM-friendly content:
|
||||
# Manifest: ${baseUrl}/llms.txt
|
||||
# Full content: ${baseUrl}/llms-full.txt
|
||||
# Individual pages: ${baseUrl}/llms.mdx/[page-path]
|
||||
|
||||
# Multi-language documentation available at:
|
||||
# ${baseUrl}/en - English
|
||||
# ${baseUrl}/es - Español
|
||||
# ${baseUrl}/fr - Français
|
||||
# ${baseUrl}/de - Deutsch
|
||||
# ${baseUrl}/ja - 日本語
|
||||
# ${baseUrl}/zh - 简体中文`
|
||||
|
||||
return new Response(robotsTxt, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { i18n } from '@/lib/i18n'
|
||||
import { source } from '@/lib/source'
|
||||
|
||||
export const revalidate = false
|
||||
|
||||
export async function GET() {
|
||||
const baseUrl = 'https://docs.sim.ai'
|
||||
|
||||
const allPages = source.getPages()
|
||||
|
||||
const getPriority = (url: string): string => {
|
||||
if (url === '/introduction' || url === '/') return '1.0'
|
||||
if (url === '/getting-started') return '0.9'
|
||||
if (url.match(/^\/[^/]+$/)) return '0.8'
|
||||
if (url.includes('/sdks/') || url.includes('/tools/')) return '0.7'
|
||||
return '0.6'
|
||||
}
|
||||
|
||||
const urls = allPages
|
||||
.flatMap((page) => {
|
||||
const urlWithoutLang = page.url.replace(/^\/[a-z]{2}\//, '/')
|
||||
|
||||
return i18n.languages.map((lang) => {
|
||||
const url =
|
||||
lang === i18n.defaultLanguage
|
||||
? `${baseUrl}${urlWithoutLang}`
|
||||
: `${baseUrl}/${lang}${urlWithoutLang}`
|
||||
|
||||
return ` <url>
|
||||
<loc>${url}</loc>
|
||||
<lastmod>${new Date().toISOString().split('T')[0]}</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>${getPriority(urlWithoutLang)}</priority>
|
||||
${i18n.languages.length > 1 ? generateAlternateLinks(baseUrl, urlWithoutLang) : ''}
|
||||
</url>`
|
||||
})
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
||||
${urls}
|
||||
</urlset>`
|
||||
|
||||
return new Response(sitemap, {
|
||||
headers: {
|
||||
'Content-Type': 'application/xml',
|
||||
'Cache-Control': 'public, max-age=3600, s-maxage=3600',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function generateAlternateLinks(baseUrl: string, urlWithoutLang: string): string {
|
||||
return i18n.languages
|
||||
.map((lang) => {
|
||||
const url =
|
||||
lang === i18n.defaultLanguage
|
||||
? `${baseUrl}${urlWithoutLang}`
|
||||
: `${baseUrl}/${lang}${urlWithoutLang}`
|
||||
return ` <xhtml:link rel="alternate" hreflang="${lang}" href="${url}" />`
|
||||
})
|
||||
.join('\n')
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"aliases": {
|
||||
"uiDir": "./components/ui",
|
||||
"componentsDir": "./components",
|
||||
"blockDir": "./components",
|
||||
"cssDir": "./styles",
|
||||
"libDir": "./lib"
|
||||
},
|
||||
"baseDir": "",
|
||||
"commands": {}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface PageNavigationArrowsProps {
|
||||
previous?: {
|
||||
url: string
|
||||
}
|
||||
next?: {
|
||||
url: string
|
||||
}
|
||||
}
|
||||
|
||||
export function PageNavigationArrows({ previous, next }: PageNavigationArrowsProps) {
|
||||
if (!previous && !next) return null
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-2'>
|
||||
{previous && (
|
||||
<Link
|
||||
href={previous.url}
|
||||
className='inline-flex items-center justify-center gap-1.5 rounded-lg border border-border/40 bg-background px-2.5 py-1.5 text-muted-foreground/60 text-sm transition-all hover:border-border hover:bg-accent/50 hover:text-muted-foreground'
|
||||
aria-label='Previous page'
|
||||
title='Previous page'
|
||||
>
|
||||
<ChevronLeft className='h-4 w-4' />
|
||||
</Link>
|
||||
)}
|
||||
{next && (
|
||||
<Link
|
||||
href={next.url}
|
||||
className='inline-flex items-center justify-center gap-1.5 rounded-lg border border-border/40 bg-background px-2.5 py-1.5 text-muted-foreground/60 text-sm transition-all hover:border-border hover:bg-accent/50 hover:text-muted-foreground'
|
||||
aria-label='Next page'
|
||||
title='Next page'
|
||||
>
|
||||
<ChevronRight className='h-4 w-4' />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { type ReactNode, useEffect, useState } from 'react'
|
||||
import type { Folder, Item, Separator } from 'fumadocs-core/page-tree'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const LANG_PREFIXES = ['/en', '/es', '/fr', '/de', '/ja', '/zh']
|
||||
|
||||
function stripLangPrefix(path: string): string {
|
||||
for (const prefix of LANG_PREFIXES) {
|
||||
if (path === prefix) return '/'
|
||||
if (path.startsWith(`${prefix}/`)) return path.slice(prefix.length)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
function isActive(url: string, pathname: string, nested = true): boolean {
|
||||
const normalizedPathname = stripLangPrefix(pathname)
|
||||
const normalizedUrl = stripLangPrefix(url)
|
||||
return (
|
||||
normalizedUrl === normalizedPathname ||
|
||||
(nested && normalizedPathname.startsWith(`${normalizedUrl}/`))
|
||||
)
|
||||
}
|
||||
|
||||
export function SidebarItem({ item }: { item: Item }) {
|
||||
const pathname = usePathname()
|
||||
const active = isActive(item.url, pathname, false)
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={item.url}
|
||||
data-active={active}
|
||||
className={cn(
|
||||
// Mobile styles (default)
|
||||
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
|
||||
'text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground',
|
||||
active && 'bg-fd-primary/10 font-medium text-fd-primary',
|
||||
// Desktop styles (lg+)
|
||||
'lg:mb-[0.0625rem] lg:block lg:rounded-md lg:px-2.5 lg:py-1.5 lg:font-normal lg:text-[13px] lg:leading-tight',
|
||||
'lg:text-gray-600 lg:dark:text-gray-400',
|
||||
!active && 'lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40',
|
||||
active &&
|
||||
'lg:bg-purple-50/80 lg:font-normal lg:text-purple-600 lg:dark:bg-purple-900/15 lg:dark:text-purple-400'
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export function SidebarFolder({ item, children }: { item: Folder; children: ReactNode }) {
|
||||
const pathname = usePathname()
|
||||
const hasActiveChild = checkHasActiveChild(item, pathname)
|
||||
const hasChildren = item.children.length > 0
|
||||
const [open, setOpen] = useState(hasActiveChild)
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(hasActiveChild)
|
||||
}, [hasActiveChild])
|
||||
|
||||
const active = item.index ? isActive(item.index.url, pathname, false) : false
|
||||
|
||||
if (item.index && !hasChildren) {
|
||||
return (
|
||||
<Link
|
||||
href={item.index.url}
|
||||
data-active={active}
|
||||
className={cn(
|
||||
// Mobile styles (default)
|
||||
'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
|
||||
'text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground',
|
||||
active && 'bg-fd-primary/10 font-medium text-fd-primary',
|
||||
// Desktop styles (lg+)
|
||||
'lg:mb-[0.0625rem] lg:block lg:rounded-md lg:px-2.5 lg:py-1.5 lg:font-normal lg:text-[13px] lg:leading-tight',
|
||||
'lg:text-gray-600 lg:dark:text-gray-400',
|
||||
!active && 'lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40',
|
||||
active &&
|
||||
'lg:bg-purple-50/80 lg:font-normal lg:text-purple-600 lg:dark:bg-purple-900/15 lg:dark:text-purple-400'
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col lg:mb-[0.0625rem]'>
|
||||
<div className='flex w-full items-center lg:gap-0.5'>
|
||||
{item.index ? (
|
||||
<Link
|
||||
href={item.index.url}
|
||||
data-active={active}
|
||||
className={cn(
|
||||
// Mobile styles (default)
|
||||
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
|
||||
'text-fd-muted-foreground hover:bg-fd-accent/50 hover:text-fd-accent-foreground',
|
||||
active && 'bg-fd-primary/10 font-medium text-fd-primary',
|
||||
// Desktop styles (lg+)
|
||||
'lg:block lg:flex-1 lg:rounded-md lg:px-2.5 lg:py-1.5 lg:font-medium lg:text-[13px] lg:leading-tight',
|
||||
'lg:text-gray-800 lg:dark:text-gray-200',
|
||||
!active && 'lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40',
|
||||
active &&
|
||||
'lg:bg-purple-50/80 lg:text-purple-600 lg:dark:bg-purple-900/15 lg:dark:text-purple-400'
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
className={cn(
|
||||
// Mobile styles (default)
|
||||
'flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors',
|
||||
'text-fd-muted-foreground hover:bg-fd-accent/50',
|
||||
// Desktop styles (lg+)
|
||||
'lg:flex lg:w-full lg:cursor-pointer lg:items-center lg:justify-between lg:rounded-md lg:px-2.5 lg:py-1.5 lg:text-left lg:font-medium lg:text-[13px] lg:leading-tight',
|
||||
'lg:text-gray-800 lg:hover:bg-gray-100/60 lg:dark:text-gray-200 lg:dark:hover:bg-gray-800/40'
|
||||
)}
|
||||
>
|
||||
<span>{item.name}</span>
|
||||
{/* Desktop-only chevron for non-index folders */}
|
||||
<ChevronRight
|
||||
className={cn(
|
||||
'ml-auto hidden h-3 w-3 flex-shrink-0 text-gray-400 transition-transform duration-200 ease-in-out lg:block dark:text-gray-500',
|
||||
open && 'rotate-90'
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
{hasChildren && (
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
className={cn(
|
||||
// Mobile styles
|
||||
'rounded p-1 hover:bg-fd-accent/50',
|
||||
// Desktop styles
|
||||
'lg:cursor-pointer lg:rounded lg:p-1 lg:transition-colors lg:hover:bg-gray-100/60 lg:dark:hover:bg-gray-800/40'
|
||||
)}
|
||||
aria-label={open ? 'Collapse' : 'Expand'}
|
||||
>
|
||||
<ChevronRight
|
||||
className={cn(
|
||||
// Mobile styles
|
||||
'h-4 w-4 transition-transform',
|
||||
// Desktop styles
|
||||
'lg:h-3 lg:w-3 lg:text-gray-400 lg:duration-200 lg:ease-in-out lg:dark:text-gray-500',
|
||||
open && 'rotate-90'
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{hasChildren && (
|
||||
<div
|
||||
className={cn(
|
||||
'overflow-hidden transition-all duration-200 ease-in-out',
|
||||
open ? 'max-h-[10000px] opacity-100' : 'max-h-0 opacity-0'
|
||||
)}
|
||||
>
|
||||
{/* Mobile: simple indent */}
|
||||
<div className='ml-4 flex flex-col gap-0.5 lg:hidden'>{children}</div>
|
||||
{/* Desktop: styled with border */}
|
||||
<ul className='mt-0.5 ml-2 hidden space-y-[0.0625rem] border-gray-200/60 border-l pl-2.5 lg:block dark:border-gray-700/60'>
|
||||
{children}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SidebarSeparator({ item }: { item: Separator }) {
|
||||
return (
|
||||
<p
|
||||
className={cn(
|
||||
// Mobile styles
|
||||
'mt-4 mb-2 px-2 font-medium text-fd-muted-foreground text-xs',
|
||||
// Desktop styles
|
||||
'lg:mt-4 lg:mb-1.5 lg:px-2.5 lg:font-semibold lg:text-[10px] lg:text-gray-500/80 lg:uppercase lg:tracking-wide lg:dark:text-gray-500'
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
function checkHasActiveChild(node: Folder, pathname: string): boolean {
|
||||
if (node.index && isActive(node.index.url, pathname)) {
|
||||
return true
|
||||
}
|
||||
|
||||
for (const child of node.children) {
|
||||
if (child.type === 'page' && isActive(child.url, pathname)) {
|
||||
return true
|
||||
}
|
||||
if (child.type === 'folder' && checkHasActiveChild(child, pathname)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { ArrowRight, ChevronRight } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export function TOCFooter() {
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
|
||||
return (
|
||||
<div className='sticky bottom-0 mt-6'>
|
||||
<div className='flex flex-col gap-2 rounded-lg border border-border bg-secondary p-6 text-sm'>
|
||||
<div className='text-balance font-semibold text-base leading-tight'>
|
||||
Start building today
|
||||
</div>
|
||||
<div className='text-muted-foreground'>Trusted by over 60,000 builders.</div>
|
||||
<div className='text-muted-foreground'>
|
||||
Build Agentic workflows visually on a drag-and-drop canvas or with natural language.
|
||||
</div>
|
||||
<Link
|
||||
href='https://sim.ai/signup'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
className='group mt-2 inline-flex h-8 w-fit items-center justify-center gap-1 whitespace-nowrap rounded-[10px] border border-[#6F3DFA] bg-gradient-to-b from-[#8357FF] to-[#6F3DFA] px-3 pr-[10px] pl-[12px] font-medium text-sm text-white shadow-[inset_0_2px_4px_0_#9B77FF] outline-none transition-all hover:shadow-lg focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50'
|
||||
aria-label='Get started with Sim - Sign up for free'
|
||||
>
|
||||
<span>Get started</span>
|
||||
<span className='inline-flex transition-transform duration-200 group-hover:translate-x-0.5'>
|
||||
{isHovered ? (
|
||||
<ArrowRight className='h-4 w-4' aria-hidden='true' />
|
||||
) : (
|
||||
<ChevronRight className='h-4 w-4' aria-hidden='true' />
|
||||
)}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
10
apps/docs/components/mdx-components.tsx
Normal file
10
apps/docs/components/mdx-components.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import defaultMdxComponents from 'fumadocs-ui/mdx'
|
||||
import { ThemeImage } from './ui/theme-image'
|
||||
|
||||
// Extend the default MDX components with our custom components
|
||||
const mdxComponents = {
|
||||
...defaultMdxComponents,
|
||||
ThemeImage,
|
||||
}
|
||||
|
||||
export default mdxComponents
|
||||
@@ -1,66 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { LanguageDropdown } from '@/components/ui/language-dropdown'
|
||||
import { SearchTrigger } from '@/components/ui/search-trigger'
|
||||
import { ThemeToggle } from '@/components/ui/theme-toggle'
|
||||
|
||||
export function Navbar() {
|
||||
return (
|
||||
<nav
|
||||
className='sticky top-0 z-50 border-border/50 border-b'
|
||||
style={{
|
||||
backdropFilter: 'blur(25px) saturate(180%)',
|
||||
WebkitBackdropFilter: 'blur(25px) saturate(180%)',
|
||||
}}
|
||||
>
|
||||
{/* Desktop: Single row layout */}
|
||||
<div className='hidden h-16 w-full items-center lg:flex'>
|
||||
<div
|
||||
className='relative flex w-full items-center justify-between'
|
||||
style={{
|
||||
paddingLeft: 'calc(var(--sidebar-offset) + 32px)',
|
||||
paddingRight: 'calc(var(--toc-offset) + 60px)',
|
||||
}}
|
||||
>
|
||||
{/* Left cluster: logo */}
|
||||
<div className='flex items-center'>
|
||||
<Link href='/' className='flex min-w-[100px] items-center'>
|
||||
<Image
|
||||
src='/static/logo.png'
|
||||
alt='Sim'
|
||||
width={72}
|
||||
height={28}
|
||||
className='h-7 w-auto'
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Center cluster: search - absolutely positioned to center */}
|
||||
<div className='-translate-x-1/2 absolute left-1/2 flex items-center justify-center'>
|
||||
<SearchTrigger />
|
||||
</div>
|
||||
|
||||
{/* Right cluster aligns with TOC edge */}
|
||||
<div className='flex items-center gap-4'>
|
||||
<Link
|
||||
href='https://sim.ai'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='rounded-xl px-3 py-2 font-normal text-[0.9375rem] text-foreground/60 leading-[1.4] transition-colors hover:bg-foreground/8 hover:text-foreground'
|
||||
style={{
|
||||
fontFamily:
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||
}}
|
||||
>
|
||||
Platform
|
||||
</Link>
|
||||
<LanguageDropdown />
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button'
|
||||
import { Check, Copy } from 'lucide-react'
|
||||
|
||||
const cache = new Map<string, string>()
|
||||
|
||||
export function LLMCopyButton({
|
||||
markdownUrl,
|
||||
}: {
|
||||
/**
|
||||
* A URL to fetch the raw Markdown/MDX content of page
|
||||
*/
|
||||
markdownUrl: string
|
||||
}) {
|
||||
const [isLoading, setLoading] = useState(false)
|
||||
const [checked, onClick] = useCopyButton(async () => {
|
||||
const cached = cache.get(markdownUrl)
|
||||
if (cached) return navigator.clipboard.writeText(cached)
|
||||
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
'text/plain': fetch(markdownUrl).then(async (res) => {
|
||||
const content = await res.text()
|
||||
cache.set(markdownUrl, content)
|
||||
|
||||
return content
|
||||
}),
|
||||
}),
|
||||
])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<button
|
||||
disabled={isLoading}
|
||||
onClick={onClick}
|
||||
className='flex cursor-pointer items-center gap-1.5 rounded-lg border border-border/40 bg-background px-2.5 py-2 text-muted-foreground/60 text-sm leading-none transition-all hover:border-border hover:bg-accent/50 hover:text-muted-foreground'
|
||||
aria-label={checked ? 'Copied to clipboard' : 'Copy page content'}
|
||||
>
|
||||
{checked ? (
|
||||
<>
|
||||
<Check className='h-3.5 w-3.5' />
|
||||
<span>Copied</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className='h-3.5 w-3.5' />
|
||||
<span>Copy page</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
import Script from 'next/script'
|
||||
|
||||
interface StructuredDataProps {
|
||||
title: string
|
||||
description: string
|
||||
url: string
|
||||
lang: string
|
||||
dateModified?: string
|
||||
breadcrumb?: Array<{ name: string; url: string }>
|
||||
}
|
||||
|
||||
export function StructuredData({
|
||||
title,
|
||||
description,
|
||||
url,
|
||||
lang,
|
||||
dateModified,
|
||||
breadcrumb,
|
||||
}: StructuredDataProps) {
|
||||
const baseUrl = 'https://docs.sim.ai'
|
||||
|
||||
const articleStructuredData = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'TechArticle',
|
||||
headline: title,
|
||||
description: description,
|
||||
url: url,
|
||||
datePublished: dateModified || new Date().toISOString(),
|
||||
dateModified: dateModified || new Date().toISOString(),
|
||||
author: {
|
||||
'@type': 'Organization',
|
||||
name: 'Sim Team',
|
||||
url: baseUrl,
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'Sim',
|
||||
url: baseUrl,
|
||||
logo: {
|
||||
'@type': 'ImageObject',
|
||||
url: `${baseUrl}/static/logo.png`,
|
||||
},
|
||||
},
|
||||
mainEntityOfPage: {
|
||||
'@type': 'WebPage',
|
||||
'@id': url,
|
||||
},
|
||||
inLanguage: lang,
|
||||
isPartOf: {
|
||||
'@type': 'WebSite',
|
||||
name: 'Sim Documentation',
|
||||
url: baseUrl,
|
||||
},
|
||||
potentialAction: {
|
||||
'@type': 'ReadAction',
|
||||
target: url,
|
||||
},
|
||||
}
|
||||
|
||||
const breadcrumbStructuredData = breadcrumb && {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: breadcrumb.map((item, index) => ({
|
||||
'@type': 'ListItem',
|
||||
position: index + 1,
|
||||
name: item.name,
|
||||
item: item.url,
|
||||
})),
|
||||
}
|
||||
|
||||
const websiteStructuredData = url === baseUrl && {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
name: 'Sim Documentation',
|
||||
url: baseUrl,
|
||||
description:
|
||||
'Comprehensive documentation for Sim visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines.',
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'Sim',
|
||||
url: baseUrl,
|
||||
},
|
||||
potentialAction: {
|
||||
'@type': 'SearchAction',
|
||||
target: {
|
||||
'@type': 'EntryPoint',
|
||||
urlTemplate: `${baseUrl}/search?q={search_term_string}`,
|
||||
},
|
||||
'query-input': 'required name=search_term_string',
|
||||
},
|
||||
inLanguage: ['en', 'es', 'fr', 'de', 'ja', 'zh'],
|
||||
}
|
||||
|
||||
const faqStructuredData = title.toLowerCase().includes('faq') && {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: [],
|
||||
}
|
||||
|
||||
const softwareStructuredData = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'Sim',
|
||||
applicationCategory: 'DeveloperApplication',
|
||||
operatingSystem: 'Any',
|
||||
description:
|
||||
'Visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines by connecting blocks on a canvas—no coding required.',
|
||||
url: baseUrl,
|
||||
author: {
|
||||
'@type': 'Organization',
|
||||
name: 'Sim Team',
|
||||
},
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
category: 'Developer Tools',
|
||||
},
|
||||
featureList: [
|
||||
'Visual workflow builder with drag-and-drop interface',
|
||||
'AI agent creation and automation',
|
||||
'80+ built-in integrations',
|
||||
'Real-time team collaboration',
|
||||
'Multiple deployment options',
|
||||
'Custom integrations via MCP protocol',
|
||||
],
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script
|
||||
id='article-structured-data'
|
||||
type='application/ld+json'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(articleStructuredData),
|
||||
}}
|
||||
/>
|
||||
{breadcrumbStructuredData && (
|
||||
<Script
|
||||
id='breadcrumb-structured-data'
|
||||
type='application/ld+json'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(breadcrumbStructuredData),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{websiteStructuredData && (
|
||||
<Script
|
||||
id='website-structured-data'
|
||||
type='application/ld+json'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(websiteStructuredData),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{faqStructuredData && (
|
||||
<Script
|
||||
id='faq-structured-data'
|
||||
type='application/ld+json'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(faqStructuredData),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{url === baseUrl && (
|
||||
<Script
|
||||
id='software-structured-data'
|
||||
type='application/ld+json'
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(softwareStructuredData),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,40 +1,44 @@
|
||||
'use client'
|
||||
|
||||
import type * as React from 'react'
|
||||
import { blockTypeToIconMap } from '@/components/ui/icon-mapping'
|
||||
|
||||
interface BlockInfoCardProps {
|
||||
type: string
|
||||
color: string
|
||||
icon?: React.ComponentType<{ className?: string }>
|
||||
iconSvg?: string // Deprecated: Use automatic icon resolution instead
|
||||
icon?: boolean
|
||||
iconSvg?: string
|
||||
}
|
||||
|
||||
export function BlockInfoCard({
|
||||
type,
|
||||
color,
|
||||
icon: IconComponent,
|
||||
icon = false,
|
||||
iconSvg,
|
||||
}: BlockInfoCardProps): React.ReactNode {
|
||||
// Auto-resolve icon component from block type if not explicitly provided
|
||||
const ResolvedIcon = IconComponent || blockTypeToIconMap[type] || null
|
||||
|
||||
return (
|
||||
<div className='mb-6 overflow-hidden rounded-lg border border-border'>
|
||||
<div className='flex items-center justify-center p-6'>
|
||||
<div
|
||||
className='flex h-20 w-20 items-center justify-center rounded-lg'
|
||||
style={{ background: color }}
|
||||
style={{ backgroundColor: color }}
|
||||
>
|
||||
{ResolvedIcon ? (
|
||||
<ResolvedIcon className='h-10 w-10 text-white' />
|
||||
) : iconSvg ? (
|
||||
{iconSvg ? (
|
||||
<div className='h-10 w-10 text-white' dangerouslySetInnerHTML={{ __html: iconSvg }} />
|
||||
) : (
|
||||
<div className='font-mono text-xl opacity-70'>{type.substring(0, 2)}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{icon && (
|
||||
<style jsx global>{`
|
||||
.block-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 1rem auto;
|
||||
display: block;
|
||||
}
|
||||
`}</style>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
163
apps/docs/components/ui/block-types.tsx
Normal file
163
apps/docs/components/ui/block-types.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
AgentIcon,
|
||||
ApiIcon,
|
||||
ChartBarIcon,
|
||||
CodeIcon,
|
||||
ConditionalIcon,
|
||||
ConnectIcon,
|
||||
ResponseIcon,
|
||||
} from '../icons'
|
||||
|
||||
// Custom Feature component specifically for BlockTypes to handle the 6-item layout
|
||||
const BlockFeature = ({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
href,
|
||||
index,
|
||||
totalItems,
|
||||
itemsPerRow,
|
||||
}: {
|
||||
title: string
|
||||
description: string
|
||||
icon: React.ReactNode
|
||||
href?: string
|
||||
index: number
|
||||
totalItems: number
|
||||
itemsPerRow: number
|
||||
}) => {
|
||||
const blockColor = {
|
||||
'--block-color':
|
||||
title === 'Agent'
|
||||
? '#8b5cf6'
|
||||
: title === 'API'
|
||||
? '#3b82f6'
|
||||
: title === 'Condition'
|
||||
? '#f59e0b'
|
||||
: title === 'Function'
|
||||
? '#10b981'
|
||||
: title === 'Router'
|
||||
? '#6366f1'
|
||||
: title === 'Evaluator'
|
||||
? '#ef4444'
|
||||
: '#8b5cf6',
|
||||
} as React.CSSProperties
|
||||
|
||||
const content = (
|
||||
<>
|
||||
{index < itemsPerRow && (
|
||||
<div className='pointer-events-none absolute inset-0 h-full w-full bg-gradient-to-t from-neutral-100 to-transparent opacity-0 transition duration-200 group-hover/feature:opacity-100 dark:from-neutral-800' />
|
||||
)}
|
||||
{index >= itemsPerRow && (
|
||||
<div className='pointer-events-none absolute inset-0 h-full w-full bg-gradient-to-b from-neutral-100 to-transparent opacity-0 transition duration-200 group-hover/feature:opacity-100 dark:from-neutral-800' />
|
||||
)}
|
||||
<div
|
||||
className='relative z-10 mb-4 px-10 text-neutral-500 transition-colors duration-200 group-hover/feature:text-[color:var(--block-color,#8b5cf6)] dark:text-neutral-400 dark:group-hover/feature:text-[color:var(--block-color,#a78bfa)]'
|
||||
style={blockColor}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
<div className='relative z-10 mb-2 px-10 font-bold text-lg'>
|
||||
<div
|
||||
className='absolute inset-y-0 left-0 h-6 w-1 origin-center rounded-tr-full rounded-br-full bg-neutral-300 transition-all duration-200 group-hover/feature:h-8 group-hover/feature:bg-[color:var(--block-color,#8b5cf6)] dark:bg-neutral-700'
|
||||
style={blockColor}
|
||||
/>
|
||||
<span className='inline-block text-neutral-800 transition duration-200 group-hover/feature:translate-x-2 dark:text-neutral-100'>
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
<p className='relative z-10 max-w-xs px-10 text-neutral-600 text-sm dark:text-neutral-300'>
|
||||
{description}
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
|
||||
const containerClasses = cn(
|
||||
'flex flex-col lg:border-r py-5 relative group/feature dark:border-neutral-800',
|
||||
(index === 0 || index === itemsPerRow) && 'lg:border-l dark:border-neutral-800',
|
||||
index < itemsPerRow && 'lg:border-b dark:border-neutral-800',
|
||||
href && 'cursor-pointer hover:bg-neutral-50 dark:hover:bg-neutral-900/50 transition-colors'
|
||||
)
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<a href={href} className={containerClasses} style={{ textDecoration: 'none' }}>
|
||||
{content}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
return <div className={containerClasses}>{content}</div>
|
||||
}
|
||||
|
||||
export function BlockTypes() {
|
||||
const features = [
|
||||
{
|
||||
title: 'Agent',
|
||||
description:
|
||||
'Create powerful AI agents using any LLM provider with customizable system prompts and tool integrations.',
|
||||
icon: <AgentIcon className='h-6 w-6' />,
|
||||
href: '/blocks/agent',
|
||||
},
|
||||
{
|
||||
title: 'API',
|
||||
description:
|
||||
'Connect to any external API with support for all standard HTTP methods and customizable request parameters.',
|
||||
icon: <ApiIcon className='h-6 w-6' />,
|
||||
href: '/blocks/api',
|
||||
},
|
||||
{
|
||||
title: 'Condition',
|
||||
description:
|
||||
'Add a condition to the workflow to branch the execution path based on a boolean expression.',
|
||||
icon: <ConditionalIcon className='h-6 w-6' />,
|
||||
href: '/blocks/condition',
|
||||
},
|
||||
{
|
||||
title: 'Function',
|
||||
description:
|
||||
'Execute custom JavaScript or TypeScript code within your workflow to transform data or implement complex logic.',
|
||||
icon: <CodeIcon className='h-6 w-6' />,
|
||||
href: '/blocks/function',
|
||||
},
|
||||
{
|
||||
title: 'Router',
|
||||
description:
|
||||
'Intelligently direct workflow execution to different paths based on input analysis.',
|
||||
icon: <ConnectIcon className='h-6 w-6' />,
|
||||
href: '/blocks/router',
|
||||
},
|
||||
{
|
||||
title: 'Evaluator',
|
||||
description:
|
||||
'Assess content using customizable evaluation metrics and scoring criteria across multiple dimensions.',
|
||||
icon: <ChartBarIcon className='h-6 w-6' />,
|
||||
href: '/blocks/evaluator',
|
||||
},
|
||||
{
|
||||
title: 'Response',
|
||||
description:
|
||||
'Send a response back to the caller with customizable data, status, and headers.',
|
||||
icon: <ResponseIcon className='h-6 w-6' />,
|
||||
href: '/blocks/response',
|
||||
},
|
||||
]
|
||||
|
||||
const totalItems = features.length
|
||||
const itemsPerRow = 3 // For large screens
|
||||
|
||||
return (
|
||||
<div className='relative z-10 mx-auto grid max-w-7xl grid-cols-1 py-10 md:grid-cols-2 lg:grid-cols-3'>
|
||||
{features.map((feature, index) => (
|
||||
<BlockFeature
|
||||
key={feature.title}
|
||||
{...feature}
|
||||
index={index}
|
||||
totalItems={totalItems}
|
||||
itemsPerRow={itemsPerRow}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { cva, type VariantProps } from 'class-variance-authority'
|
||||
|
||||
const variants = {
|
||||
primary: 'bg-fd-primary text-fd-primary-foreground hover:bg-fd-primary/80',
|
||||
outline: 'border hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||
ghost: 'hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||
secondary:
|
||||
'border bg-fd-secondary text-fd-secondary-foreground hover:bg-fd-accent hover:text-fd-accent-foreground',
|
||||
} as const
|
||||
|
||||
export const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-md p-2 text-sm font-medium transition-colors duration-100 disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fd-ring',
|
||||
{
|
||||
variants: {
|
||||
variant: variants,
|
||||
color: variants,
|
||||
size: {
|
||||
sm: 'gap-1 px-2 py-1.5 text-xs',
|
||||
icon: 'p-1.5 [&_svg]:size-5',
|
||||
'icon-sm': 'p-1.5 [&_svg]:size-4.5',
|
||||
'icon-xs': 'p-1 [&_svg]:size-4',
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export type ButtonProps = VariantProps<typeof buttonVariants>
|
||||
@@ -1,50 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { CodeBlock as FumadocsCodeBlock } from 'fumadocs-ui/components/codeblock'
|
||||
import { Check, Copy } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function CodeBlock(props: React.ComponentProps<typeof FumadocsCodeBlock>) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const handleCopy = async (text: string) => {
|
||||
await navigator.clipboard.writeText(text)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
|
||||
return (
|
||||
<FumadocsCodeBlock
|
||||
{...props}
|
||||
Actions={({ children, className }) => (
|
||||
<div className={cn('empty:hidden', className)}>
|
||||
{/* Custom copy button */}
|
||||
<button
|
||||
type='button'
|
||||
aria-label={copied ? 'Copied Text' : 'Copy Text'}
|
||||
onClick={(e) => {
|
||||
const pre = (e.currentTarget as HTMLElement)
|
||||
.closest('.nd-codeblock')
|
||||
?.querySelector('pre')
|
||||
if (pre) handleCopy(pre.textContent || '')
|
||||
}}
|
||||
className={cn(
|
||||
'cursor-pointer rounded-md p-2 transition-all',
|
||||
'border border-border bg-background/80 hover:bg-muted',
|
||||
'backdrop-blur-sm'
|
||||
)}
|
||||
>
|
||||
<span className='flex items-center justify-center'>
|
||||
{copied ? (
|
||||
<Check size={16} className='text-green-600 dark:text-green-400' />
|
||||
) : (
|
||||
<Copy size={16} className='text-muted-foreground' />
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
102
apps/docs/components/ui/features.tsx
Normal file
102
apps/docs/components/ui/features.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import {
|
||||
IconAdjustmentsBolt,
|
||||
IconCloud,
|
||||
IconEaseInOut,
|
||||
IconHeart,
|
||||
IconHelp,
|
||||
IconHistory,
|
||||
IconRouteAltLeft,
|
||||
IconTerminal2,
|
||||
} from '@tabler/icons-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function Features() {
|
||||
const features = [
|
||||
{
|
||||
title: 'Multi-LLM Support',
|
||||
description: 'Connect to any LLM provider including OpenAI, Anthropic, and more',
|
||||
icon: <IconCloud />,
|
||||
},
|
||||
{
|
||||
title: 'API Deployment',
|
||||
description: 'Deploy your workflows as secure, scalable APIs',
|
||||
icon: <IconTerminal2 />,
|
||||
},
|
||||
{
|
||||
title: 'Webhook Integration',
|
||||
description: 'Trigger workflows via webhooks from external services',
|
||||
icon: <IconRouteAltLeft />,
|
||||
},
|
||||
{
|
||||
title: 'Scheduled Execution',
|
||||
description: 'Schedule workflows to run at specific times or intervals',
|
||||
icon: <IconEaseInOut />,
|
||||
},
|
||||
{
|
||||
title: '40+ Integrations',
|
||||
description: 'Connect to hundreds of external services and data sources',
|
||||
icon: <IconAdjustmentsBolt />,
|
||||
},
|
||||
{
|
||||
title: 'Visual Debugging',
|
||||
description: 'Debug workflows visually with detailed execution logs',
|
||||
icon: <IconHelp />,
|
||||
},
|
||||
{
|
||||
title: 'Version Control',
|
||||
description: 'Track changes and roll back to previous versions',
|
||||
icon: <IconHistory />,
|
||||
},
|
||||
{
|
||||
title: 'Team Collaboration',
|
||||
description: 'Collaborate with team members on workflow development',
|
||||
icon: <IconHeart />,
|
||||
},
|
||||
]
|
||||
return (
|
||||
<div className='relative z-20 mx-auto grid max-w-7xl grid-cols-1 py-10 md:grid-cols-2 lg:grid-cols-4'>
|
||||
{features.map((feature, index) => (
|
||||
<Feature key={feature.title} {...feature} index={index} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Feature = ({
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
index,
|
||||
}: {
|
||||
title: string
|
||||
description: string
|
||||
icon: React.ReactNode
|
||||
index: number
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'group/feature relative flex flex-col py-5 lg:border-r dark:border-neutral-800',
|
||||
(index === 0 || index === 4) && 'lg:border-l dark:border-neutral-800',
|
||||
index < 4 && 'lg:border-b dark:border-neutral-800'
|
||||
)}
|
||||
>
|
||||
{index < 4 && (
|
||||
<div className='pointer-events-none absolute inset-0 h-full w-full bg-gradient-to-t from-neutral-100 to-transparent opacity-0 transition duration-200 group-hover/feature:opacity-100 dark:from-neutral-800' />
|
||||
)}
|
||||
{index >= 4 && (
|
||||
<div className='pointer-events-none absolute inset-0 h-full w-full bg-gradient-to-b from-neutral-100 to-transparent opacity-0 transition duration-200 group-hover/feature:opacity-100 dark:from-neutral-800' />
|
||||
)}
|
||||
<div className='relative z-10 mb-4 px-10 text-neutral-600 dark:text-neutral-400'>{icon}</div>
|
||||
<div className='relative z-10 mb-2 px-10 font-bold text-lg'>
|
||||
<div className='absolute inset-y-0 left-0 h-6 w-1 origin-center rounded-tr-full rounded-br-full bg-neutral-300 transition-all duration-200 group-hover/feature:h-8 group-hover/feature:bg-purple-500 dark:bg-neutral-700' />
|
||||
<span className='inline-block text-neutral-800 transition duration-200 group-hover/feature:translate-x-2 dark:text-neutral-100'>
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
<p className='relative z-10 max-w-xs px-10 text-neutral-600 text-sm dark:text-neutral-300'>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { type ComponentPropsWithoutRef, useState } from 'react'
|
||||
import { Check, Link } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
||||
|
||||
interface HeadingProps extends ComponentPropsWithoutRef<'h1'> {
|
||||
as?: HeadingTag
|
||||
}
|
||||
|
||||
export function Heading({ as, className, ...props }: HeadingProps) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
const As = as ?? 'h1'
|
||||
|
||||
if (!props.id) {
|
||||
return <As className={className} {...props} />
|
||||
}
|
||||
|
||||
const handleClick = async (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
const url = `${window.location.origin}${window.location.pathname}#${props.id}`
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(url)
|
||||
setCopied(true)
|
||||
|
||||
// Update URL hash without scrolling
|
||||
window.history.pushState(null, '', `#${props.id}`)
|
||||
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
} catch {
|
||||
// Fallback: just navigate to the anchor
|
||||
window.location.hash = props.id as string
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<As className={cn('group flex scroll-m-28 flex-row items-center gap-2', className)} {...props}>
|
||||
<a data-card='' href={`#${props.id}`} className='peer' onClick={handleClick}>
|
||||
{props.children}
|
||||
</a>
|
||||
{copied ? (
|
||||
<Check
|
||||
aria-hidden
|
||||
className='size-3.5 shrink-0 text-green-500 opacity-100 transition-opacity'
|
||||
/>
|
||||
) : (
|
||||
<Link
|
||||
aria-hidden
|
||||
className='size-3.5 shrink-0 text-fd-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 peer-hover:opacity-100'
|
||||
/>
|
||||
)}
|
||||
</As>
|
||||
)
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
// Auto-generated file - do not edit manually
|
||||
// Generated by scripts/generate-docs.ts
|
||||
// Maps block types to their icon component references
|
||||
|
||||
import type { ComponentType, SVGProps } from 'react'
|
||||
import {
|
||||
AhrefsIcon,
|
||||
AirtableIcon,
|
||||
ApifyIcon,
|
||||
ApolloIcon,
|
||||
ArxivIcon,
|
||||
AsanaIcon,
|
||||
BrainIcon,
|
||||
BrowserUseIcon,
|
||||
CalendlyIcon,
|
||||
CirclebackIcon,
|
||||
ClayIcon,
|
||||
ConfluenceIcon,
|
||||
CursorIcon,
|
||||
DatadogIcon,
|
||||
DiscordIcon,
|
||||
DocumentIcon,
|
||||
DropboxIcon,
|
||||
DuckDuckGoIcon,
|
||||
DynamoDBIcon,
|
||||
ElasticsearchIcon,
|
||||
ElevenLabsIcon,
|
||||
ExaAIIcon,
|
||||
EyeIcon,
|
||||
FirecrawlIcon,
|
||||
GithubIcon,
|
||||
GitLabIcon,
|
||||
GmailIcon,
|
||||
GoogleCalendarIcon,
|
||||
GoogleDocsIcon,
|
||||
GoogleDriveIcon,
|
||||
GoogleFormsIcon,
|
||||
GoogleGroupsIcon,
|
||||
GoogleIcon,
|
||||
GoogleSheetsIcon,
|
||||
GoogleSlidesIcon,
|
||||
GoogleVaultIcon,
|
||||
GrafanaIcon,
|
||||
GrainIcon,
|
||||
GreptileIcon,
|
||||
HubspotIcon,
|
||||
HuggingFaceIcon,
|
||||
HunterIOIcon,
|
||||
ImageIcon,
|
||||
IncidentioIcon,
|
||||
IntercomIcon,
|
||||
JinaAIIcon,
|
||||
JiraIcon,
|
||||
JiraServiceManagementIcon,
|
||||
KalshiIcon,
|
||||
LinearIcon,
|
||||
LinkedInIcon,
|
||||
LinkupIcon,
|
||||
MailchimpIcon,
|
||||
MailgunIcon,
|
||||
Mem0Icon,
|
||||
MicrosoftExcelIcon,
|
||||
MicrosoftOneDriveIcon,
|
||||
MicrosoftPlannerIcon,
|
||||
MicrosoftSharepointIcon,
|
||||
MicrosoftTeamsIcon,
|
||||
MistralIcon,
|
||||
MongoDBIcon,
|
||||
MySQLIcon,
|
||||
Neo4jIcon,
|
||||
NotionIcon,
|
||||
OpenAIIcon,
|
||||
OutlookIcon,
|
||||
PackageSearchIcon,
|
||||
ParallelIcon,
|
||||
PerplexityIcon,
|
||||
PineconeIcon,
|
||||
PipedriveIcon,
|
||||
PolymarketIcon,
|
||||
PostgresIcon,
|
||||
PosthogIcon,
|
||||
QdrantIcon,
|
||||
RDSIcon,
|
||||
RedditIcon,
|
||||
ResendIcon,
|
||||
S3Icon,
|
||||
SalesforceIcon,
|
||||
SearchIcon,
|
||||
SendgridIcon,
|
||||
SentryIcon,
|
||||
SerperIcon,
|
||||
ServiceNowIcon,
|
||||
SftpIcon,
|
||||
ShopifyIcon,
|
||||
SlackIcon,
|
||||
SmtpIcon,
|
||||
SpotifyIcon,
|
||||
SQSIcon,
|
||||
SshIcon,
|
||||
STTIcon,
|
||||
StagehandIcon,
|
||||
StripeIcon,
|
||||
SupabaseIcon,
|
||||
TavilyIcon,
|
||||
TelegramIcon,
|
||||
TranslateIcon,
|
||||
TrelloIcon,
|
||||
TTSIcon,
|
||||
TwilioIcon,
|
||||
TypeformIcon,
|
||||
VideoIcon,
|
||||
WealthboxIcon,
|
||||
WebflowIcon,
|
||||
WhatsAppIcon,
|
||||
WikipediaIcon,
|
||||
WordpressIcon,
|
||||
xIcon,
|
||||
YouTubeIcon,
|
||||
ZendeskIcon,
|
||||
ZepIcon,
|
||||
ZoomIcon,
|
||||
} from '@/components/icons'
|
||||
|
||||
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
|
||||
|
||||
export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
ahrefs: AhrefsIcon,
|
||||
airtable: AirtableIcon,
|
||||
apify: ApifyIcon,
|
||||
apollo: ApolloIcon,
|
||||
arxiv: ArxivIcon,
|
||||
asana: AsanaIcon,
|
||||
browser_use: BrowserUseIcon,
|
||||
calendly: CalendlyIcon,
|
||||
circleback: CirclebackIcon,
|
||||
clay: ClayIcon,
|
||||
confluence: ConfluenceIcon,
|
||||
cursor: CursorIcon,
|
||||
datadog: DatadogIcon,
|
||||
discord: DiscordIcon,
|
||||
dropbox: DropboxIcon,
|
||||
duckduckgo: DuckDuckGoIcon,
|
||||
dynamodb: DynamoDBIcon,
|
||||
elasticsearch: ElasticsearchIcon,
|
||||
elevenlabs: ElevenLabsIcon,
|
||||
exa: ExaAIIcon,
|
||||
file: DocumentIcon,
|
||||
firecrawl: FirecrawlIcon,
|
||||
github: GithubIcon,
|
||||
gitlab: GitLabIcon,
|
||||
gmail: GmailIcon,
|
||||
google_calendar: GoogleCalendarIcon,
|
||||
google_docs: GoogleDocsIcon,
|
||||
google_drive: GoogleDriveIcon,
|
||||
google_forms: GoogleFormsIcon,
|
||||
google_groups: GoogleGroupsIcon,
|
||||
google_search: GoogleIcon,
|
||||
google_sheets: GoogleSheetsIcon,
|
||||
google_slides: GoogleSlidesIcon,
|
||||
google_vault: GoogleVaultIcon,
|
||||
grafana: GrafanaIcon,
|
||||
grain: GrainIcon,
|
||||
greptile: GreptileIcon,
|
||||
hubspot: HubspotIcon,
|
||||
huggingface: HuggingFaceIcon,
|
||||
hunter: HunterIOIcon,
|
||||
image_generator: ImageIcon,
|
||||
incidentio: IncidentioIcon,
|
||||
intercom: IntercomIcon,
|
||||
jina: JinaAIIcon,
|
||||
jira: JiraIcon,
|
||||
jira_service_management: JiraServiceManagementIcon,
|
||||
kalshi: KalshiIcon,
|
||||
knowledge: PackageSearchIcon,
|
||||
linear: LinearIcon,
|
||||
linkedin: LinkedInIcon,
|
||||
linkup: LinkupIcon,
|
||||
mailchimp: MailchimpIcon,
|
||||
mailgun: MailgunIcon,
|
||||
mem0: Mem0Icon,
|
||||
memory: BrainIcon,
|
||||
microsoft_excel: MicrosoftExcelIcon,
|
||||
microsoft_planner: MicrosoftPlannerIcon,
|
||||
microsoft_teams: MicrosoftTeamsIcon,
|
||||
mistral_parse: MistralIcon,
|
||||
mongodb: MongoDBIcon,
|
||||
mysql: MySQLIcon,
|
||||
neo4j: Neo4jIcon,
|
||||
notion: NotionIcon,
|
||||
onedrive: MicrosoftOneDriveIcon,
|
||||
openai: OpenAIIcon,
|
||||
outlook: OutlookIcon,
|
||||
parallel_ai: ParallelIcon,
|
||||
perplexity: PerplexityIcon,
|
||||
pinecone: PineconeIcon,
|
||||
pipedrive: PipedriveIcon,
|
||||
polymarket: PolymarketIcon,
|
||||
postgresql: PostgresIcon,
|
||||
posthog: PosthogIcon,
|
||||
qdrant: QdrantIcon,
|
||||
rds: RDSIcon,
|
||||
reddit: RedditIcon,
|
||||
resend: ResendIcon,
|
||||
s3: S3Icon,
|
||||
salesforce: SalesforceIcon,
|
||||
search: SearchIcon,
|
||||
sendgrid: SendgridIcon,
|
||||
sentry: SentryIcon,
|
||||
serper: SerperIcon,
|
||||
servicenow: ServiceNowIcon,
|
||||
sftp: SftpIcon,
|
||||
sharepoint: MicrosoftSharepointIcon,
|
||||
shopify: ShopifyIcon,
|
||||
slack: SlackIcon,
|
||||
smtp: SmtpIcon,
|
||||
spotify: SpotifyIcon,
|
||||
sqs: SQSIcon,
|
||||
ssh: SshIcon,
|
||||
stagehand: StagehandIcon,
|
||||
stripe: StripeIcon,
|
||||
stt: STTIcon,
|
||||
supabase: SupabaseIcon,
|
||||
tavily: TavilyIcon,
|
||||
telegram: TelegramIcon,
|
||||
thinking: BrainIcon,
|
||||
translate: TranslateIcon,
|
||||
trello: TrelloIcon,
|
||||
tts: TTSIcon,
|
||||
twilio_sms: TwilioIcon,
|
||||
twilio_voice: TwilioIcon,
|
||||
typeform: TypeformIcon,
|
||||
video_generator: VideoIcon,
|
||||
vision: EyeIcon,
|
||||
wealthbox: WealthboxIcon,
|
||||
webflow: WebflowIcon,
|
||||
whatsapp: WhatsAppIcon,
|
||||
wikipedia: WikipediaIcon,
|
||||
wordpress: WordpressIcon,
|
||||
x: xIcon,
|
||||
youtube: YouTubeIcon,
|
||||
zendesk: ZendeskIcon,
|
||||
zep: ZepIcon,
|
||||
zoom: ZoomIcon,
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import NextImage, { type ImageProps as NextImageProps } from 'next/image'
|
||||
import { Lightbox } from '@/components/ui/lightbox'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface ImageProps extends Omit<NextImageProps, 'className'> {
|
||||
className?: string
|
||||
enableLightbox?: boolean
|
||||
}
|
||||
|
||||
export function Image({
|
||||
className = 'w-full',
|
||||
enableLightbox = true,
|
||||
alt = '',
|
||||
src,
|
||||
...props
|
||||
}: ImageProps) {
|
||||
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
|
||||
|
||||
const handleImageClick = () => {
|
||||
if (enableLightbox) {
|
||||
setIsLightboxOpen(true)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextImage
|
||||
className={cn(
|
||||
'overflow-hidden rounded-xl border border-border object-cover shadow-sm',
|
||||
enableLightbox && 'cursor-pointer transition-opacity hover:opacity-90',
|
||||
className
|
||||
)}
|
||||
alt={alt}
|
||||
src={src}
|
||||
onClick={handleImageClick}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
{enableLightbox && (
|
||||
<Lightbox
|
||||
isOpen={isLightboxOpen}
|
||||
onClose={() => setIsLightboxOpen(false)}
|
||||
src={typeof src === 'string' ? src : String(src)}
|
||||
alt={alt}
|
||||
type='image'
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Check, ChevronRight } from 'lucide-react'
|
||||
import { useParams, usePathname, useRouter } from 'next/navigation'
|
||||
|
||||
const languages = {
|
||||
en: { name: 'English', flag: '🇺🇸' },
|
||||
es: { name: 'Español', flag: '🇪🇸' },
|
||||
fr: { name: 'Français', flag: '🇫🇷' },
|
||||
de: { name: 'Deutsch', flag: '🇩🇪' },
|
||||
ja: { name: '日本語', flag: '🇯🇵' },
|
||||
zh: { name: '简体中文', flag: '🇨🇳' },
|
||||
}
|
||||
|
||||
export function LanguageDropdown() {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const pathname = usePathname()
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
|
||||
const [currentLang, setCurrentLang] = useState(() => {
|
||||
const langFromParams = params?.lang as string
|
||||
return langFromParams && Object.keys(languages).includes(langFromParams) ? langFromParams : 'en'
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const langFromParams = params?.lang as string
|
||||
|
||||
if (langFromParams && Object.keys(languages).includes(langFromParams)) {
|
||||
if (langFromParams !== currentLang) {
|
||||
setCurrentLang(langFromParams)
|
||||
}
|
||||
} else {
|
||||
if (currentLang !== 'en') {
|
||||
setCurrentLang('en')
|
||||
}
|
||||
}
|
||||
}, [params, currentLang])
|
||||
|
||||
const handleLanguageChange = (locale: string) => {
|
||||
if (locale === currentLang) {
|
||||
setIsOpen(false)
|
||||
return
|
||||
}
|
||||
|
||||
setIsOpen(false)
|
||||
|
||||
const segments = pathname.split('/').filter(Boolean)
|
||||
|
||||
if (segments[0] && Object.keys(languages).includes(segments[0])) {
|
||||
segments.shift()
|
||||
}
|
||||
|
||||
let newPath = ''
|
||||
if (locale === 'en') {
|
||||
newPath = segments.length > 0 ? `/${segments.join('/')}` : '/introduction'
|
||||
} else {
|
||||
newPath = `/${locale}${segments.length > 0 ? `/${segments.join('/')}` : '/introduction'}`
|
||||
}
|
||||
|
||||
router.push(newPath)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) return
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') setIsOpen(false)
|
||||
}
|
||||
window.addEventListener('keydown', onKey)
|
||||
return () => window.removeEventListener('keydown', onKey)
|
||||
}, [isOpen])
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setIsOpen(!isOpen)
|
||||
}}
|
||||
aria-haspopup='listbox'
|
||||
aria-expanded={isOpen}
|
||||
aria-controls='language-menu'
|
||||
className='flex cursor-pointer items-center gap-1.5 rounded-xl px-3 py-2 font-normal text-[0.9375rem] text-foreground/60 leading-[1.4] transition-colors hover:bg-foreground/8 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring'
|
||||
style={{
|
||||
fontFamily:
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||
}}
|
||||
>
|
||||
<span>{languages[currentLang as keyof typeof languages]?.name}</span>
|
||||
<ChevronRight className='h-3.5 w-3.5' />
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<>
|
||||
<div className='fixed inset-0 z-[1000]' aria-hidden onClick={() => setIsOpen(false)} />
|
||||
<div
|
||||
id='language-menu'
|
||||
role='listbox'
|
||||
className='absolute top-full right-0 z-[1001] mt-1 max-h-[75vh] w-56 overflow-auto rounded-xl border border-border/50 bg-white shadow-2xl md:w-44 md:bg-background/95 md:backdrop-blur-md dark:bg-neutral-950 md:dark:bg-background/95'
|
||||
>
|
||||
{Object.entries(languages).map(([code, lang]) => (
|
||||
<button
|
||||
key={code}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleLanguageChange(code)
|
||||
}}
|
||||
role='option'
|
||||
aria-selected={currentLang === code}
|
||||
className={`flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-base transition-colors first:rounded-t-xl last:rounded-b-xl hover:bg-muted/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring md:gap-2 md:px-2.5 md:py-2 md:text-sm ${
|
||||
currentLang === code ? 'bg-muted/60 font-medium text-primary' : 'text-foreground'
|
||||
}`}
|
||||
>
|
||||
<span className='text-base md:text-sm'>{lang.flag}</span>
|
||||
<span className='leading-none'>{lang.name}</span>
|
||||
{currentLang === code && (
|
||||
<Check className='ml-auto h-4 w-4 text-primary md:h-3.5 md:w-3.5' />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
|
||||
interface LightboxProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
src: string
|
||||
alt: string
|
||||
type: 'image' | 'video'
|
||||
}
|
||||
|
||||
export function Lightbox({ isOpen, onClose, src, alt, type }: LightboxProps) {
|
||||
const overlayRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (overlayRef.current && event.target === overlayRef.current) {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
if (isOpen) {
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
document.body.style.overflow = 'hidden'
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
document.body.style.overflow = 'unset'
|
||||
}
|
||||
}, [isOpen, onClose])
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={overlayRef}
|
||||
className='fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-12 backdrop-blur-sm'
|
||||
role='dialog'
|
||||
aria-modal='true'
|
||||
aria-label='Media viewer'
|
||||
>
|
||||
<div className='relative max-h-full max-w-full overflow-hidden rounded-xl shadow-2xl'>
|
||||
{type === 'image' ? (
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
className='max-h-[calc(100vh-6rem)] max-w-[calc(100vw-6rem)] rounded-xl object-contain'
|
||||
loading='lazy'
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
src={getAssetUrl(src)}
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className='max-h-[calc(100vh-6rem)] max-w-[calc(100vw-6rem)] rounded-xl outline-none focus:outline-none'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { Search } from 'lucide-react'
|
||||
|
||||
export function SearchTrigger() {
|
||||
const handleClick = () => {
|
||||
const event = new KeyboardEvent('keydown', {
|
||||
key: 'k',
|
||||
metaKey: true,
|
||||
bubbles: true,
|
||||
})
|
||||
document.dispatchEvent(event)
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
className='flex h-10 w-[460px] cursor-pointer items-center gap-2 rounded-xl border border-border/50 px-3 py-2 text-sm backdrop-blur-xl transition-colors hover:border-border'
|
||||
style={{
|
||||
backgroundColor: 'hsla(0, 0%, 5%, 0.85)',
|
||||
backdropFilter: 'blur(33px) saturate(180%)',
|
||||
WebkitBackdropFilter: 'blur(33px) saturate(180%)',
|
||||
color: 'rgba(255, 255, 255, 0.6)',
|
||||
}}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Search className='h-4 w-4' />
|
||||
<span>Search...</span>
|
||||
<kbd
|
||||
className='ml-auto flex items-center gap-0.5 font-medium'
|
||||
style={{ color: 'rgba(255, 255, 255, 0.6)' }}
|
||||
>
|
||||
<span style={{ fontSize: '15px', lineHeight: '1' }}>⌘</span>
|
||||
<span style={{ fontSize: '13px', lineHeight: '1' }}>K</span>
|
||||
</kbd>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
48
apps/docs/components/ui/theme-image.tsx
Normal file
48
apps/docs/components/ui/theme-image.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { useTheme } from 'next-themes'
|
||||
|
||||
interface ThemeImageProps {
|
||||
lightSrc: string
|
||||
darkSrc: string
|
||||
alt: string
|
||||
width?: number
|
||||
height?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function ThemeImage({
|
||||
lightSrc,
|
||||
darkSrc,
|
||||
alt,
|
||||
width = 600,
|
||||
height = 400,
|
||||
className = 'rounded-lg border border-border my-6',
|
||||
}: ThemeImageProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const [imageSrc, setImageSrc] = useState(lightSrc)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
// Wait until component is mounted to avoid hydration mismatch
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (mounted) {
|
||||
setImageSrc(resolvedTheme === 'dark' ? darkSrc : lightSrc)
|
||||
}
|
||||
}, [resolvedTheme, lightSrc, darkSrc, mounted])
|
||||
|
||||
if (!mounted) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex justify-center'>
|
||||
<Image src={imageSrc} alt={alt} width={width} height={height} className={className} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Moon, Sun } from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { theme, setTheme } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<button className='flex cursor-pointer items-center justify-center rounded-md p-1 text-muted-foreground'>
|
||||
<Moon className='h-4 w-4' />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
||||
className='flex cursor-pointer items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:text-foreground'
|
||||
aria-label='Toggle theme'
|
||||
>
|
||||
{theme === 'dark' ? <Moon className='h-4 w-4' /> : <Sun className='h-4 w-4' />}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { getAssetUrl } from '@/lib/utils'
|
||||
import { Lightbox } from './lightbox'
|
||||
|
||||
interface VideoProps {
|
||||
src: string
|
||||
className?: string
|
||||
autoPlay?: boolean
|
||||
loop?: boolean
|
||||
muted?: boolean
|
||||
playsInline?: boolean
|
||||
enableLightbox?: boolean
|
||||
}
|
||||
|
||||
export function Video({
|
||||
src,
|
||||
className = 'w-full rounded-xl border border-border shadow-sm overflow-hidden outline-none focus:outline-none',
|
||||
autoPlay = true,
|
||||
loop = true,
|
||||
muted = true,
|
||||
playsInline = true,
|
||||
enableLightbox = true,
|
||||
}: VideoProps) {
|
||||
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
|
||||
|
||||
const handleVideoClick = () => {
|
||||
if (enableLightbox) {
|
||||
setIsLightboxOpen(true)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<video
|
||||
autoPlay={autoPlay}
|
||||
loop={loop}
|
||||
muted={muted}
|
||||
playsInline={playsInline}
|
||||
className={`${className} ${enableLightbox ? 'cursor-pointer transition-opacity hover:opacity-90' : ''}`}
|
||||
src={getAssetUrl(src)}
|
||||
onClick={handleVideoClick}
|
||||
/>
|
||||
|
||||
{enableLightbox && (
|
||||
<Lightbox
|
||||
isOpen={isLightboxOpen}
|
||||
onClose={() => setIsLightboxOpen(false)}
|
||||
src={src}
|
||||
alt={`Video: ${src}`}
|
||||
type='video'
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
212
apps/docs/content/docs/blocks/agent.mdx
Normal file
212
apps/docs/content/docs/blocks/agent.mdx
Normal file
@@ -0,0 +1,212 @@
|
||||
---
|
||||
title: Agent
|
||||
description: Create powerful AI agents using any LLM provider
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
|
||||
The Agent block is a fundamental component in Sim Studio that allows you to create powerful AI agents using various LLM providers. These agents can process inputs based on customizable system prompts and utilize integrated tools to enhance their capabilities.
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/agent-light.png"
|
||||
darkSrc="/static/dark/agent-dark.png"
|
||||
alt="Agent Block"
|
||||
width={300}
|
||||
height={175}
|
||||
/>
|
||||
|
||||
<Callout type="info">
|
||||
Agent blocks serve as interfaces to Large Language Models, enabling your workflow to leverage
|
||||
state-of-the-art AI capabilities.
|
||||
</Callout>
|
||||
|
||||
## Overview
|
||||
|
||||
The Agent block serves as an interface to Large Language Models (LLMs), enabling you to create agents that can:
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
<strong>Respond to user inputs</strong>: Generate natural language responses based on provided
|
||||
inputs
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Follow instructions</strong>: Adhere to specific instructions defined in the system
|
||||
prompt
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Use specialized tools</strong>: Interact with integrated tools to extend capabilities
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Structure output</strong>: Generate responses in structured formats when needed
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### System Prompt
|
||||
|
||||
The system prompt defines the agent's behavior, capabilities, and limitations. It's the primary way to instruct the agent on how to respond to inputs.
|
||||
|
||||
```markdown
|
||||
You are a helpful assistant that specializes in financial analysis.
|
||||
Always provide clear explanations and cite sources when possible.
|
||||
When responding to questions about investments, include risk disclaimers.
|
||||
```
|
||||
|
||||
### User Prompt
|
||||
|
||||
The user prompt or context is the specific input or question that the agent should respond to. This can be:
|
||||
|
||||
- Directly provided in the block configuration
|
||||
- Connected from another block's output
|
||||
- Dynamically generated during workflow execution
|
||||
|
||||
### Model Selection
|
||||
|
||||
Choose from a variety of LLM providers:
|
||||
|
||||
- OpenAI (GPT-4o, o1, o3, o4-mini, gpt-4.1)
|
||||
- Anthropic (Claude 3.7 Sonnet)
|
||||
- Google (Gemini 2.5 Pro, Gemini 2.0 Flash)
|
||||
- Groq, Cerebras
|
||||
- Ollama Local Models
|
||||
- And more
|
||||
|
||||
### Temperature
|
||||
|
||||
Control the creativity and randomness of responses:
|
||||
|
||||
<Tabs items={['Low (0-0.3)', 'Medium (0.3-0.7)', 'High (0.7-2.0)']}>
|
||||
<Tab>
|
||||
<p>
|
||||
More deterministic, focused responses. Best for factual tasks, customer support, and
|
||||
situations where accuracy is critical.
|
||||
</p>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<p>
|
||||
Balanced creativity and focus. Suitable for general purpose applications that require both
|
||||
accuracy and some creativity.
|
||||
</p>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<p>
|
||||
More creative, varied responses. Ideal for creative writing, brainstorming, and generating
|
||||
diverse ideas.
|
||||
</p>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<p className="mt-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
The temperature range (0-1 or 0-2) varies depending on the selected model.
|
||||
</p>
|
||||
|
||||
### API Key
|
||||
|
||||
Your API key for the selected LLM provider. This is securely stored and used for authentication.
|
||||
|
||||
### Tools
|
||||
|
||||
Integrate specialized tools to enhance the agent's capabilities. You can add tools to your agent by:
|
||||
|
||||
1. Clicking the Tools section in the Agent configuration
|
||||
2. Selecting from the tools dropdown menu
|
||||
3. Choosing an existing tool or creating a new one
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/tooldropdown-light.png"
|
||||
darkSrc="/static/dark/tooldropdown-dark.png"
|
||||
alt="Tools Dropdown"
|
||||
width={150}
|
||||
height={125}
|
||||
/>
|
||||
|
||||
Available tools include:
|
||||
|
||||
- **Confluence**: Access and query Confluence knowledge bases
|
||||
- **Evaluator**: Use evaluation metrics to assess content
|
||||
- **GitHub**: Interact with GitHub repositories and issues
|
||||
- **Gmail**: Process and respond to emails
|
||||
- **Firecrawl**: Web search and content retrieval
|
||||
- And many, many more pre-built integrations
|
||||
|
||||
You can also create custom tools to meet specific requirements for your agent's capabilities.
|
||||
|
||||
<Callout type="info">
|
||||
Tools significantly expand what your agent can do, allowing it to access external systems,
|
||||
retrieve information, and take actions beyond simple text generation.
|
||||
</Callout>
|
||||
|
||||
### Response Format
|
||||
|
||||
Define a structured format for the agent's response when needed, using JSON or other formats.
|
||||
|
||||
## Inputs and Outputs
|
||||
|
||||
<Tabs items={['Inputs', 'Outputs']}>
|
||||
<Tab>
|
||||
<ul className="list-disc space-y-2 pl-6">
|
||||
<li>
|
||||
<strong>User Prompt</strong>: The user's query or context for the agent
|
||||
</li>
|
||||
<li>
|
||||
<strong>System Prompt</strong>: Instructions for the agent (optional)
|
||||
</li>
|
||||
<li>
|
||||
<strong>Tools</strong>: Optional tool connections that the agent can use
|
||||
</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<ul className="list-disc space-y-2 pl-6">
|
||||
<li>
|
||||
<strong>Content</strong>: The agent's response text
|
||||
</li>
|
||||
<li>
|
||||
<strong>Model</strong>: The model used for generation
|
||||
</li>
|
||||
<li>
|
||||
<strong>Tokens</strong>: Usage statistics (prompt, completion, total)
|
||||
</li>
|
||||
<li>
|
||||
<strong>Tool Calls</strong>: Details of any tools used during processing
|
||||
</li>
|
||||
<li>
|
||||
<strong>Cost</strong>: Cost of the response
|
||||
</li>
|
||||
<li>
|
||||
<strong>Usage</strong>: Usage statistics (prompt, completion, total)
|
||||
</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here's an example of how an Agent block might be configured for a customer support workflow:
|
||||
|
||||
```yaml
|
||||
# Example Agent Configuration
|
||||
systemPrompt: |
|
||||
You are a customer support agent for TechCorp.
|
||||
Always maintain a professional, friendly tone.
|
||||
If you don't know an answer, direct the customer to email support@techcorp.com.
|
||||
Never make up information about products or policies.
|
||||
|
||||
model: OpenAI/gpt-4
|
||||
temperature: 0.2
|
||||
tools:
|
||||
- ProductDatabase
|
||||
- OrderHistory
|
||||
- SupportTicketCreator
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Be specific in system prompts**: Clearly define the agent's role, tone, and limitations. The more specific your instructions are, the better the agent will be able to fulfill its intended purpose.
|
||||
- **Choose the right temperature setting**: Use lower temperature settings (0-0.3) when accuracy is important, or increase temperature (0.7-2.0) for more creative or varied responses
|
||||
- **Combine with Evaluator blocks**: Use Evaluator blocks to assess agent responses and ensure quality. This allows you to create feedback loops and implement quality control measures.
|
||||
- **Leverage tools effectively**: Integrate tools that complement the agent's purpose and enhance its capabilities. Be selective about which tools you provide to avoid overwhelming the agent.
|
||||
128
apps/docs/content/docs/blocks/api.mdx
Normal file
128
apps/docs/content/docs/blocks/api.mdx
Normal file
@@ -0,0 +1,128 @@
|
||||
---
|
||||
title: API
|
||||
description: Connect to external services through API endpoints
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
|
||||
The API block enables you to connect your workflow to external services through HTTP requests. It supports various methods like GET, POST, PUT, DELETE, and PATCH, allowing you to interact with virtually any API endpoint.
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/api-light.png"
|
||||
darkSrc="/static/dark/api-dark.png"
|
||||
alt="API Block"
|
||||
width={300}
|
||||
height={175}
|
||||
/>
|
||||
|
||||
## Overview
|
||||
|
||||
The API block enables you to:
|
||||
|
||||
- Make HTTP requests to external services and APIs
|
||||
- Process and transform data from external sources
|
||||
- Send data to external systems
|
||||
- Integrate with third-party platforms and services
|
||||
- Create webhooks and callbacks
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### URL
|
||||
|
||||
The endpoint URL for the API request. This can be:
|
||||
|
||||
- A static URL entered directly in the block
|
||||
- A dynamic URL connected from another block's output
|
||||
- A URL with path parameters
|
||||
|
||||
### Method
|
||||
|
||||
Select the HTTP method for your request:
|
||||
|
||||
- **GET**: Retrieve data from the server
|
||||
- **POST**: Send data to the server to create a resource
|
||||
- **PUT**: Update an existing resource on the server
|
||||
- **DELETE**: Remove a resource from the server
|
||||
- **PATCH**: Partially update an existing resource
|
||||
|
||||
### Query Parameters
|
||||
|
||||
Define key-value pairs that will be appended to the URL as query parameters. For example:
|
||||
|
||||
```
|
||||
Key: apiKey
|
||||
Value: your_api_key_here
|
||||
|
||||
Key: limit
|
||||
Value: 10
|
||||
```
|
||||
|
||||
These would be added to the URL as `?apiKey=your_api_key_here&limit=10`.
|
||||
|
||||
### Headers
|
||||
|
||||
Configure HTTP headers for your request. Common headers include:
|
||||
|
||||
```
|
||||
Key: Content-Type
|
||||
Value: application/json
|
||||
|
||||
Key: Authorization
|
||||
Value: Bearer your_token_here
|
||||
```
|
||||
|
||||
### Request Body
|
||||
|
||||
For methods that support a request body (POST, PUT, PATCH), you can define the data to send. The body can be:
|
||||
|
||||
- JSON data entered directly in the block
|
||||
- Data connected from another block's output
|
||||
- Dynamically generated during workflow execution
|
||||
|
||||
## Inputs and Outputs
|
||||
|
||||
### Inputs
|
||||
|
||||
- **URL**: The endpoint to send the request to
|
||||
- **Method**: The HTTP method to use
|
||||
- **Query Parameters**: Key-value pairs for URL parameters
|
||||
- **Headers**: HTTP headers for the request
|
||||
- **Body**: Data to send with the request (for applicable methods)
|
||||
|
||||
### Outputs
|
||||
|
||||
- **Status Code**: The HTTP status code returned by the server
|
||||
- **Response Body**: The data returned by the server
|
||||
- **Headers**: Response headers from the server
|
||||
- **Error**: Any error information if the request fails
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here's an example of how an API block might be configured to fetch weather data:
|
||||
|
||||
```yaml
|
||||
# Example API Configuration
|
||||
url: https://api.weatherapi.com/v1/current.json
|
||||
method: GET
|
||||
params:
|
||||
- key: key
|
||||
value: your_api_key_here
|
||||
- key: q
|
||||
value: London
|
||||
- key: aqi
|
||||
value: no
|
||||
headers:
|
||||
- key: Accept
|
||||
value: application/json
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Use environment variables for sensitive data**: Don't hardcode API keys or credentials
|
||||
- **Handle errors gracefully**: Connect error handling logic for failed requests
|
||||
- **Validate responses**: Check status codes and response formats before processing data
|
||||
- **Respect rate limits**: Be mindful of API rate limits and implement appropriate throttling
|
||||
- **Cache responses when appropriate**: For frequently accessed data that doesn't change often
|
||||
191
apps/docs/content/docs/blocks/condition.mdx
Normal file
191
apps/docs/content/docs/blocks/condition.mdx
Normal file
@@ -0,0 +1,191 @@
|
||||
---
|
||||
title: Condition
|
||||
description: Create conditional logic and branching in your workflows
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { File, Files, Folder } from 'fumadocs-ui/components/files'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
|
||||
The Condition block allows you to branch your workflow execution path based on boolean expressions. It evaluates conditions and routes the workflow accordingly, enabling you to create dynamic, responsive workflows with different execution paths.
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/condition-light.png"
|
||||
darkSrc="/static/dark/condition-dark.png"
|
||||
alt="Condition Block"
|
||||
width={300}
|
||||
height={175}
|
||||
/>
|
||||
|
||||
<Callout>
|
||||
Condition blocks enable deterministic decision-making without requiring an LLM, making them ideal
|
||||
for straightforward branching logic.
|
||||
</Callout>
|
||||
|
||||
## Overview
|
||||
|
||||
The Condition block serves as a decision point in your workflow, enabling:
|
||||
|
||||
<div className="my-6 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="rounded-lg border border-gray-200 p-4 dark:border-gray-800">
|
||||
<h3 className="mb-2 text-lg font-medium">Branching Logic</h3>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Create different execution paths based on specific conditions
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-gray-200 p-4 dark:border-gray-800">
|
||||
<h3 className="mb-2 text-lg font-medium">Rule-Based Routing</h3>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Route workflows deterministically without needing an LLM
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-gray-200 p-4 dark:border-gray-800">
|
||||
<h3 className="mb-2 text-lg font-medium">Data-Driven Decisions</h3>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Create workflow paths based on structured data values
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-gray-200 p-4 dark:border-gray-800">
|
||||
<h3 className="mb-2 text-lg font-medium">If-Then-Else Logic</h3>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Implement conditional programming paradigms in your workflows
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## How It Works
|
||||
|
||||
The Condition block:
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
<strong>Evaluate Expression</strong>: Evaluates a boolean expression or condition
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Determine Result</strong>: Determines whether the condition evaluates to true or false
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Route Workflow</strong>: Routes the workflow to the appropriate path based on the result
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Provide Context</strong>: Provides context about the decision made
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Conditions
|
||||
|
||||
Define one or more conditions that will be evaluated. Each condition includes:
|
||||
|
||||
- **Expression**: A JavaScript/TypeScript expression that evaluates to true or false
|
||||
- **Path**: The destination block to route to if the condition is true
|
||||
- **Description**: Optional explanation of what the condition checks
|
||||
|
||||
You can create multiple conditions that are evaluated in order, with the first matching condition determining the execution path.
|
||||
|
||||
### Condition Expression Format
|
||||
|
||||
Conditions use JavaScript syntax and can reference input values from previous blocks.
|
||||
|
||||
<Tabs items={['Score Threshold', 'Text Analysis', 'Multiple Conditions']}>
|
||||
<Tab>
|
||||
```javascript
|
||||
// Check if a score is above a threshold
|
||||
input.score > 75
|
||||
```
|
||||
</Tab>
|
||||
<Tab>
|
||||
```javascript
|
||||
// Check if a text contains specific keywords
|
||||
input.text.includes('urgent') || input.text.includes('emergency')
|
||||
```
|
||||
</Tab>
|
||||
<Tab>
|
||||
```javascript
|
||||
// Check multiple conditions
|
||||
input.age >= 18 && input.country === 'US'
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Inputs and Outputs
|
||||
|
||||
<Tabs items={['Inputs', 'Outputs']}>
|
||||
<Tab>
|
||||
<ul className="list-disc space-y-2 pl-6">
|
||||
<li>
|
||||
<strong>Variables</strong>: Values from previous blocks that can be referenced in conditions
|
||||
</li>
|
||||
<li>
|
||||
<strong>Conditions</strong>: Boolean expressions to evaluate
|
||||
</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<ul className="list-disc space-y-2 pl-6">
|
||||
<li>
|
||||
<strong>Content</strong>: A description of the evaluation result
|
||||
</li>
|
||||
<li>
|
||||
<strong>Condition Result</strong>: The boolean result of the condition evaluation
|
||||
</li>
|
||||
<li>
|
||||
<strong>Selected Path</strong>: Details of the chosen routing destination
|
||||
</li>
|
||||
<li>
|
||||
<strong>Selected Condition ID</strong>: Identifier of the condition that was matched
|
||||
</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here's an example of how a Condition block might be used in a customer satisfaction workflow:
|
||||
|
||||
```yaml
|
||||
# Example Condition Configuration
|
||||
conditions:
|
||||
- id: 'high_satisfaction'
|
||||
expression: 'input.satisfactionScore >= 8'
|
||||
description: 'Customer is highly satisfied'
|
||||
path: 'positive_feedback_block'
|
||||
|
||||
- id: 'medium_satisfaction'
|
||||
expression: 'input.satisfactionScore >= 5'
|
||||
description: 'Customer is moderately satisfied'
|
||||
path: 'neutral_feedback_block'
|
||||
|
||||
- id: 'default'
|
||||
expression: 'true'
|
||||
description: 'Customer is not satisfied'
|
||||
path: 'improvement_feedback_block'
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Order conditions correctly
|
||||
|
||||
Conditions are evaluated in order, so place more specific conditions before general ones. This ensures that more specific logic takes precedence over general fallbacks.
|
||||
|
||||
### Include a default condition
|
||||
|
||||
Add a catch-all condition (e.g., `true`) as the last condition to handle cases when no other conditions match. This prevents workflow execution from getting stuck.
|
||||
|
||||
### Keep expressions simple
|
||||
|
||||
Use clear, straightforward boolean expressions for better readability. Complex expressions can be difficult to debug and maintain.
|
||||
|
||||
### Document your conditions
|
||||
|
||||
Add descriptions to explain the purpose of each condition. This helps other team members understand the logic and makes maintenance easier.
|
||||
|
||||
### Test edge cases
|
||||
|
||||
Ensure your conditions handle boundary values correctly. Test with values at the edges of your condition ranges to verify correct behavior.
|
||||
128
apps/docs/content/docs/blocks/evaluator.mdx
Normal file
128
apps/docs/content/docs/blocks/evaluator.mdx
Normal file
@@ -0,0 +1,128 @@
|
||||
---
|
||||
title: Evaluator
|
||||
description: Assess content quality using customizable evaluation metrics
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
|
||||
The Evaluator block allows you to assess the quality of content using customizable evaluation metrics. This is particularly useful for evaluating AI-generated text, ensuring outputs meet specific criteria, and building quality-control mechanisms into your workflows.
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/evaluator-light.png"
|
||||
darkSrc="/static/dark/evaluator-dark.png"
|
||||
alt="Evaluator Block"
|
||||
width={300}
|
||||
height={175}
|
||||
/>
|
||||
|
||||
## Overview
|
||||
|
||||
The Evaluator block utilizes LLMs to objectively evaluate content based on custom metrics you define. This is especially useful for:
|
||||
|
||||
- Assessing the quality of AI-generated content
|
||||
- Evaluating responses against specific criteria
|
||||
- Creating scoring frameworks for different types of content
|
||||
- Building objective feedback loops in your workflows
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Evaluation Metrics
|
||||
|
||||
Define custom metrics to evaluate content against. Each metric includes:
|
||||
|
||||
- **Name**: A short identifier for the metric
|
||||
- **Description**: A detailed explanation of what the metric measures
|
||||
- **Range**: The numeric range for scoring (e.g., 1-5, 0-10)
|
||||
|
||||
Example metrics:
|
||||
|
||||
```
|
||||
Accuracy (1-5): How factually accurate is the content?
|
||||
Clarity (1-5): How clear and understandable is the content?
|
||||
Relevance (1-5): How relevant is the content to the original query?
|
||||
```
|
||||
|
||||
### Content
|
||||
|
||||
The content to be evaluated. This can be:
|
||||
|
||||
- Directly provided in the block configuration
|
||||
- Connected from another block's output (typically an Agent block)
|
||||
- Dynamically generated during workflow execution
|
||||
|
||||
### Model Selection
|
||||
|
||||
Choose an LLM provider to perform the evaluation:
|
||||
|
||||
- OpenAI (GPT-4o, o1, o3, , gpt-4.1)
|
||||
- Anthropic (Claude 3.7 Sonnet)
|
||||
- Google (Gemini 2.5 Pro, Gemini 2.0 Flash)
|
||||
- Groq, Cerebras
|
||||
- Ollama Local Models
|
||||
- And more
|
||||
|
||||
The chosen model should have strong reasoning capabilities to provide accurate evaluations.
|
||||
|
||||
### API Key
|
||||
|
||||
Your API key for the selected LLM provider. This is securely stored and used for authentication.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. The Evaluator block takes the provided content and your custom metrics
|
||||
2. It generates a specialized prompt that instructs the LLM to evaluate the content
|
||||
3. The prompt includes clear guidelines on how to score each metric
|
||||
4. The LLM evaluates the content and returns numeric scores for each metric
|
||||
5. The Evaluator block formats these scores as structured output for use in your workflow
|
||||
|
||||
## Inputs and Outputs
|
||||
|
||||
### Inputs
|
||||
|
||||
- **Content**: The text or structured data to evaluate
|
||||
- **Metrics**: Custom evaluation criteria with scoring ranges
|
||||
- **Model Settings**: LLM provider and parameters
|
||||
|
||||
### Outputs
|
||||
|
||||
- **Content**: A summary of the evaluation
|
||||
- **Model**: The model used for evaluation
|
||||
- **Tokens**: Usage statistics
|
||||
- **Metric Scores**: Numeric scores for each defined metric
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here's an example of how an Evaluator block might be configured for assessing customer service responses:
|
||||
|
||||
```yaml
|
||||
# Example Evaluator Configuration
|
||||
metrics:
|
||||
- name: Empathy
|
||||
description: How well does the response acknowledge and address the customer's emotional state?
|
||||
range:
|
||||
min: 1
|
||||
max: 5
|
||||
- name: Solution
|
||||
description: How effectively does the response solve the customer's problem?
|
||||
range:
|
||||
min: 1
|
||||
max: 5
|
||||
- name: Clarity
|
||||
description: How clear and easy to understand is the response?
|
||||
range:
|
||||
min: 1
|
||||
max: 5
|
||||
|
||||
model: Anthropic/claude-3-opus
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Use specific metric descriptions**: Clearly define what each metric measures to get more accurate evaluations
|
||||
- **Choose appropriate ranges**: Select scoring ranges that provide enough granularity without being overly complex
|
||||
- **Connect with Agent blocks**: Use Evaluator blocks to assess Agent block outputs and create feedback loops
|
||||
- **Use consistent metrics**: For comparative analysis, maintain consistent metrics across similar evaluations
|
||||
- **Combine multiple metrics**: Use several metrics to get a comprehensive evaluation
|
||||
137
apps/docs/content/docs/blocks/function.mdx
Normal file
137
apps/docs/content/docs/blocks/function.mdx
Normal file
@@ -0,0 +1,137 @@
|
||||
---
|
||||
title: Function
|
||||
description: Execute custom JavaScript or TypeScript code in your workflows
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
|
||||
The Function block allows you to write and execute custom JavaScript or TypeScript code directly within your workflow. This powerful feature enables you to implement complex logic, data transformations, and integration with external libraries.
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/function-light.png"
|
||||
darkSrc="/static/dark/function-dark.png"
|
||||
alt="Function Block"
|
||||
width={300}
|
||||
height={175}
|
||||
/>
|
||||
|
||||
## Overview
|
||||
|
||||
The Function block brings the full power of JavaScript/TypeScript to your workflows, allowing for:
|
||||
|
||||
- Custom data transformation and manipulation
|
||||
- Complex conditional logic
|
||||
- Mathematical calculations and algorithms
|
||||
- Integration with external libraries
|
||||
- Creation of reusable utility functions
|
||||
|
||||
## How It Works
|
||||
|
||||
The Function block:
|
||||
|
||||
1. Takes your custom JavaScript/TypeScript code
|
||||
2. Executes it in a secure, isolated environment
|
||||
3. Processes any inputs provided from previous blocks
|
||||
4. Returns the result for use in subsequent blocks
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Code Editor
|
||||
|
||||
A full-featured code editor where you can write your JavaScript/TypeScript code. The editor supports:
|
||||
|
||||
- Syntax highlighting
|
||||
- Code completion
|
||||
- Error checking
|
||||
- Multiple lines of code
|
||||
|
||||
### Inputs
|
||||
|
||||
Your function can access inputs from previous blocks through an `input` object. For example:
|
||||
|
||||
```javascript
|
||||
// Access data from a previous block
|
||||
const customerName = input.customerData.name;
|
||||
const orderTotal = input.orderData.total;
|
||||
|
||||
// Process the data
|
||||
const discount = orderTotal > 100 ? 0.1 : 0;
|
||||
const finalPrice = orderTotal * (1 - discount);
|
||||
|
||||
// Return the result
|
||||
return {
|
||||
customerName,
|
||||
originalTotal: orderTotal,
|
||||
discount: discount * 100 + '%',
|
||||
finalPrice
|
||||
};
|
||||
```
|
||||
|
||||
## Safety and Limitations
|
||||
|
||||
For security and performance reasons, function execution has certain limitations:
|
||||
|
||||
- **Execution Time**: Functions have a maximum execution time to prevent infinite loops
|
||||
- **Memory Usage**: Memory is limited to prevent excessive resource usage
|
||||
- **Network Access**: Network calls are restricted to prevent unauthorized access
|
||||
- **Available APIs**: Only a subset of browser APIs are available
|
||||
|
||||
## Inputs and Outputs
|
||||
|
||||
### Inputs
|
||||
|
||||
- **Code**: Your JavaScript/TypeScript code to execute
|
||||
- **Input Data**: Values from previous blocks that can be accessed in your code
|
||||
|
||||
### Outputs
|
||||
|
||||
- **Result**: The value returned by your function
|
||||
- **Standard Output**: Any console output from your function
|
||||
- **Execution Time**: The time taken to execute your function (in milliseconds)
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here's an example of a Function block that processes customer data and calculates a loyalty score:
|
||||
|
||||
```javascript
|
||||
// Example Function Block Code
|
||||
// Process customer data and calculate loyalty score
|
||||
|
||||
// Access input from previous blocks
|
||||
const { purchaseHistory, accountAge, supportTickets } = input;
|
||||
|
||||
// Calculate metrics
|
||||
const totalSpent = purchaseHistory.reduce((sum, purchase) => sum + purchase.amount, 0);
|
||||
const purchaseFrequency = purchaseHistory.length / (accountAge / 365);
|
||||
const ticketRatio = supportTickets.resolved / supportTickets.total;
|
||||
|
||||
// Calculate loyalty score (0-100)
|
||||
const spendScore = Math.min(totalSpent / 1000 * 30, 30);
|
||||
const frequencyScore = Math.min(purchaseFrequency * 20, 40);
|
||||
const supportScore = ticketRatio * 30;
|
||||
|
||||
const loyaltyScore = Math.round(spendScore + frequencyScore + supportScore);
|
||||
|
||||
// Return results
|
||||
return {
|
||||
customer: input.name,
|
||||
loyaltyScore,
|
||||
loyaltyTier: loyaltyScore >= 80 ? "Platinum" : loyaltyScore >= 60 ? "Gold" : loyaltyScore >= 40 ? "Silver" : "Bronze",
|
||||
metrics: {
|
||||
spendScore,
|
||||
frequencyScore,
|
||||
supportScore
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Keep functions focused**: Write functions that do one thing well
|
||||
- **Handle errors gracefully**: Use try/catch blocks to handle potential errors
|
||||
- **Document your code**: Add comments to explain complex logic
|
||||
- **Test edge cases**: Ensure your code handles unusual inputs correctly
|
||||
- **Optimize for performance**: Be mindful of computational complexity for large datasets
|
||||
54
apps/docs/content/docs/blocks/index.mdx
Normal file
54
apps/docs/content/docs/blocks/index.mdx
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: Blocks
|
||||
description: Building blocks for your agentic workflows
|
||||
---
|
||||
|
||||
import { Card, Cards } from 'fumadocs-ui/components/card'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { BlockTypes } from '@/components/ui/block-types'
|
||||
|
||||
Blocks are the fundamental building components of Sim Studio workflows. Each block has a specific purpose and can be connected to other blocks to create sophisticated workflows.
|
||||
|
||||
## What is a Block?
|
||||
|
||||
A block is a reusable, configurable component that performs a specific function within your workflow. Blocks have inputs and outputs that allow them to communicate with other blocks. They can process data, make decisions, interact with external systems, or perform computations.
|
||||
|
||||
## Primary Block Types
|
||||
|
||||
Sim Studio provides six powerful block types that form the foundation of any workflow. Each block is designed to handle specific aspects of your agentic applications, from AI-powered reasoning to conditional logic and external integrations.
|
||||
|
||||
<BlockTypes />
|
||||
|
||||
## Block Connections
|
||||
|
||||
Blocks can be connected to form a directed graph representing your workflow. Each connection represents the flow of data from one block to another:
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
<strong>Outputs to Inputs</strong>: A block's outputs can be connected to another block's
|
||||
inputs.
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Multiple Connections</strong>: A block can have multiple incoming and outgoing
|
||||
connections.
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Conditional Flows</strong>: Some blocks (like Router and Condition) can have multiple
|
||||
output paths based on conditions.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Block Configuration
|
||||
|
||||
Each block type has its own configuration options allowing you to customize its behavior.
|
||||
|
||||
### Common Configuration Options
|
||||
|
||||
- **Input/output definitions**: Define how data flows in and out of the block
|
||||
- **Processing instructions**: Configure how the block processes its inputs
|
||||
- **API keys or authentication details**: Provide necessary credentials for external services
|
||||
- **Retry policies**: Configure how the block handles failures
|
||||
- **Error handling behavior**: Define how errors are managed and reported
|
||||
|
||||
See the specific documentation for each block type to learn about its configuration options.
|
||||
4
apps/docs/content/docs/blocks/meta.json
Normal file
4
apps/docs/content/docs/blocks/meta.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"title": "Blocks",
|
||||
"pages": ["agent", "api", "condition", "function", "evaluator", "router", "response", "workflow"]
|
||||
}
|
||||
188
apps/docs/content/docs/blocks/response.mdx
Normal file
188
apps/docs/content/docs/blocks/response.mdx
Normal file
@@ -0,0 +1,188 @@
|
||||
---
|
||||
title: Response
|
||||
description: Send a structured response back to API calls
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
|
||||
The Response block is the final component in API-enabled workflows that transforms your workflow's variables into a structured HTTP response. This block serves as the endpoint that returns data, status codes, and headers back to API callers.
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/response-light.png"
|
||||
darkSrc="/static/dark/response-dark.png"
|
||||
alt="Response Block"
|
||||
width={430}
|
||||
height={784}
|
||||
/>
|
||||
|
||||
<Callout type="info">
|
||||
Response blocks are terminal blocks - they mark the end of a workflow execution and cannot have further connections.
|
||||
</Callout>
|
||||
|
||||
## Overview
|
||||
|
||||
The Response block serves as the final output mechanism for API workflows, enabling you to:
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
<strong>Return structured data</strong>: Transform workflow variables into JSON responses
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Set HTTP status codes</strong>: Control the response status (200, 400, 500, etc.)
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Configure headers</strong>: Add custom HTTP headers to the response
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Reference variables</strong>: Use workflow variables dynamically in the response
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Response Data
|
||||
|
||||
The response data is the main content that will be sent back to the API caller. This should be formatted as JSON and can include:
|
||||
|
||||
- Static values
|
||||
- Dynamic references to workflow variables using the `<variable.name>` syntax
|
||||
- Nested objects and arrays
|
||||
- Any valid JSON structure
|
||||
|
||||
### Status Code
|
||||
|
||||
Set the HTTP status code for the response. Common status codes include:
|
||||
|
||||
<Tabs items={['Success (2xx)', 'Client Error (4xx)', 'Server Error (5xx)']}>
|
||||
<Tab>
|
||||
<ul className="list-disc space-y-2 pl-6">
|
||||
<li><strong>200</strong>: OK - Standard success response</li>
|
||||
<li><strong>201</strong>: Created - Resource successfully created</li>
|
||||
<li><strong>204</strong>: No Content - Success with no response body</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<ul className="list-disc space-y-2 pl-6">
|
||||
<li><strong>400</strong>: Bad Request - Invalid request parameters</li>
|
||||
<li><strong>401</strong>: Unauthorized - Authentication required</li>
|
||||
<li><strong>404</strong>: Not Found - Resource doesn't exist</li>
|
||||
<li><strong>422</strong>: Unprocessable Entity - Validation errors</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<ul className="list-disc space-y-2 pl-6">
|
||||
<li><strong>500</strong>: Internal Server Error - Server-side error</li>
|
||||
<li><strong>502</strong>: Bad Gateway - External service error</li>
|
||||
<li><strong>503</strong>: Service Unavailable - Service temporarily down</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<p className="mt-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
Default status code is 200 if not specified.
|
||||
</p>
|
||||
|
||||
### Response Headers
|
||||
|
||||
Configure additional HTTP headers to include in the response.
|
||||
|
||||
Headers are configured as key-value pairs:
|
||||
|
||||
| Key | Value |
|
||||
|-----|-------|
|
||||
| Content-Type | application/json |
|
||||
| Cache-Control | no-cache |
|
||||
| X-API-Version | 1.0 |
|
||||
|
||||
## Inputs and Outputs
|
||||
|
||||
<Tabs items={['Inputs', 'Outputs']}>
|
||||
<Tab>
|
||||
<ul className="list-disc space-y-2 pl-6">
|
||||
<li>
|
||||
<strong>data</strong> (JSON, optional): The JSON data to send in the response body
|
||||
</li>
|
||||
<li>
|
||||
<strong>status</strong> (number, optional): HTTP status code (default: 200)
|
||||
</li>
|
||||
<li>
|
||||
<strong>headers</strong> (JSON, optional): Additional response headers
|
||||
</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<ul className="list-disc space-y-2 pl-6">
|
||||
<li>
|
||||
<strong>response</strong>: Complete response object containing:
|
||||
<ul className="list-disc space-y-1 pl-6 mt-2">
|
||||
<li><strong>data</strong>: The response body data</li>
|
||||
<li><strong>status</strong>: HTTP status code</li>
|
||||
<li><strong>headers</strong>: Response headers</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Variable References
|
||||
|
||||
Use the `<variable.name>` syntax to dynamically insert workflow variables into your response:
|
||||
|
||||
```json
|
||||
{
|
||||
"user": {
|
||||
"id": "<variable.userId>",
|
||||
"name": "<variable.userName>",
|
||||
"email": "<variable.userEmail>"
|
||||
},
|
||||
"query": "<variable.searchQuery>",
|
||||
"results": "<variable.searchResults>",
|
||||
"totalFound": "<variable.resultCount>",
|
||||
"processingTime": "<variable.executionTime>ms"
|
||||
}
|
||||
```
|
||||
|
||||
<Callout type="warning">
|
||||
Variable names are case-sensitive and must match exactly with the variables available in your workflow.
|
||||
</Callout>
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here's an example of how a Response block might be configured for a user search API:
|
||||
|
||||
```yaml
|
||||
data: |
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"users": "<variable.searchResults>",
|
||||
"pagination": {
|
||||
"page": "<variable.currentPage>",
|
||||
"limit": "<variable.pageSize>",
|
||||
"total": "<variable.totalUsers>"
|
||||
}
|
||||
},
|
||||
"query": {
|
||||
"searchTerm": "<variable.searchTerm>",
|
||||
"filters": "<variable.appliedFilters>"
|
||||
},
|
||||
"timestamp": "<variable.timestamp>"
|
||||
}
|
||||
status: 200
|
||||
headers:
|
||||
- key: X-Total-Count
|
||||
value: <variable.totalUsers>
|
||||
- key: Cache-Control
|
||||
value: public, max-age=300
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Use meaningful status codes**: Choose appropriate HTTP status codes that accurately reflect the outcome of the workflow
|
||||
- **Structure your responses consistently**: Maintain a consistent JSON structure across all your API endpoints for better developer experience
|
||||
- **Include relevant metadata**: Add timestamps and version information to help with debugging and monitoring
|
||||
- **Handle errors gracefully**: Use conditional logic in your workflow to set appropriate error responses with descriptive messages
|
||||
- **Validate variable references**: Ensure all referenced variables exist and contain the expected data types before the Response block executes
|
||||
120
apps/docs/content/docs/blocks/router.mdx
Normal file
120
apps/docs/content/docs/blocks/router.mdx
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: Router
|
||||
description: Route workflow execution based on specific conditions or logic
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
|
||||
The Router block is a powerful component in Sim Studio that intelligently routes workflow execution based on content analysis, user input, or predefined conditions. It acts as a decision-making junction in your workflow, directing the flow to different paths based on various criteria.
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/router-light.png"
|
||||
darkSrc="/static/dark/router-dark.png"
|
||||
alt="Router Block"
|
||||
width={300}
|
||||
height={175}
|
||||
/>
|
||||
|
||||
## Overview
|
||||
|
||||
The Router block uses LLMs to analyze input content and determine the most appropriate next step in your workflow. This allows for:
|
||||
|
||||
- Creating dynamic, adaptable workflows
|
||||
- Implementing complex decision trees
|
||||
- Routing user requests to specialized components
|
||||
- Building conversational systems that can handle diverse inputs
|
||||
|
||||
## How It Works
|
||||
|
||||
The Router block:
|
||||
|
||||
1. Analyzes the input content using an LLM
|
||||
2. Evaluates the content against the available target blocks in your workflow
|
||||
3. Identifies the most appropriate destination based on the content's intent or requirements
|
||||
4. Routes the workflow execution to the selected block
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Content/Prompt
|
||||
|
||||
The content or prompt that the Router will analyze to make routing decisions. This can be:
|
||||
|
||||
- A direct user query or input
|
||||
- Output from a previous block
|
||||
- A system-generated message
|
||||
|
||||
### Target Blocks
|
||||
|
||||
The possible destination blocks that the Router can select from. The Router will automatically detect connected blocks, but you can also:
|
||||
|
||||
- Customize the descriptions of target blocks to improve routing accuracy
|
||||
- Specify routing criteria for each target block
|
||||
- Exclude certain blocks from being considered as routing targets
|
||||
|
||||
### Model Selection
|
||||
|
||||
Choose an LLM provider to power the routing decision:
|
||||
|
||||
- OpenAI (GPT-4o, o1, o3, o4-mini)
|
||||
- Anthropic (Claude 3.7 Sonnet)
|
||||
- Google (Gemini 2.5 Pro, Gemini 2.0 Flash)
|
||||
- Groq, Cerebras
|
||||
- Ollama Local Models
|
||||
- And more
|
||||
|
||||
Select a model with strong reasoning capabilities for more accurate routing decisions.
|
||||
|
||||
### API Key
|
||||
|
||||
Your API key for the selected LLM provider. This is securely stored and used for authentication.
|
||||
|
||||
## Inputs and Outputs
|
||||
|
||||
### Inputs
|
||||
|
||||
- **Content/Prompt**: The text to analyze for routing decisions
|
||||
- **Target Blocks**: Connected blocks that are potential routing destinations
|
||||
- **Model Settings**: LLM provider and parameters
|
||||
|
||||
### Outputs
|
||||
|
||||
- **Content**: A summary of the routing decision
|
||||
- **Model**: The model used for decision-making
|
||||
- **Tokens**: Usage statistics
|
||||
- **Selected Path**: Details of the chosen routing destination, including:
|
||||
- Block ID
|
||||
- Block Type
|
||||
- Block Title
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here's an example of how a Router block might be used in a customer support workflow:
|
||||
|
||||
```yaml
|
||||
# Example Router Configuration
|
||||
prompt: |
|
||||
Analyze the user query and route to the most appropriate department.
|
||||
Choose ONE destination based on the query content and intent.
|
||||
|
||||
model: OpenAI/gpt-4
|
||||
```
|
||||
|
||||
In this example, the Router might be connected to:
|
||||
|
||||
- A product support block
|
||||
- A billing inquiries block
|
||||
- A technical support block
|
||||
- A general inquiries block
|
||||
|
||||
Based on the user's query, the Router would analyze the content and direct it to the most appropriate specialized support block.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Provide clear descriptions for target blocks**: Help the Router understand when to select each destination
|
||||
- **Use specific routing criteria**: Define clear conditions for selecting each path
|
||||
- **Consider fallback paths**: Connect a default destination for when no specific path is appropriate
|
||||
- **Test with diverse inputs**: Ensure the Router handles various input types correctly
|
||||
- **Review routing decisions**: Monitor the Router's performance and refine as needed
|
||||
231
apps/docs/content/docs/blocks/workflow.mdx
Normal file
231
apps/docs/content/docs/blocks/workflow.mdx
Normal file
@@ -0,0 +1,231 @@
|
||||
---
|
||||
title: Workflow
|
||||
description: Execute other workflows as reusable components within your current workflow
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { ThemeImage } from '@/components/ui/theme-image'
|
||||
|
||||
The Workflow block allows you to execute other workflows as reusable components within your current workflow. This powerful feature enables modular design, code reuse, and the creation of complex nested workflows that can be composed from smaller, focused workflows.
|
||||
|
||||
<ThemeImage
|
||||
lightSrc="/static/light/workflow-light.png"
|
||||
darkSrc="/static/dark/workflow-dark.png"
|
||||
alt="Workflow Block"
|
||||
width={300}
|
||||
height={175}
|
||||
/>
|
||||
|
||||
<Callout type="info">
|
||||
Workflow blocks enable modular design by allowing you to compose complex workflows from smaller, reusable components.
|
||||
</Callout>
|
||||
|
||||
## Overview
|
||||
|
||||
The Workflow block serves as a bridge between workflows, enabling you to:
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
<strong>Reuse existing workflows</strong>: Execute previously created workflows as components within new workflows
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Create modular designs</strong>: Break down complex processes into smaller, manageable workflows
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Maintain separation of concerns</strong>: Keep different business logic isolated in separate workflows
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Enable team collaboration</strong>: Share and reuse workflows across different projects and team members
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## How It Works
|
||||
|
||||
The Workflow block:
|
||||
|
||||
1. Takes a reference to another workflow in your workspace
|
||||
2. Passes input data from the current workflow to the child workflow
|
||||
3. Executes the child workflow in an isolated context
|
||||
4. Returns the results back to the parent workflow for further processing
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Workflow Selection
|
||||
|
||||
Choose which workflow to execute from a dropdown list of available workflows in your workspace. The list includes:
|
||||
|
||||
- All workflows you have access to in the current workspace
|
||||
- Workflows shared with you by other team members
|
||||
- Both enabled and disabled workflows (though only enabled workflows can be executed)
|
||||
|
||||
### Input Data
|
||||
|
||||
Define the data to pass to the child workflow:
|
||||
|
||||
- **Single Variable Input**: Select a variable or block output to pass to the child workflow
|
||||
- **Variable References**: Use `<variable.name>` to reference workflow variables
|
||||
- **Block References**: Use `<blockName.field>` to reference outputs from previous blocks
|
||||
- **Automatic Mapping**: The selected data is automatically available as `start.input` in the child workflow
|
||||
- **Optional**: The input field is optional - child workflows can run without input data
|
||||
- **Type Preservation**: Variable types (strings, numbers, objects, etc.) are preserved when passed to the child workflow
|
||||
|
||||
### Examples of Input References
|
||||
|
||||
- `<variable.customerData>` - Pass a workflow variable
|
||||
- `<dataProcessor.result>` - Pass the result from a previous block
|
||||
- `<start.input>` - Pass the original workflow input
|
||||
- `<apiCall.data.user>` - Pass a specific field from an API response
|
||||
|
||||
### Execution Context
|
||||
|
||||
The child workflow executes with:
|
||||
|
||||
- Its own isolated execution context
|
||||
- Access to the same workspace resources (API keys, environment variables)
|
||||
- Proper workspace membership and permission checks
|
||||
- Independent logging and monitoring
|
||||
|
||||
## Safety and Limitations
|
||||
|
||||
To prevent infinite recursion and ensure system stability, the Workflow block includes several safety mechanisms:
|
||||
|
||||
<Callout type="warning">
|
||||
**Cycle Detection**: The system automatically detects and prevents circular dependencies between workflows to avoid infinite loops.
|
||||
</Callout>
|
||||
|
||||
- **Maximum Depth Limit**: Nested workflows are limited to a maximum depth of 10 levels
|
||||
- **Cycle Detection**: Automatic detection and prevention of circular workflow dependencies
|
||||
- **Timeout Protection**: Child workflows inherit timeout settings to prevent indefinite execution
|
||||
- **Resource Limits**: Memory and execution time limits apply to prevent resource exhaustion
|
||||
|
||||
## Inputs and Outputs
|
||||
|
||||
<Tabs items={['Inputs', 'Outputs']}>
|
||||
<Tab>
|
||||
<ul className="list-disc space-y-2 pl-6">
|
||||
<li>
|
||||
<strong>Workflow ID</strong>: The identifier of the workflow to execute
|
||||
</li>
|
||||
<li>
|
||||
<strong>Input Variable</strong>: Variable or block reference to pass to the child workflow (e.g., `<variable.name>` or `<block.field>`)
|
||||
</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<ul className="list-disc space-y-2 pl-6">
|
||||
<li>
|
||||
<strong>Response</strong>: The complete output from the child workflow execution
|
||||
</li>
|
||||
<li>
|
||||
<strong>Child Workflow Name</strong>: The name of the executed child workflow
|
||||
</li>
|
||||
<li>
|
||||
<strong>Success Status</strong>: Boolean indicating whether the child workflow completed successfully
|
||||
</li>
|
||||
<li>
|
||||
<strong>Error Information</strong>: Details about any errors that occurred during execution
|
||||
</li>
|
||||
<li>
|
||||
<strong>Execution Metadata</strong>: Information about execution time, resource usage, and performance
|
||||
</li>
|
||||
</ul>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here's an example of how a Workflow block might be used to create a modular customer onboarding process:
|
||||
|
||||
### Parent Workflow: Customer Onboarding
|
||||
```yaml
|
||||
# Main customer onboarding workflow
|
||||
blocks:
|
||||
- type: workflow
|
||||
name: "Validate Customer Data"
|
||||
workflowId: "customer-validation-workflow"
|
||||
input: "<variable.newCustomer>"
|
||||
|
||||
- type: workflow
|
||||
name: "Setup Customer Account"
|
||||
workflowId: "account-setup-workflow"
|
||||
input: "<Validate Customer Data.result>"
|
||||
|
||||
- type: workflow
|
||||
name: "Send Welcome Email"
|
||||
workflowId: "welcome-email-workflow"
|
||||
input: "<Setup Customer Account.result.accountDetails>"
|
||||
```
|
||||
|
||||
### Child Workflow: Customer Validation
|
||||
```yaml
|
||||
# Reusable customer validation workflow
|
||||
# Access the input data using: start.input
|
||||
blocks:
|
||||
- type: function
|
||||
name: "Validate Email"
|
||||
code: |
|
||||
const customerData = start.input;
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(customerData.email);
|
||||
|
||||
- type: api
|
||||
name: "Check Credit Score"
|
||||
url: "https://api.creditcheck.com/score"
|
||||
method: "POST"
|
||||
body: "<start.input>"
|
||||
```
|
||||
|
||||
### Variable Reference Examples
|
||||
|
||||
```yaml
|
||||
# Using workflow variables
|
||||
input: "<variable.customerInfo>"
|
||||
|
||||
# Using block outputs
|
||||
input: "<dataProcessor.cleanedData>"
|
||||
|
||||
# Using nested object properties
|
||||
input: "<apiCall.data.user.profile>"
|
||||
|
||||
# Using array elements (if supported by the resolver)
|
||||
input: "<listProcessor.items[0]>"
|
||||
```
|
||||
|
||||
## Access Control and Permissions
|
||||
|
||||
The Workflow block respects workspace permissions and access controls:
|
||||
|
||||
- **Workspace Membership**: Only workflows within the same workspace can be executed
|
||||
- **Permission Inheritance**: Child workflows inherit the execution permissions of the parent workflow
|
||||
- **API Key Access**: Child workflows have access to the same API keys and environment variables as the parent
|
||||
- **User Context**: The execution maintains the original user context for audit and logging purposes
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Keep workflows focused**: Design child workflows to handle specific, well-defined tasks
|
||||
- **Minimize nesting depth**: Avoid deeply nested workflow hierarchies for better maintainability
|
||||
- **Handle errors gracefully**: Implement proper error handling for child workflow failures
|
||||
- **Document dependencies**: Clearly document which workflows depend on others
|
||||
- **Version control**: Consider versioning strategies for workflows that are used as components
|
||||
- **Test independently**: Ensure child workflows can be tested and validated independently
|
||||
- **Monitor performance**: Be aware that nested workflows can impact overall execution time
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Microservice Architecture
|
||||
Break down complex business processes into smaller, focused workflows that can be developed and maintained independently.
|
||||
|
||||
### Reusable Components
|
||||
Create library workflows for common operations like data validation, email sending, or API integrations that can be reused across multiple projects.
|
||||
|
||||
### Conditional Execution
|
||||
Use workflow blocks within conditional logic to execute different business processes based on runtime conditions.
|
||||
|
||||
### Parallel Processing
|
||||
Combine workflow blocks with parallel execution to run multiple child workflows simultaneously for improved performance.
|
||||
|
||||
<Callout type="tip">
|
||||
When designing modular workflows, think of each workflow as a function with clear inputs, outputs, and a single responsibility.
|
||||
</Callout>
|
||||
190
apps/docs/content/docs/connections/accessing-data.mdx
Normal file
190
apps/docs/content/docs/connections/accessing-data.mdx
Normal file
@@ -0,0 +1,190 @@
|
||||
---
|
||||
title: Accessing Connected Data
|
||||
description: Techniques for accessing and manipulating data from connected blocks
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { File, Files, Folder } from 'fumadocs-ui/components/files'
|
||||
|
||||
Once blocks are connected, you can access data from source blocks in destination blocks using connection tags and various data access techniques.
|
||||
|
||||
## Basic Data Access
|
||||
|
||||
The simplest way to access data is through direct references using connection tags:
|
||||
|
||||
<Files>
|
||||
<File name="Simple Property" annotation="<block.content>" />
|
||||
<File name="Nested Property" annotation="<block.tokens.total>" />
|
||||
<File name="Array Element" annotation="<block.items[0].name>" />
|
||||
<File name="Complex Path" annotation="<block.data.users[2].profile.email>" />
|
||||
</Files>
|
||||
|
||||
## Advanced Data Access Techniques
|
||||
|
||||
### Array Access
|
||||
|
||||
You can access array elements using square bracket notation:
|
||||
|
||||
```javascript
|
||||
// Access the first item in an array
|
||||
<block.items[0]>
|
||||
|
||||
// Access a specific property of an array item
|
||||
<block.items[2].name>
|
||||
|
||||
// Access the last item in an array (in Function blocks)
|
||||
const items = input.block.items;
|
||||
const lastItem = items[items.length - 1];
|
||||
```
|
||||
|
||||
### Object Property Access
|
||||
|
||||
Access object properties using dot notation:
|
||||
|
||||
```javascript
|
||||
// Access a simple property
|
||||
<block.content>
|
||||
|
||||
// Access a nested property
|
||||
<block.data.user.profile.name>
|
||||
|
||||
// Access a property with special characters (in Function blocks)
|
||||
const data = input.block.data;
|
||||
const specialProp = data['property-with-dashes'];
|
||||
```
|
||||
|
||||
### Dynamic References
|
||||
|
||||
Connection references are evaluated at runtime, allowing for dynamic data flow through your workflow:
|
||||
|
||||
```javascript
|
||||
// In a Function block, you can access connected data
|
||||
const userName = input.userBlock.name;
|
||||
const orderTotal = input.apiBlock.body.order.total;
|
||||
|
||||
// Process the data
|
||||
const discount = orderTotal > 100 ? 0.1 : 0;
|
||||
const finalPrice = orderTotal * (1 - discount);
|
||||
|
||||
// Return the result
|
||||
return {
|
||||
userName,
|
||||
originalTotal: orderTotal,
|
||||
discount: discount * 100 + '%',
|
||||
finalPrice
|
||||
};
|
||||
```
|
||||
|
||||
## Data Transformation
|
||||
|
||||
### Using Function Blocks
|
||||
|
||||
Function blocks are the most powerful way to transform data between connections:
|
||||
|
||||
```javascript
|
||||
// Example: Transform API response data
|
||||
const apiResponse = input.apiBlock.data;
|
||||
const transformedData = {
|
||||
users: apiResponse.results.map(user => ({
|
||||
id: user.id,
|
||||
fullName: `${user.firstName} ${user.lastName}`,
|
||||
email: user.email.toLowerCase(),
|
||||
isActive: user.status === 'active'
|
||||
})),
|
||||
totalCount: apiResponse.count,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return transformedData;
|
||||
```
|
||||
|
||||
### String Interpolation
|
||||
|
||||
You can combine connection tags with static text:
|
||||
|
||||
```
|
||||
Hello, <userBlock.name>! Your order #<orderBlock.id> has been processed.
|
||||
```
|
||||
|
||||
### Conditional Content
|
||||
|
||||
In Function blocks, you can create conditional content based on connected data:
|
||||
|
||||
```javascript
|
||||
const user = input.userBlock;
|
||||
const orderTotal = input.orderBlock.total;
|
||||
|
||||
let message = `Thank you for your order, ${user.name}!`;
|
||||
|
||||
if (orderTotal > 100) {
|
||||
message += " You've qualified for free shipping!";
|
||||
} else {
|
||||
message += ` Add $${(100 - orderTotal).toFixed(2)} more to qualify for free shipping.`;
|
||||
}
|
||||
|
||||
return { message };
|
||||
```
|
||||
|
||||
## Handling Missing Data
|
||||
|
||||
It's important to handle cases where connected data might be missing or null:
|
||||
|
||||
<Callout type="warning">
|
||||
Always validate connected data before using it, especially when accessing nested properties or
|
||||
array elements.
|
||||
</Callout>
|
||||
|
||||
### Default Values
|
||||
|
||||
In Function blocks, you can provide default values for missing data:
|
||||
|
||||
```javascript
|
||||
const userName = input.userBlock?.name || 'Guest'
|
||||
const items = input.orderBlock?.items || []
|
||||
const total = input.orderBlock?.total ?? 0
|
||||
```
|
||||
|
||||
### Conditional Checks
|
||||
|
||||
Check if data exists before accessing nested properties:
|
||||
|
||||
```javascript
|
||||
let userEmail = 'No email provided'
|
||||
if (input.userBlock && input.userBlock.contact && input.userBlock.contact.email) {
|
||||
userEmail = input.userBlock.contact.email
|
||||
}
|
||||
```
|
||||
|
||||
### Optional Chaining
|
||||
|
||||
In Function blocks, use optional chaining to safely access nested properties:
|
||||
|
||||
```javascript
|
||||
const userCity = input.userBlock?.address?.city
|
||||
const firstItemName = input.orderBlock?.items?.[0]?.name
|
||||
```
|
||||
|
||||
## Debugging Connection Data
|
||||
|
||||
When troubleshooting connection issues, these techniques can help:
|
||||
|
||||
1. **Log Data**: In Function blocks, use `console.log()` to inspect connected data
|
||||
2. **Return Full Objects**: Return the full input object to see all available data
|
||||
3. **Check Types**: Verify the data types of connected values
|
||||
4. **Validate Paths**: Ensure you're using the correct path to access nested data
|
||||
|
||||
```javascript
|
||||
// Example debugging function
|
||||
function debugConnections() {
|
||||
console.log('All inputs:', input)
|
||||
console.log('User data type:', typeof input.userBlock)
|
||||
console.log('Order items:', input.orderBlock?.items)
|
||||
|
||||
return {
|
||||
debug: true,
|
||||
allInputs: input,
|
||||
userExists: !!input.userBlock,
|
||||
orderItemCount: input.orderBlock?.items?.length || 0,
|
||||
}
|
||||
}
|
||||
```
|
||||
76
apps/docs/content/docs/connections/basics.mdx
Normal file
76
apps/docs/content/docs/connections/basics.mdx
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: Connection Basics
|
||||
description: Learn how connections work in Sim Studio
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
|
||||
## How Connections Work
|
||||
|
||||
Connections are the pathways that allow data to flow between blocks in your workflow. When you connect two blocks in Sim Studio, you're establishing a data flow relationship that defines how information passes from one block to another.
|
||||
|
||||
<Callout type="info">
|
||||
Each connection represents a directed relationship where data flows from a source block's output
|
||||
to a destination block's input.
|
||||
</Callout>
|
||||
|
||||
### Creating Connections
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
<strong>Select Source Block</strong>: Click on the output port of the block you want to connect
|
||||
from
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Draw Connection</strong>: Drag to the input port of the destination block
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Confirm Connection</strong>: Release to create the connection
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Configure (Optional)</strong>: Some connections may require additional configuration
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Connection Flow
|
||||
|
||||
The flow of data through connections follows these principles:
|
||||
|
||||
1. **Directional Flow**: Data always flows from outputs to inputs
|
||||
2. **Execution Order**: Blocks execute in order based on their connections
|
||||
3. **Data Transformation**: Data may be transformed as it passes between blocks
|
||||
4. **Conditional Paths**: Some blocks (like Router and Condition) can direct flow to different paths
|
||||
|
||||
### Connection Visualization
|
||||
|
||||
Connections are visually represented in the workflow editor:
|
||||
|
||||
- **Solid Lines**: Active connections that will pass data
|
||||
- **Animated Flow**: During execution, data flow is visualized along connections
|
||||
- **Color Coding**: Different connection types may have different colors
|
||||
- **Connection Tags**: Visual indicators showing what data is available
|
||||
|
||||
### Managing Connections
|
||||
|
||||
You can manage your connections in several ways:
|
||||
|
||||
- **Delete**: Click on a connection and press Delete or use the context menu
|
||||
- **Reroute**: Drag a connection to change its path
|
||||
- **Inspect**: Click on a connection to see details about the data being passed
|
||||
- **Disable**: Temporarily disable a connection without deleting it
|
||||
|
||||
<Callout type="warning">
|
||||
Deleting a connection will immediately stop data flow between the blocks. Make sure this is
|
||||
intended before removing connections.
|
||||
</Callout>
|
||||
|
||||
## Connection Compatibility
|
||||
|
||||
Not all blocks can be connected to each other. Compatibility depends on:
|
||||
|
||||
1. **Data Type Compatibility**: The output type must be compatible with the input type
|
||||
2. **Block Restrictions**: Some blocks may have restrictions on what they can connect to
|
||||
3. **Workflow Logic**: Connections must make logical sense in the context of your workflow
|
||||
|
||||
The editor will indicate when connections are invalid or incompatible.
|
||||
208
apps/docs/content/docs/connections/best-practices.mdx
Normal file
208
apps/docs/content/docs/connections/best-practices.mdx
Normal file
@@ -0,0 +1,208 @@
|
||||
---
|
||||
title: Connection Best Practices
|
||||
description: Recommended patterns for effective connection management
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Step, Steps } from 'fumadocs-ui/components/steps'
|
||||
|
||||
## Workflow Organization
|
||||
|
||||
### Organize Your Connections
|
||||
|
||||
Keep your workflow clean and understandable by organizing connections logically:
|
||||
|
||||
- **Minimize crossing connections** when possible to reduce visual complexity
|
||||
- **Group related blocks together** to make data flow more intuitive
|
||||
- **Use consistent flow direction** (typically left-to-right or top-to-bottom)
|
||||
- **Label complex connections** with descriptive names
|
||||
|
||||
<Callout type="info">
|
||||
A well-organized workflow is easier to understand, debug, and maintain. Take time to arrange your
|
||||
blocks and connections in a logical manner.
|
||||
</Callout>
|
||||
|
||||
### Connection Naming Conventions
|
||||
|
||||
When working with multiple connections, consistent naming helps maintain clarity:
|
||||
|
||||
<Steps>
|
||||
<Step>
|
||||
<strong>Use descriptive block names</strong>: Name blocks based on their function (e.g.,
|
||||
"UserDataFetcher", "ResponseGenerator")
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Be specific with connection references</strong>: Use clear variable names when
|
||||
referencing connections in code
|
||||
</Step>
|
||||
<Step>
|
||||
<strong>Document complex connections</strong>: Add comments explaining non-obvious data
|
||||
transformations
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Data Validation
|
||||
|
||||
### Validate Data Flow
|
||||
|
||||
Ensure that the data being passed between blocks is compatible:
|
||||
|
||||
- **Check that required fields are available** in the source block
|
||||
- **Verify data types match expectations** before using them
|
||||
- **Use Function blocks to transform data** when necessary
|
||||
- **Handle missing or null values** with default values or conditional logic
|
||||
|
||||
```javascript
|
||||
// Example: Validating and transforming data in a Function block
|
||||
function processUserData() {
|
||||
// Validate required fields
|
||||
if (!input.userBlock || !input.userBlock.id) {
|
||||
return { error: 'Missing user data', valid: false }
|
||||
}
|
||||
|
||||
// Transform and validate data types
|
||||
const userId = String(input.userBlock.id)
|
||||
const userName = input.userBlock.name || 'Unknown User'
|
||||
const userScore = Number(input.userBlock.score) || 0
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
user: {
|
||||
id: userId,
|
||||
name: userName,
|
||||
score: userScore,
|
||||
isHighScore: userScore > 100,
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
### Document Connection Purpose
|
||||
|
||||
Add comments or descriptions to clarify the purpose of connections, especially in complex workflows:
|
||||
|
||||
- **What data is being passed**: Document the key fields and their purpose
|
||||
- **Why this connection exists**: Explain the relationship between blocks
|
||||
- **Any transformations or conditions applied**: Note any data processing that occurs
|
||||
|
||||
```javascript
|
||||
// Example: Documenting connection purpose in a Function block
|
||||
/*
|
||||
* This function processes user data from the UserFetcher block
|
||||
* and order history from the OrderHistory block to generate
|
||||
* personalized product recommendations.
|
||||
*
|
||||
* Input:
|
||||
* - userBlock: User profile data (id, preferences, history)
|
||||
* - orderBlock: Recent order history (items, dates, amounts)
|
||||
*
|
||||
* Output:
|
||||
* - recommendations: Array of recommended product IDs
|
||||
* - userSegment: Calculated user segment for marketing
|
||||
* - conversionProbability: Estimated likelihood of purchase
|
||||
*/
|
||||
function generateRecommendations() {
|
||||
// Implementation...
|
||||
}
|
||||
```
|
||||
|
||||
## Testing and Debugging
|
||||
|
||||
### Test Connection References
|
||||
|
||||
Verify that connection references work as expected:
|
||||
|
||||
- **Test with different input values** to ensure robustness
|
||||
- **Check edge cases** (empty values, large datasets, special characters)
|
||||
- **Ensure error handling for missing or invalid data**
|
||||
- **Use console logging in Function blocks** to debug connection issues
|
||||
|
||||
```javascript
|
||||
// Example: Testing connection references with edge cases
|
||||
function testConnections() {
|
||||
console.log('Testing connections...')
|
||||
|
||||
// Log all inputs for debugging
|
||||
console.log('All inputs:', JSON.stringify(input, null, 2))
|
||||
|
||||
// Test for missing data
|
||||
const hasUserData = !!input.userBlock
|
||||
console.log('Has user data:', hasUserData)
|
||||
|
||||
// Test edge cases
|
||||
const items = input.orderBlock?.items || []
|
||||
console.log('Item count:', items.length)
|
||||
console.log('Empty items test:', items.length === 0 ? 'Passed' : 'Failed')
|
||||
|
||||
// Return test results
|
||||
return {
|
||||
tests: {
|
||||
hasUserData,
|
||||
hasItems: items.length > 0,
|
||||
hasLargeOrder: items.length > 10,
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Optimize Data Flow
|
||||
|
||||
Keep your workflows efficient by optimizing how data flows through connections:
|
||||
|
||||
- **Pass only necessary data** between blocks to reduce memory usage
|
||||
- **Use Function blocks to filter large datasets** before passing them on
|
||||
- **Consider caching results** for expensive operations
|
||||
- **Break complex workflows into smaller, reusable components**
|
||||
|
||||
```javascript
|
||||
// Example: Optimizing data flow by filtering
|
||||
function optimizeUserData() {
|
||||
const userData = input.userBlock
|
||||
|
||||
// Only pass necessary fields to downstream blocks
|
||||
return {
|
||||
id: userData.id,
|
||||
name: userData.name,
|
||||
email: userData.email,
|
||||
// Filter out unnecessary profile data, history, etc.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Secure Sensitive Data
|
||||
|
||||
Protect sensitive information when using connections:
|
||||
|
||||
- **Never expose API keys or credentials** in connection data
|
||||
- **Sanitize user input** before processing it
|
||||
- **Redact sensitive information** when logging connection data
|
||||
- **Use secure connections** for external API calls
|
||||
|
||||
<Callout type="warning">
|
||||
Be careful when logging connection data that might contain sensitive information. Always redact or
|
||||
mask sensitive fields like passwords, API keys, or personal information.
|
||||
</Callout>
|
||||
|
||||
## Advanced Patterns
|
||||
|
||||
### Conditional Connections
|
||||
|
||||
Use Condition blocks to create dynamic workflows:
|
||||
|
||||
- **Route data based on content** to different processing paths
|
||||
- **Implement fallback paths** for error handling
|
||||
- **Create decision trees** for complex business logic
|
||||
|
||||
### Feedback Loops
|
||||
|
||||
Create more sophisticated workflows with feedback connections:
|
||||
|
||||
- **Implement iterative processing** by connecting later blocks back to earlier ones
|
||||
- **Use Memory blocks** to store state between iterations
|
||||
- **Set termination conditions** to prevent infinite loops
|
||||
192
apps/docs/content/docs/connections/data-structure.mdx
Normal file
192
apps/docs/content/docs/connections/data-structure.mdx
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
title: Connection Data Structure
|
||||
description: Understanding the data structure of different block outputs
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
|
||||
When you connect blocks, the output data structure from the source block determines what values are available in the destination block. Each block type produces a specific output structure that you can reference in downstream blocks.
|
||||
|
||||
<Callout type="info">
|
||||
Understanding these data structures is essential for effectively using connection tags and
|
||||
accessing the right data in your workflows.
|
||||
</Callout>
|
||||
|
||||
## Block Output Structures
|
||||
|
||||
Different block types produce different output structures. Here's what you can expect from each block type:
|
||||
|
||||
<Tabs items={['Agent Output', 'API Output', 'Function Output', 'Evaluator Output', 'Condition Output', 'Router Output']}>
|
||||
<Tab>
|
||||
```json
|
||||
{
|
||||
"content": "The generated text response",
|
||||
"model": "gpt-4o",
|
||||
"tokens": {
|
||||
"prompt": 120,
|
||||
"completion": 85,
|
||||
"total": 205
|
||||
},
|
||||
"toolCalls": [...],
|
||||
"cost": [...],
|
||||
"usage": [...]
|
||||
}
|
||||
```
|
||||
|
||||
### Agent Block Output Fields
|
||||
|
||||
- **content**: The main text response generated by the agent
|
||||
- **model**: The AI model used (e.g., "gpt-4o", "claude-3-opus")
|
||||
- **tokens**: Token usage statistics
|
||||
- **prompt**: Number of tokens in the prompt
|
||||
- **completion**: Number of tokens in the completion
|
||||
- **total**: Total tokens used
|
||||
- **toolCalls**: Array of tool calls made by the agent (if any)
|
||||
- **cost**: Array of cost objects for each tool call (if any)
|
||||
- **usage**: Token usage statistics for the entire response
|
||||
|
||||
</Tab>
|
||||
<Tab>
|
||||
```json
|
||||
{
|
||||
"data": "Response data",
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "application/json",
|
||||
"cache-control": "no-cache"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API Block Output Fields
|
||||
|
||||
- **data**: The response data from the API (can be any type)
|
||||
- **status**: HTTP status code of the response
|
||||
- **headers**: HTTP headers returned by the API
|
||||
|
||||
</Tab>
|
||||
<Tab>
|
||||
```json
|
||||
{
|
||||
"result": "Function return value",
|
||||
"stdout": "Console output",
|
||||
"executionTime": 45
|
||||
}
|
||||
```
|
||||
|
||||
### Function Block Output Fields
|
||||
|
||||
- **result**: The return value of the function (can be any type)
|
||||
- **stdout**: Console output captured during function execution
|
||||
- **executionTime**: Time taken to execute the function (in milliseconds)
|
||||
|
||||
</Tab>
|
||||
<Tab>
|
||||
```json
|
||||
{
|
||||
"content": "Evaluation summary",
|
||||
"model": "gpt-4o",
|
||||
"tokens": {
|
||||
"prompt": 120,
|
||||
"completion": 85,
|
||||
"total": 205
|
||||
},
|
||||
"metric1": 8.5,
|
||||
"metric2": 7.2,
|
||||
"metric3": 9.0
|
||||
}
|
||||
```
|
||||
|
||||
### Evaluator Block Output Fields
|
||||
|
||||
- **content**: Summary of the evaluation
|
||||
- **model**: The AI model used for evaluation
|
||||
- **tokens**: Token usage statistics
|
||||
- **[metricName]**: Score for each metric defined in the evaluator (dynamic fields)
|
||||
|
||||
</Tab>
|
||||
<Tab>
|
||||
```json
|
||||
{
|
||||
"content": "Original content passed through",
|
||||
"conditionResult": true,
|
||||
"selectedPath": {
|
||||
"blockId": "2acd9007-27e8-4510-a487-73d3b825e7c1",
|
||||
"blockType": "agent",
|
||||
"blockTitle": "Follow-up Agent"
|
||||
},
|
||||
"selectedConditionId": "condition-1"
|
||||
}
|
||||
```
|
||||
|
||||
### Condition Block Output Fields
|
||||
|
||||
- **content**: The original content passed through
|
||||
- **conditionResult**: Boolean result of the condition evaluation
|
||||
- **selectedPath**: Information about the selected path
|
||||
- **blockId**: ID of the next block in the selected path
|
||||
- **blockType**: Type of the next block
|
||||
- **blockTitle**: Title of the next block
|
||||
- **selectedConditionId**: ID of the selected condition
|
||||
|
||||
</Tab>
|
||||
<Tab>
|
||||
```json
|
||||
{
|
||||
"content": "Routing decision",
|
||||
"model": "gpt-4o",
|
||||
"tokens": {
|
||||
"prompt": 120,
|
||||
"completion": 85,
|
||||
"total": 205
|
||||
},
|
||||
"selectedPath": {
|
||||
"blockId": "2acd9007-27e8-4510-a487-73d3b825e7c1",
|
||||
"blockType": "agent",
|
||||
"blockTitle": "Customer Service Agent"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Router Block Output Fields
|
||||
|
||||
- **content**: The routing decision text
|
||||
- **model**: The AI model used for routing
|
||||
- **tokens**: Token usage statistics
|
||||
- **selectedPath**: Information about the selected path
|
||||
- **blockId**: ID of the selected destination block
|
||||
- **blockType**: Type of the selected block
|
||||
- **blockTitle**: Title of the selected block
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Custom Output Structures
|
||||
|
||||
Some blocks may produce custom output structures based on their configuration:
|
||||
|
||||
1. **Agent Blocks with Response Format**: When using a response format in an Agent block, the output structure will match the defined schema instead of the standard structure.
|
||||
|
||||
2. **Function Blocks**: The `result` field can contain any data structure returned by your function code.
|
||||
|
||||
3. **API Blocks**: The `data` field will contain whatever the API returns, which could be any valid JSON structure.
|
||||
|
||||
<Callout type="warning">
|
||||
Always check the actual output structure of your blocks during development to ensure you're
|
||||
referencing the correct fields in your connections.
|
||||
</Callout>
|
||||
|
||||
## Nested Data Structures
|
||||
|
||||
Many block outputs contain nested data structures. You can access these using dot notation in connection tags:
|
||||
|
||||
```
|
||||
<blockId.path.to.nested.data>
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
- `<agent1.tokens.total>` - Access the total tokens from an Agent block
|
||||
- `<api1.data.results[0].id>` - Access the ID of the first result from an API response
|
||||
- `<function1.result.calculations.total>` - Access a nested field in a Function block's result
|
||||
41
apps/docs/content/docs/connections/index.mdx
Normal file
41
apps/docs/content/docs/connections/index.mdx
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
title: Connections
|
||||
description: Connect your blocks to one another.
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Card, Cards } from 'fumadocs-ui/components/card'
|
||||
import { ConnectIcon } from '@/components/icons'
|
||||
|
||||
Connections are the pathways that allow data to flow between blocks in your workflow. They define how information is passed from one block to another, enabling you to create sophisticated, multi-step processes.
|
||||
|
||||
<Callout type="info">
|
||||
Properly configured connections are essential for creating effective workflows. They determine how
|
||||
data moves through your system and how blocks interact with each other.
|
||||
</Callout>
|
||||
|
||||
<div>
|
||||
<video autoPlay loop muted playsInline className="w-full" src="/connections.mp4"></video>
|
||||
</div>
|
||||
|
||||
## Connection Types
|
||||
|
||||
Sim Studio supports different types of connections that enable various workflow patterns:
|
||||
|
||||
<Cards>
|
||||
<Card title="Connection Basics" href="/connections/basics">
|
||||
Learn how connections work and how to create them in your workflows
|
||||
</Card>
|
||||
<Card title="Connection Tags" href="/connections/tags">
|
||||
Understand how to use connection tags to reference data between blocks
|
||||
</Card>
|
||||
<Card title="Data Structure" href="/connections/data-structure">
|
||||
Explore the output data structures of different block types
|
||||
</Card>
|
||||
<Card title="Accessing Data" href="/connections/accessing-data">
|
||||
Learn techniques for accessing and manipulating connected data
|
||||
</Card>
|
||||
<Card title="Best Practices" href="/connections/best-practices">
|
||||
Follow recommended patterns for effective connection management
|
||||
</Card>
|
||||
</Cards>
|
||||
4
apps/docs/content/docs/connections/meta.json
Normal file
4
apps/docs/content/docs/connections/meta.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"title": "Connections",
|
||||
"pages": ["basics", "tags", "data-structure", "accessing-data", "best-practices"]
|
||||
}
|
||||
109
apps/docs/content/docs/connections/tags.mdx
Normal file
109
apps/docs/content/docs/connections/tags.mdx
Normal file
@@ -0,0 +1,109 @@
|
||||
---
|
||||
title: Connection Tags
|
||||
description: Using connection tags to reference data between blocks
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
|
||||
Connection tags are visual representations of the data available from connected blocks. They provide an easy way to reference outputs from previous blocks in your workflow.
|
||||
|
||||
<div>
|
||||
<video autoPlay loop muted playsInline className="w-full" src="/connections.mp4"></video>
|
||||
</div>
|
||||
|
||||
### What Are Connection Tags?
|
||||
|
||||
Connection tags are interactive elements that appear when blocks are connected. They represent the data that can flow from one block to another and allow you to:
|
||||
|
||||
- Visualize available data from source blocks
|
||||
- Reference specific data fields in destination blocks
|
||||
- Create dynamic data flows between blocks
|
||||
|
||||
<Callout type="info">
|
||||
Connection tags make it easy to see what data is available from previous blocks and use it in your
|
||||
current block without having to remember complex data structures.
|
||||
</Callout>
|
||||
|
||||
## Using Connection Tags
|
||||
|
||||
There are two primary ways to use connection tags in your workflows:
|
||||
|
||||
<div className="my-6 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="rounded-lg border border-gray-200 p-4 dark:border-gray-800">
|
||||
<h3 className="mb-2 text-lg font-medium">Drag and Drop</h3>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Click on a connection tag and drag it into input fields of destination blocks. A dropdown will
|
||||
appear showing available values.
|
||||
</div>
|
||||
<ol className="mt-2 list-decimal pl-5 text-sm text-gray-600 dark:text-gray-400">
|
||||
<li>Hover over a connection tag to see available data</li>
|
||||
<li>Click and drag the tag to an input field</li>
|
||||
<li>Select the specific data field from the dropdown</li>
|
||||
<li>The reference is inserted automatically</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-gray-200 p-4 dark:border-gray-800">
|
||||
<h3 className="mb-2 text-lg font-medium">Angle Bracket Syntax</h3>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Type <code><></code> in input fields to see a dropdown of available connection values
|
||||
from previous blocks.
|
||||
</div>
|
||||
<ol className="mt-2 list-decimal pl-5 text-sm text-gray-600 dark:text-gray-400">
|
||||
<li>Click in any input field where you want to use connected data</li>
|
||||
<li>
|
||||
Type <code><></code> to trigger the connection dropdown
|
||||
</li>
|
||||
<li>Browse and select the data you want to reference</li>
|
||||
<li>Continue typing or select from the dropdown to complete the reference</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Tag Syntax
|
||||
|
||||
Connection tags use a simple syntax to reference data:
|
||||
|
||||
```
|
||||
<blockId.path.to.data>
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `blockId` is the identifier of the source block
|
||||
- `path.to.data` is the path to the specific data field
|
||||
|
||||
For example:
|
||||
|
||||
- `<agent1.content>` - References the content field from a block with ID "agent1"
|
||||
- `<api2.data.users[0].name>` - References the name of the first user in the users array from the data field of a block with ID "api2"
|
||||
|
||||
## Dynamic Tag References
|
||||
|
||||
Connection tags are evaluated at runtime, which means:
|
||||
|
||||
1. They always reference the most current data
|
||||
2. They can be used in expressions and combined with static text
|
||||
3. They can be nested within other data structures
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
// Reference in text
|
||||
"The user's name is <userBlock.name>"
|
||||
|
||||
// Reference in JSON
|
||||
{
|
||||
"userName": "<userBlock.name>",
|
||||
"orderTotal": <apiBlock.data.total>
|
||||
}
|
||||
|
||||
// Reference in code
|
||||
const greeting = "Hello, <userBlock.name>!";
|
||||
const total = <apiBlock.data.total> * 1.1; // Add 10% tax
|
||||
```
|
||||
|
||||
<Callout type="warning">
|
||||
When using connection tags in numeric contexts, make sure the referenced data is actually a number
|
||||
to avoid type conversion issues.
|
||||
</Callout>
|
||||
@@ -1,154 +0,0 @@
|
||||
---
|
||||
title: Agent
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { Image } from '@/components/ui/image'
|
||||
|
||||
Der Agent-Block verbindet deinen Workflow mit Large Language Models (LLMs). Er verarbeitet natürlichsprachliche Eingaben, ruft externe Tools auf und generiert strukturierte oder unstrukturierte Ausgaben.
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src="/static/blocks/agent.png"
|
||||
alt="Agent-Block-Konfiguration"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## Konfigurationsoptionen
|
||||
|
||||
### System-Prompt
|
||||
|
||||
Der System-Prompt legt die Betriebsparameter und Verhaltenseinschränkungen des Agenten fest. Diese Konfiguration definiert die Rolle des Agenten, die Antwortmethodik und die Verarbeitungsgrenzen für alle eingehenden Anfragen.
|
||||
|
||||
```markdown
|
||||
You are a helpful assistant that specializes in financial analysis.
|
||||
Always provide clear explanations and cite sources when possible.
|
||||
When responding to questions about investments, include risk disclaimers.
|
||||
```
|
||||
|
||||
### Benutzer-Prompt
|
||||
|
||||
Der Benutzer-Prompt stellt die primären Eingabedaten für die Inferenzverarbeitung dar. Dieser Parameter akzeptiert natürlichsprachlichen Text oder strukturierte Daten, die der Agent analysieren und auf die er reagieren wird. Zu den Eingabequellen gehören:
|
||||
|
||||
- **Statische Konfiguration**: Direkte Texteingabe, die in der Block-Konfiguration angegeben ist
|
||||
- **Dynamische Eingabe**: Daten, die von vorgelagerten Blöcken über Verbindungsschnittstellen übergeben werden
|
||||
- **Laufzeitgenerierung**: Programmatisch generierte Inhalte während der Workflow-Ausführung
|
||||
|
||||
### Modellauswahl
|
||||
|
||||
Der Agent-Block unterstützt mehrere LLM-Anbieter über eine einheitliche Inferenzschnittstelle. Verfügbare Modelle umfassen:
|
||||
|
||||
- **OpenAI**: GPT-5.1, GPT-5, GPT-4o, o1, o3, o4-mini, gpt-4.1
|
||||
- **Anthropic**: Claude 4.5 Sonnet, Claude Opus 4.1
|
||||
- **Google**: Gemini 2.5 Pro, Gemini 2.0 Flash
|
||||
- **Andere Anbieter**: Groq, Cerebras, xAI, Azure OpenAI, OpenRouter
|
||||
- **Lokale Modelle**: Ollama oder VLLM-kompatible Modelle
|
||||
|
||||
### Temperatur
|
||||
|
||||
Steuert die Zufälligkeit und Kreativität der Antworten:
|
||||
|
||||
- **Niedrig (0-0,3)**: Deterministisch und fokussiert. Am besten für faktische Aufgaben und Genauigkeit.
|
||||
- **Mittel (0,3-0,7)**: Ausgewogene Kreativität und Fokus. Gut für allgemeine Verwendung.
|
||||
- **Hoch (0,7-2,0)**: Kreativ und abwechslungsreich. Ideal für Brainstorming und Content-Generierung.
|
||||
|
||||
### API-Schlüssel
|
||||
|
||||
Ihr API-Schlüssel für den ausgewählten LLM-Anbieter. Dieser wird sicher gespeichert und für die Authentifizierung verwendet.
|
||||
|
||||
### Tools
|
||||
|
||||
Erweitern Sie die Fähigkeiten des Agenten mit externen Integrationen. Wählen Sie aus über 60 vorgefertigten Tools oder definieren Sie benutzerdefinierte Funktionen.
|
||||
|
||||
**Verfügbare Kategorien:**
|
||||
- **Kommunikation**: Gmail, Slack, Telegram, WhatsApp, Microsoft Teams
|
||||
- **Datenquellen**: Notion, Google Sheets, Airtable, Supabase, Pinecone
|
||||
- **Webdienste**: Firecrawl, Google Search, Exa AI, Browser-Automatisierung
|
||||
- **Entwicklung**: GitHub, Jira, Linear
|
||||
- **KI-Dienste**: OpenAI, Perplexity, Hugging Face, ElevenLabs
|
||||
|
||||
**Ausführungsmodi:**
|
||||
- **Auto**: Modell entscheidet kontextbasiert, wann Tools verwendet werden
|
||||
- **Erforderlich**: Tool muss bei jeder Anfrage aufgerufen werden
|
||||
- **Keine**: Tool verfügbar, aber dem Modell nicht vorgeschlagen
|
||||
|
||||
### Antwortformat
|
||||
|
||||
Der Parameter für das Antwortformat erzwingt die Generierung strukturierter Ausgaben durch JSON-Schema-Validierung. Dies gewährleistet konsistente, maschinenlesbare Antworten, die vordefinierten Datenstrukturen entsprechen:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sentiment": {
|
||||
"type": "string",
|
||||
"enum": ["positive", "neutral", "negative"]
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"description": "Brief summary of the content"
|
||||
}
|
||||
},
|
||||
"required": ["sentiment", "summary"]
|
||||
}
|
||||
```
|
||||
|
||||
Diese Konfiguration beschränkt die Ausgabe des Modells auf die Einhaltung des angegebenen Schemas, verhindert Freitextantworten und stellt die Generierung strukturierter Daten sicher.
|
||||
|
||||
### Zugriff auf Ergebnisse
|
||||
|
||||
Nach Abschluss eines Agenten können Sie auf seine Ausgaben zugreifen:
|
||||
|
||||
- **response**: Der Antworttext oder die strukturierten Daten des Agenten
|
||||
- **usage**: Token-Nutzungsstatistiken (Prompt, Completion, Gesamt)
|
||||
- **toolExecutions**: Details zu allen Tools, die der Agent während der Ausführung verwendet hat
|
||||
- **estimatedCost**: Geschätzte Kosten des API-Aufrufs (falls verfügbar)
|
||||
|
||||
## Erweiterte Funktionen
|
||||
|
||||
### Memory + Agent: Gesprächsverlauf
|
||||
|
||||
Verwenden Sie einen memory Block mit einer konsistenten memoryId (zum Beispiel, conversationHistory), um Nachrichten zwischen Durchläufen zu speichern und diesen Verlauf in den Prompt des Agenten einzubeziehen.
|
||||
|
||||
- Fügen Sie die Nachricht des Benutzers vor dem Agenten hinzu
|
||||
- Lesen Sie den Gesprächsverlauf für den Kontext
|
||||
- Hängen Sie die Antwort des Agenten nach dessen Ausführung an
|
||||
|
||||
Siehe den [`Memory`](/tools/memory) Blockverweis für Details.
|
||||
|
||||
## Ausgaben
|
||||
|
||||
- **`<agent.content>`**: Antworttext des Agenten
|
||||
- **`<agent.tokens>`**: Token-Nutzungsstatistiken
|
||||
- **`<agent.tool_calls>`**: Details zur Tool-Ausführung
|
||||
- **`<agent.cost>`**: Geschätzte Kosten des API-Aufrufs
|
||||
|
||||
## Beispielanwendungsfälle
|
||||
|
||||
**Automatisierung des Kundenservice** - Bearbeitung von Anfragen mit Datenbank- und Tool-Zugriff
|
||||
|
||||
```
|
||||
API (Ticket) → Agent (Postgres, KB, Linear) → Gmail (Reply) → Memory (Save)
|
||||
```
|
||||
|
||||
**Multi-Modell-Inhaltsanalyse** - Analyse von Inhalten mit verschiedenen KI-Modellen
|
||||
|
||||
```
|
||||
Function (Process) → Agent (GPT-4o Technical) → Agent (Claude Sentiment) → Function (Report)
|
||||
```
|
||||
|
||||
**Tool-gestützter Rechercheassistent** - Recherche mit Websuche und Dokumentenzugriff
|
||||
|
||||
```
|
||||
Input → Agent (Google Search, Notion) → Function (Compile Report)
|
||||
```
|
||||
|
||||
## Bewährte Praktiken
|
||||
|
||||
- **Sei spezifisch in System-Prompts**: Definiere die Rolle, den Ton und die Einschränkungen des Agenten klar. Je spezifischer deine Anweisungen sind, desto besser kann der Agent seinen vorgesehenen Zweck erfüllen.
|
||||
- **Wähle die richtige Temperatureinstellung**: Verwende niedrigere Temperatureinstellungen (0-0,3), wenn Genauigkeit wichtig ist, oder erhöhe die Temperatur (0,7-2,0) für kreativere oder vielfältigere Antworten
|
||||
- **Nutze Tools effektiv**: Integriere Tools, die den Zweck des Agenten ergänzen und seine Fähigkeiten erweitern. Sei selektiv bei der Auswahl der Tools, um den Agenten nicht zu überfordern. Für Aufgaben mit wenig Überschneidung verwende einen anderen Agent-Block für die besten Ergebnisse.
|
||||
@@ -1,145 +0,0 @@
|
||||
---
|
||||
title: API
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { Image } from '@/components/ui/image'
|
||||
|
||||
Der API-Block verbindet Ihren Workflow mit externen Diensten durch HTTP-Anfragen. Unterstützt GET, POST, PUT, DELETE und PATCH Methoden für die Interaktion mit REST-APIs.
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src="/static/blocks/api.png"
|
||||
alt="API-Block"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## Konfigurationsoptionen
|
||||
|
||||
### URL
|
||||
|
||||
Die Endpunkt-URL für die API-Anfrage. Diese kann sein:
|
||||
|
||||
- Eine statische URL, die direkt im Block eingegeben wird
|
||||
- Eine dynamische URL, die mit der Ausgabe eines anderen Blocks verbunden ist
|
||||
- Eine URL mit Pfadparametern
|
||||
|
||||
### Methode
|
||||
|
||||
Wählen Sie die HTTP-Methode für Ihre Anfrage:
|
||||
|
||||
- **GET**: Daten vom Server abrufen
|
||||
- **POST**: Daten an den Server senden, um eine Ressource zu erstellen
|
||||
- **PUT**: Eine bestehende Ressource auf dem Server aktualisieren
|
||||
- **DELETE**: Eine Ressource vom Server entfernen
|
||||
- **PATCH**: Eine bestehende Ressource teilweise aktualisieren
|
||||
|
||||
### Abfrageparameter
|
||||
|
||||
Definieren Sie Schlüssel-Wert-Paare, die als Abfrageparameter an die URL angehängt werden. Zum Beispiel:
|
||||
|
||||
```
|
||||
Key: apiKey
|
||||
Value: your_api_key_here
|
||||
|
||||
Key: limit
|
||||
Value: 10
|
||||
```
|
||||
|
||||
Diese würden der URL als `?apiKey=your_api_key_here&limit=10` hinzugefügt.
|
||||
|
||||
### Header
|
||||
|
||||
Konfigurieren Sie HTTP-Header für Ihre Anfrage. Häufige Header sind:
|
||||
|
||||
```
|
||||
Key: Content-Type
|
||||
Value: application/json
|
||||
|
||||
Key: Authorization
|
||||
Value: Bearer your_token_here
|
||||
```
|
||||
|
||||
### Anfragekörper
|
||||
|
||||
Für Methoden, die einen Anfragekörper unterstützen (POST, PUT, PATCH), können Sie die zu sendenden Daten definieren. Der Körper kann sein:
|
||||
|
||||
- JSON-Daten, die direkt im Block eingegeben werden
|
||||
- Daten, die mit der Ausgabe eines anderen Blocks verbunden sind
|
||||
- Dynamisch während der Workflow-Ausführung generiert
|
||||
|
||||
### Zugriff auf Ergebnisse
|
||||
|
||||
Nach Abschluss einer API-Anfrage können Sie auf folgende Ausgaben zugreifen:
|
||||
|
||||
- **`<api.data>`**: Die Antwortdaten vom API
|
||||
- **`<api.status>`**: HTTP-Statuscode (200, 404, 500, usw.)
|
||||
- **`<api.headers>`**: Antwort-Header vom Server
|
||||
- **`<api.error>`**: Fehlerdetails, falls die Anfrage fehlgeschlagen ist
|
||||
|
||||
## Erweiterte Funktionen
|
||||
|
||||
### Dynamische URL-Konstruktion
|
||||
|
||||
Erstellen Sie URLs dynamisch mit Variablen aus vorherigen Blöcken:
|
||||
|
||||
```javascript
|
||||
// In a Function block before the API
|
||||
const userId = <start.userId>;
|
||||
const apiUrl = `https://api.example.com/users/${userId}/profile`;
|
||||
```
|
||||
|
||||
### Anfrage-Wiederholungen
|
||||
|
||||
Der API-Block verarbeitet automatisch:
|
||||
- Netzwerk-Timeouts mit exponentiellem Backoff
|
||||
- Antworten bei Ratenbegrenzung (429-Statuscodes)
|
||||
- Serverfehler (5xx-Statuscodes) mit Wiederholungslogik
|
||||
- Verbindungsfehler mit Wiederverbindungsversuchen
|
||||
|
||||
### Antwortvalidierung
|
||||
|
||||
Validieren Sie API-Antworten vor der Verarbeitung:
|
||||
|
||||
```javascript
|
||||
// In a Function block after the API
|
||||
if (<api.status> === 200) {
|
||||
const data = <api.data>;
|
||||
// Process successful response
|
||||
} else {
|
||||
// Handle error response
|
||||
console.error(`API Error: ${<api.status>}`);
|
||||
}
|
||||
```
|
||||
|
||||
## Ausgaben
|
||||
|
||||
- **`<api.data>`**: Antwortdaten vom API
|
||||
- **`<api.status>`**: HTTP-Statuscode
|
||||
- **`<api.headers>`**: Antwort-Header
|
||||
- **`<api.error>`**: Fehlerdetails bei fehlgeschlagener Anfrage
|
||||
|
||||
## Anwendungsbeispiele
|
||||
|
||||
**Benutzerprofildaten abrufen** - Benutzerinformationen von externem Dienst abrufen
|
||||
|
||||
```
|
||||
Function (Build ID) → API (GET /users/{id}) → Function (Format) → Response
|
||||
```
|
||||
|
||||
**Zahlungsabwicklung** - Zahlung über die Stripe-API verarbeiten
|
||||
|
||||
```
|
||||
Function (Validate) → API (Stripe) → Condition (Success) → Supabase (Update)
|
||||
```
|
||||
|
||||
## Bewährte Praktiken
|
||||
|
||||
- **Umgebungsvariablen für sensible Daten verwenden**: Keine API-Schlüssel oder Anmeldedaten im Code festlegen
|
||||
- **Fehler elegant behandeln**: Fehlerbehandlungslogik für fehlgeschlagene Anfragen einbinden
|
||||
- **Antworten validieren**: Statuscode und Antwortformate vor der Datenverarbeitung prüfen
|
||||
- **Ratenbegrenzungen respektieren**: Achten Sie auf API-Ratenbegrenzungen und implementieren Sie angemessene Drosselung
|
||||
@@ -1,149 +0,0 @@
|
||||
---
|
||||
title: Bedingung
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { Image } from '@/components/ui/image'
|
||||
|
||||
Der Bedingungsblock verzweigt die Workflow-Ausführung basierend auf booleschen Ausdrücken. Bewerten Sie Bedingungen anhand vorheriger Block-Ausgaben und leiten Sie zu verschiedenen Pfaden weiter, ohne dass ein LLM erforderlich ist.
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src="/static/blocks/condition.png"
|
||||
alt="Bedingungsblock"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## Konfigurationsoptionen
|
||||
|
||||
### Bedingungen
|
||||
|
||||
Definieren Sie eine oder mehrere Bedingungen, die ausgewertet werden. Jede Bedingung umfasst:
|
||||
|
||||
- **Ausdruck**: Ein JavaScript/TypeScript-Ausdruck, der zu wahr oder falsch ausgewertet wird
|
||||
- **Pfad**: Der Zielblock, zu dem weitergeleitet werden soll, wenn die Bedingung wahr ist
|
||||
- **Beschreibung**: Optionale Erklärung, was die Bedingung prüft
|
||||
|
||||
Sie können mehrere Bedingungen erstellen, die der Reihe nach ausgewertet werden, wobei die erste übereinstimmende Bedingung den Ausführungspfad bestimmt.
|
||||
|
||||
### Format für Bedingungsausdrücke
|
||||
|
||||
Bedingungen verwenden JavaScript-Syntax und können auf Eingabewerte aus vorherigen Blöcken verweisen.
|
||||
|
||||
<Tabs items={['Schwellenwert', 'Textanalyse', 'Mehrere Bedingungen']}>
|
||||
<Tab>
|
||||
|
||||
```javascript
|
||||
// Check if a score is above a threshold
|
||||
<agent.score> > 75
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab>
|
||||
|
||||
```javascript
|
||||
// Check if a text contains specific keywords
|
||||
<agent.text>.includes('urgent') || <agent.text>.includes('emergency')
|
||||
```
|
||||
|
||||
</Tab>
|
||||
<Tab>
|
||||
|
||||
```javascript
|
||||
// Check multiple conditions
|
||||
<agent.age> >= 18 && <agent.country> === 'US'
|
||||
```
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### Zugriff auf Ergebnisse
|
||||
|
||||
Nach der Auswertung einer Bedingung können Sie auf folgende Ausgaben zugreifen:
|
||||
|
||||
- **condition.result**: Boolesches Ergebnis der Bedingungsauswertung
|
||||
- **condition.matched_condition**: ID der übereinstimmenden Bedingung
|
||||
- **condition.content**: Beschreibung des Auswertungsergebnisses
|
||||
- **condition.path**: Details zum gewählten Routing-Ziel
|
||||
|
||||
## Erweiterte Funktionen
|
||||
|
||||
### Komplexe Ausdrücke
|
||||
|
||||
Verwenden Sie JavaScript-Operatoren und -Funktionen in Bedingungen:
|
||||
|
||||
```javascript
|
||||
// String operations
|
||||
<user.email>.endsWith('@company.com')
|
||||
|
||||
// Array operations
|
||||
<api.tags>.includes('urgent')
|
||||
|
||||
// Mathematical operations
|
||||
<agent.confidence> * 100 > 85
|
||||
|
||||
// Date comparisons
|
||||
new Date(<api.created_at>) > new Date('2024-01-01')
|
||||
```
|
||||
|
||||
### Auswertung mehrerer Bedingungen
|
||||
|
||||
Bedingungen werden der Reihe nach ausgewertet, bis eine übereinstimmt:
|
||||
|
||||
```javascript
|
||||
// Condition 1: Check for high priority
|
||||
<ticket.priority> === 'high'
|
||||
|
||||
// Condition 2: Check for urgent keywords
|
||||
<ticket.subject>.toLowerCase().includes('urgent')
|
||||
|
||||
// Condition 3: Default fallback
|
||||
true
|
||||
```
|
||||
|
||||
### Fehlerbehandlung
|
||||
|
||||
Bedingungen behandeln automatisch:
|
||||
- Undefinierte oder Null-Werte mit sicherer Auswertung
|
||||
- Typabweichungen mit geeigneten Fallbacks
|
||||
- Ungültige Ausdrücke mit Fehlerprotokollierung
|
||||
- Fehlende Variablen mit Standardwerten
|
||||
|
||||
## Ausgaben
|
||||
|
||||
- **`<condition.result>`**: Boolesches Ergebnis der Auswertung
|
||||
- **`<condition.matched_condition>`**: ID der übereinstimmenden Bedingung
|
||||
- **`<condition.content>`**: Beschreibung des Auswertungsergebnisses
|
||||
- **`<condition.path>`**: Details zum gewählten Routing-Ziel
|
||||
|
||||
## Beispielanwendungsfälle
|
||||
|
||||
**Kundenservice-Routing** - Tickets basierend auf Priorität weiterleiten
|
||||
|
||||
```
|
||||
API (Ticket) → Condition (priority === 'high') → Agent (Escalation) or Agent (Standard)
|
||||
```
|
||||
|
||||
**Inhaltsmoderation** - Inhalte basierend auf Analysen filtern
|
||||
|
||||
```
|
||||
Agent (Analyze) → Condition (toxicity > 0.7) → Moderation or Publish
|
||||
```
|
||||
|
||||
**Benutzer-Onboarding-Ablauf** - Onboarding basierend auf Benutzertyp personalisieren
|
||||
|
||||
```
|
||||
Function (Process) → Condition (account_type === 'enterprise') → Advanced or Simple
|
||||
```
|
||||
|
||||
## Bewährte Praktiken
|
||||
|
||||
- **Bedingungen korrekt anordnen**: Platzieren Sie spezifischere Bedingungen vor allgemeinen, um sicherzustellen, dass spezifische Logik Vorrang vor Fallbacks hat
|
||||
- **Verwenden Sie den Else-Zweig bei Bedarf**: Wenn keine Bedingungen übereinstimmen und der Else-Zweig nicht verbunden ist, endet der Workflow-Zweig ordnungsgemäß. Verbinden Sie den Else-Zweig, wenn Sie einen Fallback-Pfad für nicht übereinstimmende Fälle benötigen
|
||||
- **Halten Sie Ausdrücke einfach**: Verwenden Sie klare, unkomplizierte boolesche Ausdrücke für bessere Lesbarkeit und einfachere Fehlersuche
|
||||
- **Dokumentieren Sie Ihre Bedingungen**: Fügen Sie Beschreibungen hinzu, um den Zweck jeder Bedingung für bessere Teamzusammenarbeit und Wartung zu erklären
|
||||
- **Testen Sie Grenzfälle**: Überprüfen Sie, ob Bedingungen Grenzwerte korrekt behandeln, indem Sie mit Werten an den Grenzen Ihrer Bedingungsbereiche testen
|
||||
@@ -1,96 +0,0 @@
|
||||
---
|
||||
title: Evaluator
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
|
||||
import { Image } from '@/components/ui/image'
|
||||
|
||||
Der Evaluator-Block nutzt KI, um die Inhaltsqualität anhand benutzerdefinierter Metriken zu bewerten. Perfekt für Qualitätskontrolle, A/B-Tests und um sicherzustellen, dass KI-Ausgaben bestimmte Standards erfüllen.
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src="/static/blocks/evaluator.png"
|
||||
alt="Evaluator-Block-Konfiguration"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## Konfigurationsoptionen
|
||||
|
||||
### Bewertungsmetriken
|
||||
|
||||
Definieren Sie benutzerdefinierte Metriken, anhand derer Inhalte bewertet werden. Jede Metrik umfasst:
|
||||
|
||||
- **Name**: Eine kurze Bezeichnung für die Metrik
|
||||
- **Beschreibung**: Eine detaillierte Erklärung, was die Metrik misst
|
||||
- **Bereich**: Der numerische Bereich für die Bewertung (z.B. 1-5, 0-10)
|
||||
|
||||
Beispielmetriken:
|
||||
|
||||
```
|
||||
Accuracy (1-5): How factually accurate is the content?
|
||||
Clarity (1-5): How clear and understandable is the content?
|
||||
Relevance (1-5): How relevant is the content to the original query?
|
||||
```
|
||||
|
||||
### Inhalt
|
||||
|
||||
Der zu bewertende Inhalt. Dies kann sein:
|
||||
|
||||
- Direkt in der Blockkonfiguration bereitgestellt
|
||||
- Verbunden mit der Ausgabe eines anderen Blocks (typischerweise ein Agent-Block)
|
||||
- Dynamisch während der Workflow-Ausführung generiert
|
||||
|
||||
### Modellauswahl
|
||||
|
||||
Wählen Sie ein KI-Modell für die Durchführung der Bewertung:
|
||||
|
||||
- **OpenAI**: GPT-4o, o1, o3, o4-mini, gpt-4.1
|
||||
- **Anthropic**: Claude 3.7 Sonnet
|
||||
- **Google**: Gemini 2.5 Pro, Gemini 2.0 Flash
|
||||
- **Andere Anbieter**: Groq, Cerebras, xAI, DeepSeek
|
||||
- **Lokale Modelle**: Ollama oder VLLM-kompatible Modelle
|
||||
|
||||
Verwenden Sie Modelle mit starken Argumentationsfähigkeiten wie GPT-4o oder Claude 3.7 Sonnet für beste Ergebnisse.
|
||||
|
||||
### API-Schlüssel
|
||||
|
||||
Ihr API-Schlüssel für den ausgewählten LLM-Anbieter. Dieser wird sicher gespeichert und für die Authentifizierung verwendet.
|
||||
|
||||
## Beispielanwendungsfälle
|
||||
|
||||
**Bewertung der Inhaltsqualität** - Inhalte vor der Veröffentlichung bewerten
|
||||
|
||||
```
|
||||
Agent (Generate) → Evaluator (Score) → Condition (Check threshold) → Publish or Revise
|
||||
```
|
||||
|
||||
**A/B-Tests von Inhalten** - Vergleich mehrerer KI-generierter Antworten
|
||||
|
||||
```
|
||||
Parallel (Variations) → Evaluator (Score Each) → Function (Select Best) → Response
|
||||
```
|
||||
|
||||
**Qualitätskontrolle im Kundenservice** - Sicherstellen, dass Antworten Qualitätsstandards erfüllen
|
||||
|
||||
```
|
||||
Agent (Support Response) → Evaluator (Score) → Function (Log) → Condition (Review if Low)
|
||||
```
|
||||
|
||||
## Ausgaben
|
||||
|
||||
- **`<evaluator.content>`**: Zusammenfassung der Bewertung mit Punktzahlen
|
||||
- **`<evaluator.model>`**: Für die Bewertung verwendetes Modell
|
||||
- **`<evaluator.tokens>`**: Statistik zur Token-Nutzung
|
||||
- **`<evaluator.cost>`**: Geschätzte Bewertungskosten
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Verwenden Sie spezifische Metrikbeschreibungen**: Definieren Sie klar, was jede Metrik misst, um genauere Bewertungen zu erhalten
|
||||
- **Wählen Sie geeignete Bereiche**: Wählen Sie Bewertungsbereiche, die ausreichend Granularität bieten, ohne zu komplex zu sein
|
||||
- **Verbinden Sie mit Agent-Blöcken**: Verwenden Sie Evaluator-Blöcke, um die Ausgaben von Agent-Blöcken zu bewerten und Feedback-Schleifen zu erstellen
|
||||
- **Verwenden Sie konsistente Metriken**: Für vergleichende Analysen sollten Sie konsistente Metriken über ähnliche Bewertungen hinweg beibehalten
|
||||
- **Kombinieren Sie mehrere Metriken**: Verwenden Sie verschiedene Metriken, um eine umfassende Bewertung zu erhalten
|
||||
@@ -1,76 +0,0 @@
|
||||
---
|
||||
title: Funktion
|
||||
---
|
||||
|
||||
import { Image } from '@/components/ui/image'
|
||||
|
||||
Der Funktionsblock führt benutzerdefinierten JavaScript- oder TypeScript-Code in Ihren Workflows aus. Transformieren Sie Daten, führen Sie Berechnungen durch oder implementieren Sie benutzerdefinierte Logik.
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src="/static/blocks/function.png"
|
||||
alt="Funktionsblock mit Code-Editor"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## Ausgaben
|
||||
|
||||
- **`<function.result>`**: Der von Ihrer Funktion zurückgegebene Wert
|
||||
- **`<function.stdout>`**: Console.log()-Ausgabe Ihres Codes
|
||||
|
||||
## Beispielanwendungsfälle
|
||||
|
||||
**Datenverarbeitungspipeline** - Transformation von API-Antworten in strukturierte Daten
|
||||
|
||||
```
|
||||
API (Fetch) → Function (Process & Validate) → Function (Calculate Metrics) → Response
|
||||
```
|
||||
|
||||
**Implementierung von Geschäftslogik** - Berechnung von Treuepunkten und Stufen
|
||||
|
||||
```
|
||||
Agent (Get History) → Function (Calculate Score) → Function (Determine Tier) → Condition (Route)
|
||||
```
|
||||
|
||||
**Datenvalidierung und -bereinigung** - Validierung und Bereinigung von Benutzereingaben
|
||||
|
||||
```
|
||||
Input → Function (Validate & Sanitize) → API (Save to Database)
|
||||
```
|
||||
|
||||
### Beispiel: Treuepunkte-Rechner
|
||||
|
||||
```javascript title="loyalty-calculator.js"
|
||||
// Process customer data and calculate loyalty score
|
||||
const { purchaseHistory, accountAge, supportTickets } = <agent>;
|
||||
|
||||
// Calculate metrics
|
||||
const totalSpent = purchaseHistory.reduce((sum, purchase) => sum + purchase.amount, 0);
|
||||
const purchaseFrequency = purchaseHistory.length / (accountAge / 365);
|
||||
const ticketRatio = supportTickets.resolved / supportTickets.total;
|
||||
|
||||
// Calculate loyalty score (0-100)
|
||||
const spendScore = Math.min(totalSpent / 1000 * 30, 30);
|
||||
const frequencyScore = Math.min(purchaseFrequency * 20, 40);
|
||||
const supportScore = ticketRatio * 30;
|
||||
|
||||
const loyaltyScore = Math.round(spendScore + frequencyScore + supportScore);
|
||||
|
||||
return {
|
||||
customer: <agent.name>,
|
||||
loyaltyScore,
|
||||
loyaltyTier: loyaltyScore >= 80 ? "Platinum" : loyaltyScore >= 60 ? "Gold" : "Silver",
|
||||
metrics: { spendScore, frequencyScore, supportScore }
|
||||
};
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Funktionen fokussiert halten**: Schreiben Sie Funktionen, die eine Sache gut erledigen, um die Wartbarkeit und Fehlersuche zu verbessern
|
||||
- **Fehler elegant behandeln**: Verwenden Sie try/catch-Blöcke, um potenzielle Fehler zu behandeln und aussagekräftige Fehlermeldungen bereitzustellen
|
||||
- **Grenzfälle testen**: Stellen Sie sicher, dass Ihr Code ungewöhnliche Eingaben, Null-Werte und Grenzbedingungen korrekt behandelt
|
||||
- **Für Leistung optimieren**: Achten Sie bei großen Datensätzen auf die Berechnungskomplexität und den Speicherverbrauch
|
||||
- **Console.log() zum Debuggen verwenden**: Nutzen Sie die Stdout-Ausgabe zum Debuggen und Überwachen der Funktionsausführung
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user