mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-08 22:48:14 -05:00
feat/platform-v3 (#1698)
* feat(fonts): season replacing geist * feat(emcnn): created emcn * feat(sidebar): created new sidebar with header and workflow list * improvement(sidebar): expanded workflow/folder item text sizing and adjusted button padding * feat(sidebar): added search UI, updated workflows styling * improvement: globals styling with antialiased in dark mode only * feat(sidebar): blocks and triggers ui/ux updated * refactor(sidebar): moved logic into hooks * feat(sidebar): improved workflow/folder dragging UI/UX; refactored logic into hooks * improvement(sidebar): adjusted triggers/blocks padding for header * improvement(sidebar): dragging hover handler; closed folders by default minus active path * improvement(sidebar): panel resize logic * improvement(sidebar): blocks and triggers expanded indicator * feat(tooltips): new emcn component emerged * feat(sidebar): workflow list handling updated * refactor: added cursorrules * feat(panel): new panel layout * improvement(workspaces): firname's workspace instead of fn ln's workspace * feat(platform): panel header, new emcn icons, more button variants, refactor sidebar components * improvement(emcn): added button variants * feat(panel): tab system * feat(copilot): refactor, adjusted welcome and user-input UI/UX * feat(copilot): baseline user-input ui/ux improvement * feat(emcn): badge outline variant * fix: build errors * feat(copilot): base UI copilot * refactor(workflow-block): added hooks, components * feat(design): created design panel and removed isWide * refactor(subblock): edited components, styling * feat: emcn, editor * feat(panel): toolbar, editor * feat(workflow-block): refactor, adjust base styling * feat(workflow-block): new block, edge * feat: workflow-block, connections, action-bar, copilot * feat: panel, workflow, emcn, workflow block, subblocks; clean copilot * sim-326: remove remote code execution toggle, hide dropdown for language if E2B is not enabled * feat: sidebar navigation, tag coloring; refactor: rebased to staging * fix: build errors * improvement: subblock styles * feat: workspaces, terminal, emcn, controls * feat: delete workflow * fix: rebased * fix build errors --------- Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
This commit is contained in:
19
.cursorrules
Normal file
19
.cursorrules
Normal file
@@ -0,0 +1,19 @@
|
||||
# Role
|
||||
|
||||
You are a professional software engineer. All code you write MUST follow best practices, ensuring accuracy, quality, readability, and cleanliness. You MUST make FOCUSED EDITS that are EFFICIENT and ELEGANT.
|
||||
|
||||
## Logs
|
||||
|
||||
ENSURE that you use the logger.info and logger.warn and logger.error instead of the console.log whenever you want to display logs.
|
||||
|
||||
## Comments
|
||||
|
||||
You must use TSDOC for comments. Do not use ==== for comments to separate sections.
|
||||
|
||||
## Globals styles
|
||||
|
||||
You should not update the global styles unless it is absolutely necessary. Keep all styling local to components and files.
|
||||
|
||||
## Bun
|
||||
|
||||
Use bun and bunx not npm and npx
|
||||
6
.github/CONTRIBUTING.md
vendored
6
.github/CONTRIBUTING.md
vendored
@@ -321,8 +321,7 @@ In addition, you will need to update the registries:
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
type: 'dropdown'
|
||||
required: true,
|
||||
options: [
|
||||
{ label: 'Generate Embeddings', id: 'generate' },
|
||||
@@ -333,8 +332,7 @@ In addition, you will need to update the registries:
|
||||
{
|
||||
id: 'apiKey',
|
||||
title: 'API Key',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
type: 'short-input'
|
||||
placeholder: 'Your Pinecone API key',
|
||||
password: true,
|
||||
required: true,
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -46,7 +46,7 @@ sim-standalone.tar.gz
|
||||
next-env.d.ts
|
||||
|
||||
# cursorrules
|
||||
.cursorrules
|
||||
# .cursorrules
|
||||
|
||||
# docs
|
||||
/apps/docs/.source
|
||||
|
||||
735
apps/sim/.cursorrules
Normal file
735
apps/sim/.cursorrules
Normal file
@@ -0,0 +1,735 @@
|
||||
# Sim App Architecture Guidelines
|
||||
|
||||
You are building features in the Sim app following the architecture established by the sidebar-new component. This file defines the patterns, structures, and conventions you must follow.
|
||||
|
||||
---
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Single Responsibility Principle**: Each component, hook, and store should have one clear purpose
|
||||
2. **Composition Over Complexity**: Break down complex logic into smaller, composable pieces
|
||||
3. **Type Safety First**: Use TypeScript interfaces for all props, state, and return types
|
||||
4. **Predictable State Management**: Use Zustand for global state, local state for UI-only concerns
|
||||
5. **Performance by Default**: Use useMemo, useCallback, and refs appropriately
|
||||
6. **Accessibility**: Include semantic HTML and ARIA attributes
|
||||
7. **Documentation**: Use TSDoc for all public interfaces and keep it in sync with code changes
|
||||
|
||||
---
|
||||
|
||||
## File Organization
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
feature/
|
||||
├── components/ # Feature components
|
||||
│ ├── sub-feature/ # Sub-feature with its own components
|
||||
│ │ ├── component.tsx
|
||||
│ │ └── index.ts
|
||||
├── hooks/ # Custom hooks for feature logic
|
||||
│ ├── use-feature-logic.ts
|
||||
│ └── use-another-hook.ts
|
||||
└── feature.tsx # Main feature component
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
- **Components**: PascalCase with descriptive names (`WorkflowList`, `TriggerPanel`)
|
||||
- **Hooks**: camelCase with `use` prefix (`useWorkflowOperations`, `usePanelResize`)
|
||||
- **Files**: kebab-case matching export name (`workflow-list.tsx`, `use-panel-resize.ts`)
|
||||
- **Stores**: kebab-case in stores/ directory (`sidebar/store.ts`, `workflows/registry/store.ts`)
|
||||
- **Constants**: SCREAMING_SNAKE_CASE at module level
|
||||
- **Interfaces**: PascalCase with descriptive suffix (`WorkflowListProps`, `UseWorkspaceManagementProps`)
|
||||
|
||||
---
|
||||
|
||||
## Component Architecture
|
||||
|
||||
### Component Structure Template
|
||||
|
||||
```typescript
|
||||
'use client' // Only if using hooks like useState, useEffect, etc.
|
||||
|
||||
import { useCallback, useMemo, useRef, useState } from 'react'
|
||||
// Other imports organized: external, internal paths, relative
|
||||
|
||||
/**
|
||||
* Constants - Define at module level before component
|
||||
*/
|
||||
const DEFAULT_VALUE = 100
|
||||
const MIN_VALUE = 50
|
||||
const MAX_VALUE = 200
|
||||
|
||||
const CONFIG = {
|
||||
SPACING: 8,
|
||||
ITEM_HEIGHT: 25,
|
||||
} as const
|
||||
|
||||
interface ComponentProps {
|
||||
/** Description of prop */
|
||||
requiredProp: string
|
||||
/** Description with default noted */
|
||||
optionalProp?: boolean
|
||||
onAction?: (id: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Component description explaining purpose and key features.
|
||||
* Mention important integrations, hooks, or patterns used.
|
||||
*
|
||||
* @param props - Component props
|
||||
* @returns JSX description
|
||||
*/
|
||||
export function ComponentName({
|
||||
requiredProp,
|
||||
optionalProp = false,
|
||||
onAction,
|
||||
}: ComponentProps) {
|
||||
// 1. Refs first
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// 2. External hooks (router, params, context)
|
||||
const params = useParams()
|
||||
|
||||
// 3. Store hooks
|
||||
const { state, actions } = useStore()
|
||||
|
||||
// 4. Custom hooks (your feature hooks)
|
||||
const { data, isLoading } = useCustomHook({ requiredProp })
|
||||
|
||||
// 5. Local state (UI-only concerns)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
// 6. Derived/computed values with useMemo
|
||||
const filteredData = useMemo(() => {
|
||||
return data.filter(item => item.active)
|
||||
}, [data])
|
||||
|
||||
// 7. Callbacks with useCallback
|
||||
const handleClick = useCallback((id: string) => {
|
||||
onAction?.(id)
|
||||
}, [onAction])
|
||||
|
||||
// 8. Effects
|
||||
useEffect(() => {
|
||||
// Setup logic
|
||||
return () => {
|
||||
// Cleanup logic
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 9. Render helpers (if complex)
|
||||
const renderItem = useCallback((item: Item) => (
|
||||
<div key={item.id}>{item.name}</div>
|
||||
), [])
|
||||
|
||||
// 10. Return JSX
|
||||
return (
|
||||
<div ref={containerRef} className='...' aria-label='...'>
|
||||
{/* Section comments for clarity */}
|
||||
{/* Header */}
|
||||
<header>...</header>
|
||||
|
||||
{/* Content */}
|
||||
<main>...</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Component Rules
|
||||
|
||||
1. **Client Components**: Add `'use client'` directive when using React hooks
|
||||
2. **Props Interface**: Always define TypeScript interface, even for simple props
|
||||
3. **TSDoc Required and Up-to-Date**: Include description, @param, and @returns. Update TSDoc whenever props, behavior, or side effects change (including additions and deletions).
|
||||
4. **Constants**: Extract magic numbers and config to module-level constants using `as const`
|
||||
5. **No Inline Styles**: Use Tailwind classes exclusively (CSS variables for dynamic values)
|
||||
6. **Section Comments**: Use comments to mark logical sections of JSX
|
||||
7. **Semantic HTML**: Use appropriate elements (`aside`, `nav`, `article`, etc.)
|
||||
8. **ARIA Attributes**: Include `aria-label`, `aria-orientation`, `role` where appropriate
|
||||
9. **Refs for DOM**: Use refs for direct DOM access, not state
|
||||
10. **Callback Props**: Always use optional chaining for callback props (`onAction?.(...)`)
|
||||
|
||||
---
|
||||
|
||||
## Custom Hooks Architecture
|
||||
|
||||
### Hook Structure Template
|
||||
|
||||
```typescript
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('useFeatureName')
|
||||
|
||||
/**
|
||||
* Constants specific to this hook
|
||||
*/
|
||||
const DEFAULT_CONFIG = {
|
||||
timeout: 1000,
|
||||
retries: 3,
|
||||
} as const
|
||||
|
||||
interface UseFeatureNameProps {
|
||||
/** Description of required prop */
|
||||
id: string
|
||||
/** Optional callback fired on success */
|
||||
onSuccess?: (result: Result) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom hook to [clear description of purpose].
|
||||
* [Additional context about what it manages or coordinates].
|
||||
*
|
||||
* @param props - Configuration object containing id and callbacks
|
||||
* @returns Feature state and operations
|
||||
*/
|
||||
export function useFeatureName({ id, onSuccess }: UseFeatureNameProps) {
|
||||
// 1. Refs (to avoid dependency issues)
|
||||
const idRef = useRef(id)
|
||||
const onSuccessRef = useRef(onSuccess)
|
||||
|
||||
// 2. State
|
||||
const [data, setData] = useState<Data | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
// 3. Update refs when values change
|
||||
useEffect(() => {
|
||||
idRef.current = id
|
||||
onSuccessRef.current = onSuccess
|
||||
}, [id, onSuccess])
|
||||
|
||||
// 4. Operations with useCallback (stable references)
|
||||
const fetchData = useCallback(async () => {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const response = await fetch(`/api/data/${idRef.current}`)
|
||||
const result = await response.json()
|
||||
setData(result)
|
||||
onSuccessRef.current?.(result)
|
||||
logger.info('Data fetched successfully', { id: idRef.current })
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
setError(error)
|
||||
logger.error('Failed to fetch data', { error })
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, []) // Empty deps because using refs
|
||||
|
||||
const updateData = useCallback(async (newData: Partial<Data>) => {
|
||||
try {
|
||||
logger.info('Updating data', { id: idRef.current, newData })
|
||||
const response = await fetch(`/api/data/${idRef.current}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(newData),
|
||||
})
|
||||
const result = await response.json()
|
||||
setData(result)
|
||||
return true
|
||||
} catch (err) {
|
||||
logger.error('Failed to update data', { error: err })
|
||||
return false
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 5. Effects
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchData()
|
||||
}
|
||||
}, [id, fetchData])
|
||||
|
||||
// 6. Return object - group by state and operations
|
||||
return {
|
||||
// State
|
||||
data,
|
||||
isLoading,
|
||||
error,
|
||||
|
||||
// Operations
|
||||
fetchData,
|
||||
updateData,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Hook Rules
|
||||
|
||||
1. **Single Responsibility**: Each hook manages one concern (data fetching, resize, navigation)
|
||||
2. **Props Interface**: Define TypeScript interface for all parameters
|
||||
3. **TSDoc Required and Up-to-Date**: Include clear description, @param, and @returns. Update TSDoc whenever inputs, outputs, behavior, or side effects change (including additions and deletions).
|
||||
4. **Logger Usage**: Import and use logger instead of console.log
|
||||
5. **Refs for Stable Deps**: Use refs to avoid recreating callbacks unnecessarily
|
||||
6. **useCallback Always**: Wrap all returned functions in useCallback
|
||||
7. **Grouped Returns**: Return object with comments separating State and Operations
|
||||
8. **Error Handling**: Always try/catch async operations and log errors
|
||||
9. **Loading States**: Track loading, error states for async operations
|
||||
10. **Dependency Arrays**: Be explicit and correct with all dependency arrays
|
||||
|
||||
---
|
||||
|
||||
## Store Architecture (Zustand)
|
||||
|
||||
### Store Structure Template
|
||||
|
||||
```typescript
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
/**
|
||||
* Store state interface
|
||||
*/
|
||||
interface FeatureState {
|
||||
// State properties
|
||||
items: Item[]
|
||||
activeId: string | null
|
||||
isLoading: boolean
|
||||
|
||||
// Actions
|
||||
setItems: (items: Item[]) => void
|
||||
setActiveId: (id: string | null) => void
|
||||
addItem: (item: Item) => void
|
||||
removeItem: (id: string) => void
|
||||
clearState: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Constants - Configuration values
|
||||
*/
|
||||
const DEFAULT_CONFIG = {
|
||||
maxItems: 100,
|
||||
cacheTime: 3600,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Initial state factory
|
||||
*/
|
||||
const createInitialState = () => ({
|
||||
items: [],
|
||||
activeId: null,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
/**
|
||||
* Feature store managing [description].
|
||||
* [Additional context about what this store coordinates].
|
||||
*/
|
||||
export const useFeatureStore = create<FeatureState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
...createInitialState(),
|
||||
|
||||
setItems: (items) => set({ items }),
|
||||
|
||||
setActiveId: (id) => set({ activeId: id }),
|
||||
|
||||
addItem: (item) =>
|
||||
set((state) => ({
|
||||
items: [...state.items, item].slice(-DEFAULT_CONFIG.maxItems),
|
||||
})),
|
||||
|
||||
removeItem: (id) =>
|
||||
set((state) => ({
|
||||
items: state.items.filter((item) => item.id !== id),
|
||||
activeId: state.activeId === id ? null : state.activeId,
|
||||
})),
|
||||
|
||||
clearState: () => set(createInitialState()),
|
||||
}),
|
||||
{
|
||||
name: 'feature-state',
|
||||
// Optionally customize what to persist
|
||||
partialize: (state) => ({
|
||||
items: state.items,
|
||||
activeId: state.activeId,
|
||||
}),
|
||||
onRehydrateStorage: () => (state) => {
|
||||
// Validate and transform persisted state if needed
|
||||
if (state) {
|
||||
// Enforce constraints
|
||||
if (state.items.length > DEFAULT_CONFIG.maxItems) {
|
||||
state.items = state.items.slice(-DEFAULT_CONFIG.maxItems)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Store Rules
|
||||
|
||||
1. **Interface First**: Define TypeScript interface including both state and actions
|
||||
2. **Constants**: Extract configuration values to module-level constants
|
||||
3. **TSDoc on Store**: Document what the store manages
|
||||
4. **Persist Strategically**: Only persist what's needed across sessions
|
||||
5. **Validation**: Use onRehydrateStorage to validate persisted state
|
||||
6. **Immutable Updates**: Use set() with new objects/arrays, never mutate
|
||||
7. **Derived State**: Use getters or selectors, not stored computed values
|
||||
8. **CSS Variables**: Update CSS variables in setters for hydration-safe dynamic styles
|
||||
9. **Cleanup Actions**: Provide clear/reset actions for state cleanup
|
||||
10. **Functional Updates**: Use `set((state) => ...)` when new state depends on old state
|
||||
|
||||
---
|
||||
|
||||
## State Management Strategy
|
||||
|
||||
### When to Use Local State (useState)
|
||||
|
||||
- UI-only concerns (dropdown open, hover states, form inputs)
|
||||
- Component-scoped state not needed elsewhere
|
||||
- Temporary state that doesn't need persistence
|
||||
|
||||
### When to Use Zustand Store
|
||||
|
||||
- Shared state across multiple components
|
||||
- State that needs persistence (localStorage)
|
||||
- Global application state (user, theme, settings)
|
||||
- Complex state with many actions/reducers
|
||||
|
||||
### When to Use Refs (useRef)
|
||||
|
||||
- DOM element references
|
||||
- Avoiding dependency issues in hooks
|
||||
- Storing mutable values that don't trigger re-renders
|
||||
- Accessing latest props/state in callbacks without recreating them
|
||||
|
||||
---
|
||||
|
||||
## CSS and Styling
|
||||
|
||||
### CSS Variables for Dynamic Styles
|
||||
|
||||
Use CSS variables for values that need to persist across hydration:
|
||||
|
||||
```typescript
|
||||
// In store setter
|
||||
setSidebarWidth: (width) => {
|
||||
const clampedWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, width))
|
||||
set({ sidebarWidth: clampedWidth })
|
||||
|
||||
// Update CSS variable for immediate visual feedback
|
||||
if (typeof window !== 'undefined') {
|
||||
document.documentElement.style.setProperty('--sidebar-width', `${clampedWidth}px`)
|
||||
}
|
||||
}
|
||||
|
||||
// In component
|
||||
<aside
|
||||
className='sidebar-container'
|
||||
style={{ width: 'var(--sidebar-width)' }}
|
||||
>
|
||||
```
|
||||
|
||||
### Tailwind Classes
|
||||
|
||||
1. **No Inline Styles**: Use Tailwind utility classes exclusively
|
||||
2. **Dark Mode**: Always include dark mode variants (`dark:bg-[#1E1E1E]`)
|
||||
3. **Exact Values**: Use exact values from design system (`text-[14px]`, `h-[25px]`)
|
||||
4. **clsx for Conditionals**: Use clsx() for conditional classes
|
||||
5. **Consistent Spacing**: Use spacing tokens (`gap-[8px]`, `px-[14px]`)
|
||||
6. **Transitions**: Add transitions for interactive states (`transition-colors`)
|
||||
7. **Prefer px units**: Use arbitrary px values over scale utilities (e.g., `px-[4px]` instead of `px-1`)
|
||||
|
||||
```typescript
|
||||
<div
|
||||
className={clsx(
|
||||
'base-classes that-always-apply',
|
||||
isActive && 'active-state-classes',
|
||||
disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:bg-accent',
|
||||
'dark:bg-[#1E1E1E] dark:border-[#2C2C2C]' // Dark mode variants
|
||||
)}
|
||||
>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TypeScript Patterns
|
||||
|
||||
### Interface Conventions
|
||||
|
||||
```typescript
|
||||
// Component props
|
||||
interface ComponentNameProps {
|
||||
requiredProp: string
|
||||
optionalProp?: boolean
|
||||
}
|
||||
|
||||
// Hook props
|
||||
interface UseHookNameProps {
|
||||
id: string
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
// Store state
|
||||
interface FeatureState {
|
||||
data: Data[]
|
||||
isLoading: boolean
|
||||
actions: () => void
|
||||
}
|
||||
|
||||
// Return types (if complex)
|
||||
interface UseHookNameReturn {
|
||||
state: State
|
||||
actions: Actions
|
||||
}
|
||||
```
|
||||
|
||||
### Type Safety Rules
|
||||
|
||||
1. **No `any`**: Use proper types or `unknown` with type guards
|
||||
2. **Props Interface**: Always define, even for simple components
|
||||
3. **Callback Types**: Define full signature including parameters and return type
|
||||
4. **Generic Types**: Use generics for reusable components/hooks
|
||||
5. **Const Assertions**: Use `as const` for constant objects/arrays
|
||||
6. **Type Guards**: Create type guards for runtime checks
|
||||
7. **Ref Types**: Explicitly type refs (`useRef<HTMLDivElement>(null)`)
|
||||
|
||||
---
|
||||
|
||||
## Performance Patterns
|
||||
|
||||
### Memoization
|
||||
|
||||
```typescript
|
||||
// useMemo for expensive computations
|
||||
const sortedItems = useMemo(() => {
|
||||
return items.sort((a, b) => a.name.localeCompare(b.name))
|
||||
}, [items])
|
||||
|
||||
// useCallback for functions passed as props
|
||||
const handleClick = useCallback((id: string) => {
|
||||
onItemClick?.(id)
|
||||
}, [onItemClick])
|
||||
|
||||
// useCallback for render functions
|
||||
const renderItem = useCallback((item: Item) => (
|
||||
<ItemComponent key={item.id} item={item} onClick={handleClick} />
|
||||
), [handleClick])
|
||||
```
|
||||
|
||||
### When to Memoize
|
||||
|
||||
1. **useMemo**: Expensive calculations, filtering/sorting large arrays, object creation in render
|
||||
2. **useCallback**: Functions passed to child components, dependencies in other hooks, event handlers used in effects
|
||||
3. **Don't Over-Memoize**: Simple calculations, primitives, or functions not passed down
|
||||
|
||||
### Refs for Avoiding Recreations
|
||||
|
||||
```typescript
|
||||
// Pattern: Use refs to avoid function recreations
|
||||
const onSuccessRef = useRef(onSuccess)
|
||||
|
||||
useEffect(() => {
|
||||
onSuccessRef.current = onSuccess
|
||||
}, [onSuccess])
|
||||
|
||||
const stableCallback = useCallback(() => {
|
||||
// Use ref so this callback never needs to change
|
||||
onSuccessRef.current?.()
|
||||
}, []) // Empty deps!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logging and Debugging
|
||||
|
||||
### Logger Usage
|
||||
|
||||
```typescript
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('ComponentName')
|
||||
|
||||
// Use throughout component/hook
|
||||
logger.info('User action', { userId, action })
|
||||
logger.warn('Potential issue', { details })
|
||||
logger.error('Operation failed', { error })
|
||||
```
|
||||
|
||||
### Logging Rules
|
||||
|
||||
1. **No console.log**: Use logger.info/warn/error instead
|
||||
2. **Logger Per File**: Create logger with component/hook name
|
||||
3. **Structured Logging**: Pass objects with context, not just strings
|
||||
4. **Log Levels**:
|
||||
- `info`: Normal operations, user actions, state changes
|
||||
- `warn`: Unusual but handled situations, deprecations
|
||||
- `error`: Failures, exceptions, errors that need attention
|
||||
|
||||
---
|
||||
|
||||
## Code Quality Checklist
|
||||
|
||||
Before considering a component/hook complete, verify:
|
||||
|
||||
### Documentation
|
||||
- [ ] TSDoc in sync with implementation after any change (params/returns/behavior/throws)
|
||||
- [ ] TSDoc comment on component/hook/store
|
||||
- [ ] Props interface documented with /** */ comments
|
||||
- [ ] Complex logic explained with inline comments
|
||||
- [ ] Section comments in JSX for clarity
|
||||
|
||||
### TypeScript
|
||||
- [ ] All props have interface defined
|
||||
- [ ] No `any` types used
|
||||
- [ ] Refs properly typed
|
||||
- [ ] Return types explicit for complex hooks
|
||||
|
||||
### Performance
|
||||
- [ ] useMemo for expensive computations
|
||||
- [ ] useCallback for functions passed as props
|
||||
- [ ] Refs used to avoid unnecessary recreations
|
||||
- [ ] No unnecessary re-renders
|
||||
|
||||
### Hooks
|
||||
- [ ] Correct dependency arrays
|
||||
- [ ] Cleanup in useEffect return functions
|
||||
- [ ] Stable callback references with useCallback
|
||||
- [ ] Logic extracted to custom hooks when reusable
|
||||
|
||||
### Styling
|
||||
- [ ] No styles attributes (use className with Tailwind)
|
||||
- [ ] Dark mode variants included
|
||||
- [ ] Consistent spacing using design tokens
|
||||
- [ ] clsx for conditional classes
|
||||
|
||||
### Accessibility
|
||||
- [ ] Semantic HTML elements
|
||||
- [ ] ARIA labels and roles where needed
|
||||
- [ ] Keyboard navigation support
|
||||
- [ ] Focus management
|
||||
|
||||
### State Management
|
||||
- [ ] Local state for UI-only concerns
|
||||
- [ ] Zustand for shared/persisted state
|
||||
- [ ] No duplicate state
|
||||
- [ ] Clear state update patterns
|
||||
|
||||
### Error Handling
|
||||
- [ ] try/catch around async operations
|
||||
- [ ] Error states tracked and displayed
|
||||
- [ ] Loading states for async actions
|
||||
- [ ] Failures logged with context
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### ❌ Don't Do This
|
||||
|
||||
```typescript
|
||||
// ❌ Inline styles
|
||||
<div style={{ width: 200, marginTop: 10 }}>
|
||||
|
||||
// ❌ console.log
|
||||
console.log('Debug info')
|
||||
|
||||
// ❌ any type
|
||||
const handleClick = (e: any) => {}
|
||||
|
||||
// ❌ Missing dependencies
|
||||
useEffect(() => {
|
||||
doSomething(prop)
|
||||
}, []) // Missing prop!
|
||||
|
||||
// ❌ Mutating state
|
||||
const handleAdd = () => {
|
||||
items.push(newItem) // Mutating!
|
||||
setItems(items)
|
||||
}
|
||||
|
||||
// ❌ No error handling
|
||||
const fetchData = async () => {
|
||||
const data = await fetch('/api/data')
|
||||
setData(data)
|
||||
}
|
||||
|
||||
// ❌ Complex logic in component
|
||||
export function Component() {
|
||||
const [data, setData] = useState([])
|
||||
useEffect(() => {
|
||||
// 50 lines of complex logic
|
||||
}, [])
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Do This Instead
|
||||
|
||||
```typescript
|
||||
// ✅ Tailwind classes
|
||||
<div className='w-[200px] mt-[10px]'>
|
||||
|
||||
// ✅ Logger
|
||||
logger.info('Debug info', { context })
|
||||
|
||||
// ✅ Proper types
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {}
|
||||
|
||||
// ✅ Complete dependencies
|
||||
useEffect(() => {
|
||||
doSomething(prop)
|
||||
}, [prop])
|
||||
|
||||
// ✅ Immutable updates
|
||||
const handleAdd = () => {
|
||||
setItems([...items, newItem])
|
||||
}
|
||||
|
||||
// ✅ Error handling
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/data')
|
||||
if (!response.ok) throw new Error('Failed to fetch')
|
||||
const data = await response.json()
|
||||
setData(data)
|
||||
} catch (error) {
|
||||
logger.error('Fetch failed', { error })
|
||||
setError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Extract to custom hook
|
||||
export function Component() {
|
||||
const { data, isLoading, error } = useFeatureData()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples from Codebase
|
||||
|
||||
Study these files as reference implementations:
|
||||
|
||||
### Components
|
||||
- `sidebar-new.tsx` - Main component structure, hook composition
|
||||
- `workflow-list.tsx` - Complex component with drag-drop, memoization
|
||||
- `blocks.tsx` - Simple panel component with resize
|
||||
- `triggers.tsx` - Similar panel pattern
|
||||
|
||||
### Hooks
|
||||
- `use-workspace-management.ts` - Complex hook with multiple operations, refs pattern
|
||||
- `use-sidebar-resize.ts` - Simple focused hook with event listeners
|
||||
- `use-workflow-operations.ts` - Hook coordinating store and navigation
|
||||
- `use-panel-resize.ts` - Shared resize logic pattern
|
||||
|
||||
### Stores
|
||||
- `stores/sidebar/store.ts` - Persist middleware, CSS variables, validation
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This architecture prioritizes:
|
||||
|
||||
1. **Separation of Concerns**: Components render, hooks contain logic, stores manage state
|
||||
2. **Type Safety**: TypeScript everywhere with no escape hatches
|
||||
3. **Performance**: Smart use of memoization and refs
|
||||
4. **Maintainability**: Clear structure, documentation, and consistent patterns
|
||||
5. **Developer Experience**: Logging, error handling, and clear interfaces
|
||||
|
||||
When in doubt, follow the patterns established in the sidebar-new component family.
|
||||
3
apps/sim/.gitignore
vendored
3
apps/sim/.gitignore
vendored
@@ -42,9 +42,6 @@ sim-standalone.tar.gz
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# cursorrules
|
||||
.cursorrules
|
||||
|
||||
# Uploads
|
||||
/uploads
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { type ReactNode, useEffect, useState } from 'react'
|
||||
import { GithubIcon, GoogleIcon } from '@/components/icons'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { client } from '@/lib/auth-client'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
|
||||
interface SocialLoginButtonsProps {
|
||||
githubAvailable: boolean
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function AuthLayout({ children }: { children: React.ReactNode })
|
||||
}, [])
|
||||
return (
|
||||
<AuthBackground>
|
||||
<main className='relative flex min-h-screen flex-col font-geist-sans text-foreground'>
|
||||
<main className='relative flex min-h-screen flex-col text-foreground'>
|
||||
{/* Header - Nav handles all conditional logic */}
|
||||
<Nav hideAuthButtons={true} variant='auth' />
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import { getBaseUrl } from '@/lib/urls/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SocialLoginButtons } from '@/app/(auth)/components/social-login-buttons'
|
||||
import { SSOLoginButton } from '@/app/(auth)/components/sso-login-button'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
|
||||
const logger = createLogger('LoginForm')
|
||||
|
||||
@@ -5,7 +5,7 @@ import Link from 'next/link'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { SetNewPasswordForm } from '@/app/(auth)/reset-password/reset-password-form'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
|
||||
const logger = createLogger('ResetPasswordPage')
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
|
||||
interface RequestResetFormProps {
|
||||
email: string
|
||||
|
||||
@@ -14,7 +14,7 @@ import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SocialLoginButtons } from '@/app/(auth)/components/social-login-buttons'
|
||||
import { SSOLoginButton } from '@/app/(auth)/components/sso-login-button'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
|
||||
const logger = createLogger('SignupForm')
|
||||
|
||||
@@ -11,7 +11,7 @@ import { quickValidateEmail } from '@/lib/email/validation'
|
||||
import { env, isFalsy } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
|
||||
const logger = createLogger('SSOForm')
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useVerification } from '@/app/(auth)/verify/use-verification'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
|
||||
interface VerifyContentProps {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
LinkedInIcon,
|
||||
xIcon as XIcon,
|
||||
} from '@/components/icons'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
|
||||
const blocks = [
|
||||
'Agent',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as Icons from '@/components/icons'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
|
||||
// AI models and providers
|
||||
const modelProviderIcons = [
|
||||
|
||||
@@ -14,12 +14,12 @@ import {
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import {
|
||||
ENTERPRISE_PLAN_FEATURES,
|
||||
PRO_PLAN_FEATURES,
|
||||
TEAM_PLAN_FEATURES,
|
||||
} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/plan-configs'
|
||||
} from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/subscription/plan-configs'
|
||||
|
||||
const logger = createLogger('LandingPricing')
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
|
||||
interface LandingTemplatePreviewProps {
|
||||
previewImage: string
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import LandingTemplatePreview from './components/landing-template-preview'
|
||||
|
||||
// Mock data for templates
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import Image from 'next/image'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
|
||||
interface Testimonial {
|
||||
text: string
|
||||
|
||||
@@ -97,7 +97,6 @@ export const sampleWorkflowState = {
|
||||
},
|
||||
enabled: true,
|
||||
horizontalHandles: true,
|
||||
isWide: false,
|
||||
advancedMode: false,
|
||||
triggerMode: false,
|
||||
height: 95,
|
||||
@@ -126,7 +125,6 @@ export const sampleWorkflowState = {
|
||||
},
|
||||
enabled: true,
|
||||
horizontalHandles: true,
|
||||
isWide: false,
|
||||
advancedMode: false,
|
||||
triggerMode: false,
|
||||
height: 680,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* @deprecated This route is not currently in use
|
||||
* @remarks Kept for reference - may be removed in future cleanup
|
||||
*/
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import { copilotChats } from '@sim/db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
|
||||
@@ -52,7 +52,6 @@ describe('Function Execute API Route', () => {
|
||||
it.concurrent('should create secure fetch in VM context', async () => {
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return "test"',
|
||||
useLocalVM: true,
|
||||
})
|
||||
|
||||
const { POST } = await import('@/app/api/function/execute/route')
|
||||
@@ -97,7 +96,6 @@ describe('Function Execute API Route', () => {
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return "Hello World"',
|
||||
timeout: 5000,
|
||||
useLocalVM: true,
|
||||
})
|
||||
|
||||
const { POST } = await import('@/app/api/function/execute/route')
|
||||
@@ -127,19 +125,14 @@ describe('Function Execute API Route', () => {
|
||||
it.concurrent('should use default timeout when not provided', async () => {
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return "test"',
|
||||
useLocalVM: true,
|
||||
})
|
||||
|
||||
const { POST } = await import('@/app/api/function/execute/route')
|
||||
const response = await POST(req)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(mockLogger.info).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/\[.*\] Function execution request/),
|
||||
expect.objectContaining({
|
||||
timeout: 5000, // default timeout
|
||||
})
|
||||
)
|
||||
// The logger now logs execution success, not the request details
|
||||
expect(mockLogger.info).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -147,7 +140,6 @@ describe('Function Execute API Route', () => {
|
||||
it.concurrent('should resolve environment variables with {{var_name}} syntax', async () => {
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return {{API_KEY}}',
|
||||
useLocalVM: true,
|
||||
envVars: {
|
||||
API_KEY: 'secret-key-123',
|
||||
},
|
||||
@@ -163,7 +155,6 @@ describe('Function Execute API Route', () => {
|
||||
it.concurrent('should resolve tag variables with <tag_name> syntax', async () => {
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return <email>',
|
||||
useLocalVM: true,
|
||||
params: {
|
||||
email: { id: '123', subject: 'Test Email' },
|
||||
},
|
||||
@@ -179,7 +170,6 @@ describe('Function Execute API Route', () => {
|
||||
it.concurrent('should NOT treat email addresses as template variables', async () => {
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return "Email sent to user"',
|
||||
useLocalVM: true,
|
||||
params: {
|
||||
email: {
|
||||
from: 'Waleed Latif <waleed@sim.ai>',
|
||||
@@ -198,7 +188,6 @@ describe('Function Execute API Route', () => {
|
||||
it.concurrent('should only match valid variable names in angle brackets', async () => {
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return <validVar> + "<invalid@email.com>" + <another_valid>',
|
||||
useLocalVM: true,
|
||||
params: {
|
||||
validVar: 'hello',
|
||||
another_valid: 'world',
|
||||
@@ -238,7 +227,6 @@ describe('Function Execute API Route', () => {
|
||||
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return <email>',
|
||||
useLocalVM: true,
|
||||
params: gmailData,
|
||||
})
|
||||
|
||||
@@ -264,7 +252,6 @@ describe('Function Execute API Route', () => {
|
||||
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return <email>',
|
||||
useLocalVM: true,
|
||||
params: complexEmailData,
|
||||
})
|
||||
|
||||
@@ -280,7 +267,6 @@ describe('Function Execute API Route', () => {
|
||||
it.concurrent('should handle custom tool execution with direct parameter access', async () => {
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return location + " weather is sunny"',
|
||||
useLocalVM: true,
|
||||
params: {
|
||||
location: 'San Francisco',
|
||||
},
|
||||
@@ -312,7 +298,6 @@ describe('Function Execute API Route', () => {
|
||||
it.concurrent('should handle timeout parameter', async () => {
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return "test"',
|
||||
useLocalVM: true,
|
||||
timeout: 10000,
|
||||
})
|
||||
|
||||
@@ -330,7 +315,6 @@ describe('Function Execute API Route', () => {
|
||||
it.concurrent('should handle empty parameters object', async () => {
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return "no params"',
|
||||
useLocalVM: true,
|
||||
params: {},
|
||||
})
|
||||
|
||||
@@ -364,7 +348,6 @@ SyntaxError: Invalid or unexpected token
|
||||
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'const obj = {\n name: "test",\n description: "This has a missing closing quote\n};\nreturn obj;',
|
||||
useLocalVM: true,
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
@@ -408,7 +391,6 @@ SyntaxError: Invalid or unexpected token
|
||||
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'const obj = null;\nreturn obj.someMethod();',
|
||||
useLocalVM: true,
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
@@ -450,7 +432,6 @@ SyntaxError: Invalid or unexpected token
|
||||
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'const x = 42;\nreturn undefinedVariable + x;',
|
||||
useLocalVM: true,
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
@@ -481,7 +462,6 @@ SyntaxError: Invalid or unexpected token
|
||||
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return "test";',
|
||||
useLocalVM: true,
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
@@ -518,7 +498,6 @@ SyntaxError: Invalid or unexpected token
|
||||
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'const a = 1;\nconst b = 2;\nconst c = 3;\nconst d = 4;\nreturn a + b + c + d;',
|
||||
useLocalVM: true,
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
@@ -550,7 +529,6 @@ SyntaxError: Invalid or unexpected token
|
||||
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'const obj = {\n name: "test"\n// Missing closing brace',
|
||||
useLocalVM: true,
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
@@ -571,7 +549,6 @@ SyntaxError: Invalid or unexpected token
|
||||
// This tests the escapeRegExp function indirectly
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return {{special.chars+*?}}',
|
||||
useLocalVM: true,
|
||||
envVars: {
|
||||
'special.chars+*?': 'escaped-value',
|
||||
},
|
||||
@@ -588,7 +565,6 @@ SyntaxError: Invalid or unexpected token
|
||||
// Test with complex but not circular data first
|
||||
const req = createMockRequest('POST', {
|
||||
code: 'return <complexData>',
|
||||
useLocalVM: true,
|
||||
params: {
|
||||
complexData: {
|
||||
special: 'chars"with\'quotes',
|
||||
|
||||
@@ -8,9 +8,8 @@ import { validateProxyUrl } from '@/lib/security/input-validation'
|
||||
import { generateRequestId } from '@/lib/utils'
|
||||
export const dynamic = 'force-dynamic'
|
||||
export const runtime = 'nodejs'
|
||||
// Segment config exports must be statically analyzable.
|
||||
// Mirror MAX_EXECUTION_DURATION (210s) from '@/lib/execution/constants'.
|
||||
export const maxDuration = 210
|
||||
|
||||
export const MAX_DURATION = 210
|
||||
|
||||
const logger = createLogger('FunctionExecuteAPI')
|
||||
|
||||
@@ -658,7 +657,6 @@ export async function POST(req: NextRequest) {
|
||||
params = {},
|
||||
timeout = DEFAULT_EXECUTION_TIMEOUT_MS,
|
||||
language = DEFAULT_CODE_LANGUAGE,
|
||||
useLocalVM = false,
|
||||
envVars = {},
|
||||
blockData = {},
|
||||
blockNameMapping = {},
|
||||
@@ -693,11 +691,46 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
const e2bEnabled = isTruthy(env.E2B_ENABLED)
|
||||
const lang = isValidCodeLanguage(language) ? language : DEFAULT_CODE_LANGUAGE
|
||||
|
||||
// Extract imports once for JavaScript code (reuse later to avoid double extraction)
|
||||
let jsImports = ''
|
||||
let jsRemainingCode = resolvedCode
|
||||
let hasImports = false
|
||||
|
||||
if (lang === CodeLanguage.JavaScript) {
|
||||
const extractionResult = await extractJavaScriptImports(resolvedCode)
|
||||
jsImports = extractionResult.imports
|
||||
jsRemainingCode = extractionResult.remainingCode
|
||||
|
||||
// Check for ES6 imports or CommonJS require statements
|
||||
// ES6 imports are extracted by the TypeScript parser
|
||||
// Also check for require() calls which indicate external dependencies
|
||||
const hasRequireStatements = /require\s*\(\s*['"`]/.test(resolvedCode)
|
||||
hasImports = jsImports.trim().length > 0 || hasRequireStatements
|
||||
}
|
||||
|
||||
// Python always requires E2B
|
||||
if (lang === CodeLanguage.Python && !e2bEnabled) {
|
||||
throw new Error(
|
||||
'Python execution requires E2B to be enabled. Please contact your administrator to enable E2B, or use JavaScript instead.'
|
||||
)
|
||||
}
|
||||
|
||||
// JavaScript with imports requires E2B
|
||||
if (lang === CodeLanguage.JavaScript && hasImports && !e2bEnabled) {
|
||||
throw new Error(
|
||||
'JavaScript code with import statements requires E2B to be enabled. Please remove the import statements, or contact your administrator to enable E2B.'
|
||||
)
|
||||
}
|
||||
|
||||
// Use E2B if:
|
||||
// - E2B is enabled AND
|
||||
// - Not a custom tool AND
|
||||
// - (Python OR JavaScript with imports)
|
||||
const useE2B =
|
||||
e2bEnabled &&
|
||||
!useLocalVM &&
|
||||
!isCustomTool &&
|
||||
(lang === CodeLanguage.JavaScript || lang === CodeLanguage.Python)
|
||||
(lang === CodeLanguage.Python || (lang === CodeLanguage.JavaScript && hasImports))
|
||||
|
||||
if (useE2B) {
|
||||
logger.info(`[${requestId}] E2B status`, {
|
||||
@@ -712,7 +745,9 @@ export async function POST(req: NextRequest) {
|
||||
// Track prologue lines for error adjustment
|
||||
let prologueLineCount = 0
|
||||
|
||||
const { imports, remainingCode } = await extractJavaScriptImports(resolvedCode)
|
||||
// Reuse the imports we already extracted earlier
|
||||
const imports = jsImports
|
||||
const remainingCode = jsRemainingCode
|
||||
|
||||
const importSection: string = imports ? `${imports}\n` : ''
|
||||
const importLineCount = imports ? imports.split('\n').length : 0
|
||||
|
||||
@@ -49,7 +49,6 @@ const BlockStateSchema = z.object({
|
||||
outputs: z.record(BlockOutputSchema),
|
||||
enabled: z.boolean(),
|
||||
horizontalHandles: z.boolean().optional(),
|
||||
isWide: z.boolean().optional(),
|
||||
height: z.number().optional(),
|
||||
advancedMode: z.boolean().optional(),
|
||||
triggerMode: z.boolean().optional(),
|
||||
@@ -166,7 +165,6 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
||||
enabled: block.enabled !== undefined ? block.enabled : true,
|
||||
horizontalHandles:
|
||||
block.horizontalHandles !== undefined ? block.horizontalHandles : true,
|
||||
isWide: block.isWide !== undefined ? block.isWide : false,
|
||||
height: block.height !== undefined ? block.height : 0,
|
||||
subBlocks: block.subBlocks || {},
|
||||
outputs: block.outputs || {},
|
||||
|
||||
@@ -80,7 +80,9 @@ export async function POST(req: Request) {
|
||||
|
||||
// Helper function to create a default workspace
|
||||
async function createDefaultWorkspace(userId: string, userName?: string | null) {
|
||||
const workspaceName = userName ? `${userName}'s Workspace` : 'My Workspace'
|
||||
// Extract first name only by splitting on spaces and taking the first part
|
||||
const firstName = userName?.split(' ')[0] || null
|
||||
const workspaceName = firstName ? `${firstName}'s Workspace` : 'My Workspace'
|
||||
return createWorkspace(userId, workspaceName)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BookOpen, Github, Rss } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
import ChangelogList from './timeline-list'
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import React from 'react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
import type { ChangelogEntry } from './changelog-content'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import Nav from '@/app/(landing)/components/nav/nav'
|
||||
|
||||
export default function ChangelogLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className='min-h-screen bg-background font-geist-sans text-foreground'>
|
||||
<div className='min-h-screen bg-background text-foreground'>
|
||||
<Nav />
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { quickValidateEmail } from '@/lib/email/validation'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
import Nav from '@/app/(landing)/components/nav/nav'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
|
||||
const logger = createLogger('EmailAuth')
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Label } from '@/components/ui/label'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
import Nav from '@/app/(landing)/components/nav/nav'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
|
||||
const logger = createLogger('PasswordAuth')
|
||||
|
||||
@@ -9,7 +9,7 @@ import { quickValidateEmail } from '@/lib/email/validation'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
import Nav from '@/app/(landing)/components/nav/nav'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
|
||||
const logger = createLogger('SSOAuth')
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRouter } from 'next/navigation'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useBrandConfig } from '@/lib/branding/branding'
|
||||
import Nav from '@/app/(landing)/components/nav/nav'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
|
||||
interface ChatErrorStateProps {
|
||||
|
||||
@@ -4,7 +4,7 @@ import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { GithubIcon } from '@/components/icons'
|
||||
import { useBrandConfig } from '@/lib/branding/branding'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
|
||||
interface ChatHeaderProps {
|
||||
chatConfig: {
|
||||
|
||||
@@ -4,7 +4,7 @@ import type React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import { AlertCircle, Paperclip, Send, Square, X } from 'lucide-react'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { Tooltip } from '@/components/emcn'
|
||||
import { VoiceInput } from '@/app/chat/components/input/voice-input'
|
||||
|
||||
const logger = createLogger('ChatInput')
|
||||
@@ -194,18 +194,16 @@ export const ChatInput: React.FC<{
|
||||
<div className='flex items-center justify-center'>
|
||||
{/* Voice Input Only */}
|
||||
{isSttAvailable && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<div>
|
||||
<VoiceInput onVoiceStart={handleVoiceStart} disabled={isStreaming} large={true} />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
<p>Start voice conversation</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
@@ -338,9 +336,8 @@ export const ChatInput: React.FC<{
|
||||
|
||||
<div className='flex items-center gap-2 p-3 md:p-4'>
|
||||
{/* Paperclip Button */}
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
@@ -349,12 +346,11 @@ export const ChatInput: React.FC<{
|
||||
>
|
||||
<Paperclip size={16} className='md:h-5 md:w-5' />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
<p>Attach files</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
|
||||
{/* Hidden file input */}
|
||||
<input
|
||||
@@ -420,22 +416,16 @@ export const ChatInput: React.FC<{
|
||||
|
||||
{/* Voice Input */}
|
||||
{isSttAvailable && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<div>
|
||||
<VoiceInput
|
||||
onVoiceStart={handleVoiceStart}
|
||||
disabled={isStreaming}
|
||||
minimal
|
||||
/>
|
||||
<VoiceInput onVoiceStart={handleVoiceStart} disabled={isStreaming} minimal />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
<p>Start voice conversation</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
|
||||
{/* Send Button */}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { type HTMLAttributes, type ReactNode } from 'react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { Tooltip } from '@/components/emcn'
|
||||
|
||||
export function LinkWithPreview({ href, children }: { href: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<Tooltip.Root delayDuration={300}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<a
|
||||
href={href}
|
||||
className='text-blue-600 hover:underline dark:text-blue-400'
|
||||
@@ -15,11 +15,11 @@ export function LinkWithPreview({ href, children }: { href: string; children: Re
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top' align='center' sideOffset={5} className='max-w-sm p-3'>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top' align='center' sideOffset={5} className='max-w-sm p-3'>
|
||||
<span className='truncate font-medium text-xs'>{href}</span>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,29 +35,29 @@ export default function MarkdownRenderer({
|
||||
const customComponents = {
|
||||
// Paragraph
|
||||
p: ({ children }: React.HTMLAttributes<HTMLParagraphElement>) => (
|
||||
<p className='mb-1 font-geist-sans text-base text-gray-800 leading-relaxed last:mb-0 dark:text-gray-200'>
|
||||
<p className='mb-1 font-sans text-base text-gray-800 leading-relaxed last:mb-0 dark:text-gray-200'>
|
||||
{children}
|
||||
</p>
|
||||
),
|
||||
|
||||
// Headings
|
||||
h1: ({ children }: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||
<h1 className='mt-10 mb-5 font-geist-sans font-semibold text-2xl text-gray-900 dark:text-gray-100'>
|
||||
<h1 className='mt-10 mb-5 font-sans font-semibold text-2xl text-gray-900 dark:text-gray-100'>
|
||||
{children}
|
||||
</h1>
|
||||
),
|
||||
h2: ({ children }: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||
<h2 className='mt-8 mb-4 font-geist-sans font-semibold text-gray-900 text-xl dark:text-gray-100'>
|
||||
<h2 className='mt-8 mb-4 font-sans font-semibold text-gray-900 text-xl dark:text-gray-100'>
|
||||
{children}
|
||||
</h2>
|
||||
),
|
||||
h3: ({ children }: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||
<h3 className='mt-7 mb-3 font-geist-sans font-semibold text-gray-900 text-lg dark:text-gray-100'>
|
||||
<h3 className='mt-7 mb-3 font-sans font-semibold text-gray-900 text-lg dark:text-gray-100'>
|
||||
{children}
|
||||
</h3>
|
||||
),
|
||||
h4: ({ children }: React.HTMLAttributes<HTMLHeadingElement>) => (
|
||||
<h4 className='mt-5 mb-2 font-geist-sans font-semibold text-base text-gray-900 dark:text-gray-100'>
|
||||
<h4 className='mt-5 mb-2 font-sans font-semibold text-base text-gray-900 dark:text-gray-100'>
|
||||
{children}
|
||||
</h4>
|
||||
),
|
||||
@@ -65,7 +65,7 @@ export default function MarkdownRenderer({
|
||||
// Lists
|
||||
ul: ({ children }: React.HTMLAttributes<HTMLUListElement>) => (
|
||||
<ul
|
||||
className='mt-1 mb-1 space-y-1 pl-6 font-geist-sans text-gray-800 dark:text-gray-200'
|
||||
className='mt-1 mb-1 space-y-1 pl-6 font-sans text-gray-800 dark:text-gray-200'
|
||||
style={{ listStyleType: 'disc' }}
|
||||
>
|
||||
{children}
|
||||
@@ -73,7 +73,7 @@ export default function MarkdownRenderer({
|
||||
),
|
||||
ol: ({ children }: React.HTMLAttributes<HTMLOListElement>) => (
|
||||
<ol
|
||||
className='mt-1 mb-1 space-y-1 pl-6 font-geist-sans text-gray-800 dark:text-gray-200'
|
||||
className='mt-1 mb-1 space-y-1 pl-6 font-sans text-gray-800 dark:text-gray-200'
|
||||
style={{ listStyleType: 'decimal' }}
|
||||
>
|
||||
{children}
|
||||
@@ -84,10 +84,7 @@ export default function MarkdownRenderer({
|
||||
ordered,
|
||||
...props
|
||||
}: React.LiHTMLAttributes<HTMLLIElement> & { ordered?: boolean }) => (
|
||||
<li
|
||||
className='font-geist-sans text-gray-800 dark:text-gray-200'
|
||||
style={{ display: 'list-item' }}
|
||||
>
|
||||
<li className='font-sans text-gray-800 dark:text-gray-200' style={{ display: 'list-item' }}>
|
||||
{children}
|
||||
</li>
|
||||
),
|
||||
@@ -112,7 +109,7 @@ export default function MarkdownRenderer({
|
||||
return (
|
||||
<div className='my-6 rounded-md bg-gray-900 text-sm dark:bg-black'>
|
||||
<div className='flex items-center justify-between border-gray-700 border-b px-4 py-1.5 dark:border-gray-800'>
|
||||
<span className='font-geist-sans text-gray-400 text-xs'>
|
||||
<span className='font-sans text-gray-400 text-xs'>
|
||||
{codeProps.className?.replace('language-', '') || 'code'}
|
||||
</span>
|
||||
</div>
|
||||
@@ -149,7 +146,7 @@ export default function MarkdownRenderer({
|
||||
|
||||
// Blockquotes
|
||||
blockquote: ({ children }: React.HTMLAttributes<HTMLQuoteElement>) => (
|
||||
<blockquote className='my-4 border-gray-300 border-l-4 py-1 pl-4 font-geist-sans text-gray-700 italic dark:border-gray-600 dark:text-gray-300'>
|
||||
<blockquote className='my-4 border-gray-300 border-l-4 py-1 pl-4 font-sans text-gray-700 italic dark:border-gray-600 dark:text-gray-300'>
|
||||
{children}
|
||||
</blockquote>
|
||||
),
|
||||
@@ -167,7 +164,7 @@ export default function MarkdownRenderer({
|
||||
// Tables
|
||||
table: ({ children }: React.TableHTMLAttributes<HTMLTableElement>) => (
|
||||
<div className='my-4 w-full overflow-x-auto'>
|
||||
<table className='min-w-full table-auto border border-gray-300 font-geist-sans text-sm dark:border-gray-700'>
|
||||
<table className='min-w-full table-auto border border-gray-300 font-sans text-sm dark:border-gray-700'>
|
||||
{children}
|
||||
</table>
|
||||
</div>
|
||||
@@ -211,7 +208,7 @@ export default function MarkdownRenderer({
|
||||
const processedContent = content.trim()
|
||||
|
||||
return (
|
||||
<div className='space-y-4 break-words font-geist-sans text-[#0D0D0D] text-base leading-relaxed dark:text-gray-100'>
|
||||
<div className='space-y-4 break-words font-sans text-[#0D0D0D] text-base leading-relaxed dark:text-gray-100'>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={customComponents}>
|
||||
{processedContent}
|
||||
</ReactMarkdown>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { memo, useMemo, useState } from 'react'
|
||||
import { Check, Copy, File as FileIcon, FileText, Image as ImageIcon } from 'lucide-react'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { Tooltip } from '@/components/emcn'
|
||||
import MarkdownRenderer from './components/markdown-renderer'
|
||||
|
||||
export interface ChatAttachment {
|
||||
@@ -24,11 +24,7 @@ export interface ChatMessage {
|
||||
}
|
||||
|
||||
function EnhancedMarkdownRenderer({ content }: { content: string }) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<MarkdownRenderer content={content} />
|
||||
</TooltipProvider>
|
||||
)
|
||||
return <MarkdownRenderer content={content} />
|
||||
}
|
||||
|
||||
export const ClientChatMessage = memo(
|
||||
@@ -190,9 +186,8 @@ export const ClientChatMessage = memo(
|
||||
<div className='flex items-center justify-start space-x-2'>
|
||||
{/* Copy Button - Only show when not streaming */}
|
||||
{!message.isStreaming && (
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
<Tooltip.Root delayDuration={300}>
|
||||
<Tooltip.Trigger asChild>
|
||||
<button
|
||||
className='text-muted-foreground transition-colors hover:bg-muted'
|
||||
onClick={() => {
|
||||
@@ -211,12 +206,11 @@ export const ClientChatMessage = memo(
|
||||
<Copy className='h-3 w-3' strokeWidth={2} />
|
||||
)}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top' align='center' sideOffset={5}>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top' align='center' sideOffset={5}>
|
||||
{isCopied ? 'Copied!' : 'Copy to clipboard'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
BIN
apps/sim/app/fonts/season/SeasonSans-Bold.woff
Normal file
BIN
apps/sim/app/fonts/season/SeasonSans-Bold.woff
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/season/SeasonSans-Bold.woff2
Normal file
BIN
apps/sim/app/fonts/season/SeasonSans-Bold.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/season/SeasonSans-Heavy.woff
Normal file
BIN
apps/sim/app/fonts/season/SeasonSans-Heavy.woff
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/season/SeasonSans-Heavy.woff2
Normal file
BIN
apps/sim/app/fonts/season/SeasonSans-Heavy.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/season/SeasonSans-Light.woff
Normal file
BIN
apps/sim/app/fonts/season/SeasonSans-Light.woff
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/season/SeasonSans-Light.woff2
Normal file
BIN
apps/sim/app/fonts/season/SeasonSans-Light.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/season/SeasonSans-Medium.woff
Normal file
BIN
apps/sim/app/fonts/season/SeasonSans-Medium.woff
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/season/SeasonSans-Medium.woff2
Normal file
BIN
apps/sim/app/fonts/season/SeasonSans-Medium.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/season/SeasonSans-Regular.woff
Normal file
BIN
apps/sim/app/fonts/season/SeasonSans-Regular.woff
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/season/SeasonSans-Regular.woff2
Normal file
BIN
apps/sim/app/fonts/season/SeasonSans-Regular.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/season/SeasonSans-SemiBold.woff
Normal file
BIN
apps/sim/app/fonts/season/SeasonSans-SemiBold.woff
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/season/SeasonSans-SemiBold.woff2
Normal file
BIN
apps/sim/app/fonts/season/SeasonSans-SemiBold.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/season/SeasonSansUprightsVF.woff
Normal file
BIN
apps/sim/app/fonts/season/SeasonSansUprightsVF.woff
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/season/SeasonSansUprightsVF.woff2
Normal file
BIN
apps/sim/app/fonts/season/SeasonSansUprightsVF.woff2
Normal file
Binary file not shown.
17
apps/sim/app/fonts/season/season.ts
Normal file
17
apps/sim/app/fonts/season/season.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import localFont from 'next/font/local'
|
||||
|
||||
/**
|
||||
* Season Sans variable font configuration
|
||||
* Uses variable font file to support any weight from 300-800
|
||||
*/
|
||||
export const season = localFont({
|
||||
src: [
|
||||
// Variable font - supports all weights from 300 to 800
|
||||
{ path: './SeasonSansUprightsVF.woff2', weight: '300 800', style: 'normal' },
|
||||
],
|
||||
display: 'swap',
|
||||
preload: true,
|
||||
variable: '--font-season',
|
||||
fallback: ['system-ui', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans'],
|
||||
adjustFontFallback: 'Arial',
|
||||
})
|
||||
BIN
apps/sim/app/fonts/temp/SeasonSansBold.otf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansBold.otf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansBold.ttf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansBold.ttf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansBold.woff2
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansBold.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansBoldItalic.otf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansBoldItalic.otf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansBoldItalic.ttf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansBoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansBoldItalic.woff2
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansBoldItalic.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansHeavy.otf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansHeavy.otf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansHeavy.ttf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansHeavy.ttf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansHeavy.woff2
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansHeavy.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansHeavyItalic.otf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansHeavyItalic.otf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansHeavyItalic.ttf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansHeavyItalic.ttf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansHeavyItalic.woff2
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansHeavyItalic.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansLight.otf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansLight.otf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansLight.ttf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansLight.ttf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansLight.woff2
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansLight.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansLightItalic.otf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansLightItalic.otf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansLightItalic.ttf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansLightItalic.ttf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansLightItalic.woff2
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansLightItalic.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansMedium.otf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansMedium.otf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansMedium.ttf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansMedium.ttf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansMedium.woff2
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansMedium.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansMediumItalic.otf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansMediumItalic.otf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansMediumItalic.ttf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansMediumItalic.ttf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansMediumItalic.woff2
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansMediumItalic.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansRegular.otf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansRegular.otf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansRegular.ttf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansRegular.ttf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansRegular.woff2
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansRegular.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansRegularItalic.otf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansRegularItalic.otf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansRegularItalic.ttf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansRegularItalic.ttf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansRegularItalic.woff2
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansRegularItalic.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansSemiBold.otf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansSemiBold.otf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansSemiBold.ttf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansSemiBold.ttf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansSemiBold.woff2
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansSemiBold.woff2
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansSemiBoldItalic.otf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansSemiBoldItalic.otf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansSemiBoldItalic.ttf
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansSemiBoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/sim/app/fonts/temp/SeasonSansSemiBoldItalic.woff2
Normal file
BIN
apps/sim/app/fonts/temp/SeasonSansSemiBoldItalic.woff2
Normal file
Binary file not shown.
@@ -2,6 +2,39 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* ==========================================================================
|
||||
SIDEBAR WIDTH PERSISTENCE
|
||||
========================================================================== */
|
||||
/**
|
||||
* CSS-based sidebar width to prevent SSR hydration mismatches.
|
||||
*
|
||||
* How it works:
|
||||
* 1. Default width set here in CSS (232px)
|
||||
* 2. Blocking script in layout.tsx updates this before React hydrates
|
||||
* 3. Store updates this variable when user resizes
|
||||
*
|
||||
* This approach ensures server and client always render identical HTML.
|
||||
*/
|
||||
:root {
|
||||
--sidebar-width: 232px;
|
||||
--panel-width: 244px;
|
||||
--toolbar-triggers-height: 300px;
|
||||
--editor-connections-height: 200px;
|
||||
--terminal-height: 30px;
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
width: var(--sidebar-width);
|
||||
}
|
||||
|
||||
.panel-container {
|
||||
width: var(--panel-width);
|
||||
}
|
||||
|
||||
.terminal-container {
|
||||
height: var(--terminal-height);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
WORKFLOW COMPONENT Z-INDEX FIXES
|
||||
========================================================================== */
|
||||
@@ -31,6 +64,19 @@
|
||||
z-index: 60 !important;
|
||||
}
|
||||
|
||||
/* Workflow canvas background */
|
||||
.workflow-container,
|
||||
.workflow-container .react-flow__pane,
|
||||
.workflow-container .react-flow__renderer {
|
||||
background-color: #e4e4e4 !important;
|
||||
}
|
||||
|
||||
.dark .workflow-container,
|
||||
.dark .workflow-container .react-flow__pane,
|
||||
.dark .workflow-container .react-flow__renderer {
|
||||
background-color: #1b1b1b !important;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
LANDING LOOP ANIMATION
|
||||
========================================================================== */
|
||||
@@ -97,6 +143,7 @@
|
||||
/* Border & Input Colors */
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--input-background: 0 0% 79.22%;
|
||||
--ring: 0 0% 3.9%;
|
||||
|
||||
/* Border Radius */
|
||||
@@ -110,7 +157,6 @@
|
||||
|
||||
/* Workflow Properties */
|
||||
--workflow-background: 0 0% 100%;
|
||||
--workflow-dots: 0 0% 94.5%;
|
||||
--card-background: 0 0% 99.2%;
|
||||
--card-border: 0 0% 89.8%;
|
||||
--card-text: 0 0% 3.9%;
|
||||
@@ -137,7 +183,7 @@
|
||||
/* Dark Mode Theme */
|
||||
.dark {
|
||||
/* Core Colors */
|
||||
--background: 0 0% 3.9%;
|
||||
--background: 0 0% 10.59%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
/* Card Colors */
|
||||
@@ -171,6 +217,7 @@
|
||||
/* Border & Input Colors */
|
||||
--border: 0 0% 16.1%;
|
||||
--input: 0 0% 16.1%;
|
||||
--input-background: 0 0% 20.78%;
|
||||
--ring: 0 0% 83.9%;
|
||||
|
||||
/* Scrollbar Properties */
|
||||
@@ -179,8 +226,7 @@
|
||||
--scrollbar-thumb-hover: 0 0% 40%;
|
||||
|
||||
/* Workflow Properties */
|
||||
--workflow-background: 0 0% 3.9%;
|
||||
--workflow-dots: 0 0% 7.5%;
|
||||
--workflow-background: 0 0% 10.59%;
|
||||
--card-background: 0 0% 9.0%;
|
||||
--card-border: 0 0% 22.7%;
|
||||
--card-text: 0 0% 98%;
|
||||
@@ -214,6 +260,10 @@
|
||||
overscroll-behavior-x: none;
|
||||
}
|
||||
|
||||
*:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
overscroll-behavior-x: none;
|
||||
@@ -223,6 +273,12 @@
|
||||
scrollbar-gutter: stable;
|
||||
/* Improve animation performance */
|
||||
text-rendering: optimizeSpeed;
|
||||
/* Default text styles */
|
||||
letter-spacing: 0.28px;
|
||||
}
|
||||
|
||||
.dark body {
|
||||
@apply antialiased;
|
||||
}
|
||||
|
||||
/* Global Scrollbar Styling */
|
||||
@@ -232,7 +288,7 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
@@ -241,25 +297,36 @@
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: hsl(var(--muted-foreground) / 0.4);
|
||||
}
|
||||
|
||||
/* Dark Mode Global Scrollbar */
|
||||
.dark ::-webkit-scrollbar-track {
|
||||
background: #272727;
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background-color: hsl(var(--muted-foreground) / 0.3);
|
||||
}
|
||||
|
||||
/* For Firefox */
|
||||
.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background-color: hsl(var(--muted-foreground) / 0.4);
|
||||
}
|
||||
|
||||
/* Firefox Scrollbar */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: hsl(var(--muted-foreground) / 0.3) transparent;
|
||||
scrollbar-color: hsl(var(--muted-foreground) / 0.3) #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
TYPOGRAPHY
|
||||
========================================================================== */
|
||||
.font-geist-sans {
|
||||
font-family: var(--font-geist-sans);
|
||||
}
|
||||
.dark * {
|
||||
scrollbar-color: hsl(var(--muted-foreground) / 0.3) #272727;
|
||||
}
|
||||
|
||||
.font-geist-mono {
|
||||
font-family: var(--font-geist-mono);
|
||||
/* Copilot Scrollbar - Ensure stable gutter */
|
||||
.copilot-scrollable {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
@@ -388,6 +455,11 @@ input[type="search"]::-ms-clear {
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
/* Input Background Utility */
|
||||
.bg-input-background {
|
||||
background-color: hsl(var(--input-background));
|
||||
}
|
||||
|
||||
/* Brand Color Utilities */
|
||||
.bg-brand-primary {
|
||||
background-color: var(--brand-primary-hex);
|
||||
@@ -630,4 +702,63 @@ input[type="search"]::-ms-clear {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Panel tab visibility - prevent flash on hydration
|
||||
* Before React hydrates, use data attribute set by blocking script
|
||||
* to show the correct tab content and button states
|
||||
*/
|
||||
html[data-panel-active-tab="copilot"] .panel-container [data-tab-content="toolbar"],
|
||||
html[data-panel-active-tab="copilot"] .panel-container [data-tab-content="editor"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
html[data-panel-active-tab="toolbar"] .panel-container [data-tab-content="copilot"],
|
||||
html[data-panel-active-tab="toolbar"] .panel-container [data-tab-content="editor"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
html[data-panel-active-tab="editor"] .panel-container [data-tab-content="copilot"],
|
||||
html[data-panel-active-tab="editor"] .panel-container [data-tab-content="toolbar"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Style active and inactive tab buttons before hydration
|
||||
* Ensure only the correct tab shows as active
|
||||
* Note: Colors match the ghost button variant for consistency
|
||||
*/
|
||||
|
||||
/* Make only copilot active when it's the active tab */
|
||||
html[data-panel-active-tab="copilot"] .panel-container [data-tab-button="copilot"] {
|
||||
background-color: rgb(53 53 53) !important;
|
||||
color: rgb(230 230 230) !important;
|
||||
}
|
||||
html[data-panel-active-tab="copilot"] .panel-container [data-tab-button="toolbar"],
|
||||
html[data-panel-active-tab="copilot"] .panel-container [data-tab-button="editor"] {
|
||||
background-color: transparent !important;
|
||||
color: rgb(174 174 174) !important; /* Muted gray for inactive tabs */
|
||||
}
|
||||
|
||||
/* Make only toolbar active when it's the active tab */
|
||||
html[data-panel-active-tab="toolbar"] .panel-container [data-tab-button="toolbar"] {
|
||||
background-color: rgb(53 53 53) !important;
|
||||
color: rgb(230 230 230) !important;
|
||||
}
|
||||
html[data-panel-active-tab="toolbar"] .panel-container [data-tab-button="copilot"],
|
||||
html[data-panel-active-tab="toolbar"] .panel-container [data-tab-button="editor"] {
|
||||
background-color: transparent !important;
|
||||
color: rgb(174 174 174) !important; /* Muted gray for inactive tabs */
|
||||
}
|
||||
|
||||
/* Make only editor active when it's the active tab */
|
||||
html[data-panel-active-tab="editor"] .panel-container [data-tab-button="editor"] {
|
||||
background-color: rgb(53 53 53) !important;
|
||||
color: rgb(230 230 230) !important;
|
||||
}
|
||||
html[data-panel-active-tab="editor"] .panel-container [data-tab-button="copilot"],
|
||||
html[data-panel-active-tab="editor"] .panel-container [data-tab-button="toolbar"] {
|
||||
background-color: transparent !important;
|
||||
color: rgb(174 174 174) !important; /* Muted gray for inactive tabs */
|
||||
}
|
||||
}
|
||||
|
||||
48
apps/sim/app/hydration-error-handler.tsx
Normal file
48
apps/sim/app/hydration-error-handler.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('RootLayout')
|
||||
|
||||
const BROWSER_EXTENSION_ATTRIBUTES = [
|
||||
'data-new-gr-c-s-check-loaded',
|
||||
'data-gr-ext-installed',
|
||||
'data-gr-ext-disabled',
|
||||
'data-grammarly',
|
||||
'data-fgm',
|
||||
'data-lt-installed',
|
||||
]
|
||||
|
||||
/**
|
||||
* Client component that intercepts console.error to filter and log hydration errors
|
||||
* while ignoring errors caused by browser extensions.
|
||||
*/
|
||||
export function HydrationErrorHandler() {
|
||||
useEffect(() => {
|
||||
const originalError = console.error
|
||||
console.error = (...args) => {
|
||||
if (args[0].includes('Hydration')) {
|
||||
const isExtensionError = BROWSER_EXTENSION_ATTRIBUTES.some((attr) =>
|
||||
args.some((arg) => typeof arg === 'string' && arg.includes(attr))
|
||||
)
|
||||
|
||||
if (!isExtensionError) {
|
||||
logger.error('Hydration Error', {
|
||||
details: args,
|
||||
componentStack: args.find(
|
||||
(arg) => typeof arg === 'string' && arg.includes('component stack')
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
originalError.apply(console, args)
|
||||
}
|
||||
|
||||
return () => {
|
||||
console.error = originalError
|
||||
}
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,12 +1,20 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { AlertCircle, CheckCircle2, Mail, RotateCcw, ShieldX, UserPlus, Users2 } from 'lucide-react'
|
||||
import {
|
||||
AlertCircle,
|
||||
CheckCircle2,
|
||||
Loader2,
|
||||
Mail,
|
||||
RotateCcw,
|
||||
ShieldX,
|
||||
UserPlus,
|
||||
Users2,
|
||||
} from 'lucide-react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { LoadingAgent } from '@/components/ui/loading-agent'
|
||||
import { useBrandConfig } from '@/lib/branding/branding'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
|
||||
interface InviteStatusCardProps {
|
||||
@@ -96,7 +104,7 @@ export function InviteStatusCard({
|
||||
</p>
|
||||
</div>
|
||||
<div className='flex w-full items-center justify-center py-8'>
|
||||
<LoadingAgent size='lg' />
|
||||
<Loader2 className='h-8 w-8 animate-spin text-[var(--brand-primary-hex)]' />
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -156,7 +164,7 @@ export function InviteStatusCard({
|
||||
>
|
||||
{action.loading ? (
|
||||
<>
|
||||
<LoadingAgent size='sm' />
|
||||
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
|
||||
{action.label}...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -3,46 +3,15 @@ import { PublicEnvScript } from 'next-runtime-env'
|
||||
import { BrandedLayout } from '@/components/branded-layout'
|
||||
import { generateThemeCSS } from '@/lib/branding/inject-theme'
|
||||
import { generateBrandedMetadata, generateStructuredData } from '@/lib/branding/metadata'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { PostHogProvider } from '@/lib/posthog/provider'
|
||||
import '@/app/globals.css'
|
||||
|
||||
import { SessionProvider } from '@/lib/session/session-context'
|
||||
import { season } from '@/app/fonts/season/season'
|
||||
import { HydrationErrorHandler } from '@/app/hydration-error-handler'
|
||||
import { ThemeProvider } from '@/app/theme-provider'
|
||||
import { ZoomPrevention } from '@/app/zoom-prevention'
|
||||
|
||||
const logger = createLogger('RootLayout')
|
||||
|
||||
const BROWSER_EXTENSION_ATTRIBUTES = [
|
||||
'data-new-gr-c-s-check-loaded',
|
||||
'data-gr-ext-installed',
|
||||
'data-gr-ext-disabled',
|
||||
'data-grammarly',
|
||||
'data-fgm',
|
||||
'data-lt-installed',
|
||||
]
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const originalError = console.error
|
||||
console.error = (...args) => {
|
||||
if (args[0].includes('Hydration')) {
|
||||
const isExtensionError = BROWSER_EXTENSION_ATTRIBUTES.some((attr) =>
|
||||
args.some((arg) => typeof arg === 'string' && arg.includes(attr))
|
||||
)
|
||||
|
||||
if (!isExtensionError) {
|
||||
logger.error('Hydration Error', {
|
||||
details: args,
|
||||
componentStack: args.find(
|
||||
(arg) => typeof arg === 'string' && arg.includes('component stack')
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
originalError.apply(console, args)
|
||||
}
|
||||
}
|
||||
|
||||
export const viewport: Viewport = {
|
||||
width: 'device-width',
|
||||
initialScale: 1,
|
||||
@@ -86,9 +55,117 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<meta name='format-detection' content='telephone=no' />
|
||||
<meta httpEquiv='x-ua-compatible' content='ie=edge' />
|
||||
|
||||
{/* Blocking script to prevent sidebar dimensions flash on page load */}
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
(function() {
|
||||
try {
|
||||
var stored = localStorage.getItem('sidebar-state');
|
||||
if (stored) {
|
||||
var parsed = JSON.parse(stored);
|
||||
var state = parsed?.state;
|
||||
var width = state?.sidebarWidth;
|
||||
var maxSidebarWidth = window.innerWidth * 0.3;
|
||||
|
||||
// Cap stored width at 30% of viewport
|
||||
if (width >= 232 && width <= maxSidebarWidth) {
|
||||
document.documentElement.style.setProperty('--sidebar-width', width + 'px');
|
||||
} else if (width > maxSidebarWidth) {
|
||||
// If stored width exceeds 30%, cap it
|
||||
document.documentElement.style.setProperty('--sidebar-width', maxSidebarWidth + 'px');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback handled by CSS defaults
|
||||
}
|
||||
|
||||
// Set panel width and active tab
|
||||
try {
|
||||
var panelStored = localStorage.getItem('panel-state');
|
||||
if (panelStored) {
|
||||
var panelParsed = JSON.parse(panelStored);
|
||||
var panelState = panelParsed?.state;
|
||||
var panelWidth = panelState?.panelWidth;
|
||||
var maxPanelWidth = window.innerWidth * 0.4;
|
||||
|
||||
// Cap stored width at 40% of viewport
|
||||
if (panelWidth >= 244 && panelWidth <= maxPanelWidth) {
|
||||
document.documentElement.style.setProperty('--panel-width', panelWidth + 'px');
|
||||
} else if (panelWidth > maxPanelWidth) {
|
||||
// If stored width exceeds 40%, cap it
|
||||
document.documentElement.style.setProperty('--panel-width', maxPanelWidth + 'px');
|
||||
}
|
||||
|
||||
// Set active tab to prevent flash on hydration
|
||||
var activeTab = panelState?.activeTab;
|
||||
if (activeTab) {
|
||||
document.documentElement.setAttribute('data-panel-active-tab', activeTab);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback handled by CSS defaults
|
||||
}
|
||||
|
||||
// Set toolbar triggers height
|
||||
try {
|
||||
var toolbarStored = localStorage.getItem('toolbar-state');
|
||||
if (toolbarStored) {
|
||||
var toolbarParsed = JSON.parse(toolbarStored);
|
||||
var toolbarState = toolbarParsed?.state;
|
||||
var toolbarTriggersHeight = toolbarState?.toolbarTriggersHeight;
|
||||
if (toolbarTriggersHeight !== undefined && toolbarTriggersHeight >= 100 && toolbarTriggersHeight <= 800) {
|
||||
document.documentElement.style.setProperty('--toolbar-triggers-height', toolbarTriggersHeight + 'px');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback handled by CSS defaults
|
||||
}
|
||||
|
||||
// Set editor connections height
|
||||
try {
|
||||
var editorStored = localStorage.getItem('panel-editor-state');
|
||||
if (editorStored) {
|
||||
var editorParsed = JSON.parse(editorStored);
|
||||
var editorState = editorParsed?.state;
|
||||
var connectionsHeight = editorState?.connectionsHeight;
|
||||
if (connectionsHeight !== undefined && connectionsHeight >= 30 && connectionsHeight <= 200) {
|
||||
document.documentElement.style.setProperty('--editor-connections-height', connectionsHeight + 'px');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback handled by CSS defaults
|
||||
}
|
||||
|
||||
// Set terminal height
|
||||
try {
|
||||
var terminalStored = localStorage.getItem('terminal-state');
|
||||
if (terminalStored) {
|
||||
var terminalParsed = JSON.parse(terminalStored);
|
||||
var terminalState = terminalParsed?.state;
|
||||
var terminalHeight = terminalState?.terminalHeight;
|
||||
var maxTerminalHeight = window.innerHeight * 0.5;
|
||||
|
||||
// Cap stored height at 50% of viewport
|
||||
if (terminalHeight >= 30 && terminalHeight <= maxTerminalHeight) {
|
||||
document.documentElement.style.setProperty('--terminal-height', terminalHeight + 'px');
|
||||
} else if (terminalHeight > maxTerminalHeight) {
|
||||
// If stored height exceeds 50%, cap it
|
||||
document.documentElement.style.setProperty('--terminal-height', maxTerminalHeight + 'px');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Fallback handled by CSS defaults
|
||||
}
|
||||
})();
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
|
||||
<PublicEnvScript />
|
||||
</head>
|
||||
<body suppressHydrationWarning>
|
||||
<body className={`${season.variable} font-season`} suppressHydrationWarning>
|
||||
<HydrationErrorHandler />
|
||||
<PostHogProvider>
|
||||
<ThemeProvider>
|
||||
<SessionProvider>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRouter } from 'next/navigation'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useBrandConfig } from '@/lib/branding/branding'
|
||||
import Nav from '@/app/(landing)/components/nav/nav'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
|
||||
export default function NotFound() {
|
||||
|
||||
@@ -28,7 +28,7 @@ import { Separator } from '@/components/ui/separator'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { useBrandConfig } from '@/lib/branding/branding'
|
||||
import Nav from '@/app/(landing)/components/nav/nav'
|
||||
import { inter } from '@/app/fonts/inter'
|
||||
import { inter } from '@/app/fonts/inter/inter'
|
||||
import { soehne } from '@/app/fonts/soehne/soehne'
|
||||
import type { ResumeStatus } from '@/executor/types'
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { useParams, useRouter, useSearchParams } from 'next/navigation'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import { Tooltip } from '@/components/emcn'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
@@ -59,7 +60,6 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { CredentialRequirement } from '@/lib/workflows/credential-extractor'
|
||||
@@ -537,7 +537,6 @@ export default function TemplateDetails() {
|
||||
}
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={100} skipDelayDuration={0}>
|
||||
<div className='flex min-h-screen flex-col'>
|
||||
{/* Header */}
|
||||
<div className='border-b bg-background p-6'>
|
||||
@@ -626,9 +625,8 @@ export default function TemplateDetails() {
|
||||
{canEditTemplate && currentUserId && (
|
||||
<>
|
||||
{template.workflowId && !showWorkspaceSelectorForEdit ? (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span>
|
||||
<Button
|
||||
onClick={handleEditTemplate}
|
||||
@@ -642,14 +640,13 @@ export default function TemplateDetails() {
|
||||
{isEditing ? 'Opening...' : 'Edit Template'}
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
</Tooltip.Trigger>
|
||||
{hasWorkspaceAccess === false && (
|
||||
<TooltipContent>
|
||||
<Tooltip.Content>
|
||||
<p>Don't have access to workspace to edit template</p>
|
||||
</TooltipContent>
|
||||
</Tooltip.Content>
|
||||
)}
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</Tooltip.Root>
|
||||
) : (
|
||||
<DropdownMenu
|
||||
open={showWorkspaceSelectorForEdit}
|
||||
@@ -814,8 +811,7 @@ export default function TemplateDetails() {
|
||||
<div className='mt-8'>
|
||||
<h3 className='mb-3 font-semibold text-lg'>Credentials Needed</h3>
|
||||
<ul className='list-disc space-y-1 pl-6 text-muted-foreground text-sm'>
|
||||
{template.requiredCredentials.map(
|
||||
(cred: CredentialRequirement, idx: number) => {
|
||||
{template.requiredCredentials.map((cred: CredentialRequirement, idx: number) => {
|
||||
// Get block name from registry or format blockType
|
||||
const blockName =
|
||||
getBlock(cred.blockType)?.name ||
|
||||
@@ -825,8 +821,7 @@ export default function TemplateDetails() {
|
||||
.includes(` for ${blockName.toLowerCase()}`)
|
||||
const text = alreadyHasBlock ? cred.label : `${cred.label} for ${blockName}`
|
||||
return <li key={idx}>{text}</li>
|
||||
}
|
||||
)}
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
@@ -931,6 +926,5 @@ export default function TemplateDetails() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { GeistSans } from 'geist/font/sans'
|
||||
import { season } from '@/app/fonts/season/season'
|
||||
|
||||
export default function TemplatesLayout({ children }: { children: React.ReactNode }) {
|
||||
return <div className={GeistSans.className}>{children}</div>
|
||||
return <div className={`${season.variable} font-season`}>{children}</div>
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ export function CreateChunkModal({
|
||||
</DialogHeader>
|
||||
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
<div className='scrollbar-thin scrollbar-thumb-muted-foreground/20 hover:scrollbar-thumb-muted-foreground/25 scrollbar-track-transparent min-h-0 flex-1 overflow-y-auto px-6'>
|
||||
<div className='min-h-0 flex-1 overflow-y-auto px-6'>
|
||||
<div className='flex min-h-full flex-col py-4'>
|
||||
{/* Document Info Section - Fixed at top */}
|
||||
<div className='flex-shrink-0 space-y-4'>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { AlertCircle, ChevronDown, ChevronUp, Loader2, X } from 'lucide-react'
|
||||
import { Tooltip } from '@/components/emcn'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -16,7 +17,6 @@ import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
|
||||
import type { ChunkData, DocumentData } from '@/stores/knowledge/store'
|
||||
@@ -193,8 +193,8 @@ export function EditChunkModal({
|
||||
|
||||
{/* Navigation Controls */}
|
||||
<div className='flex items-center gap-1'>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger
|
||||
asChild
|
||||
onFocus={(e) => e.preventDefault()}
|
||||
onBlur={(e) => e.preventDefault()}
|
||||
@@ -208,15 +208,15 @@ export function EditChunkModal({
|
||||
>
|
||||
<ChevronUp className='h-4 w-4' />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='bottom'>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='bottom'>
|
||||
Previous chunk{' '}
|
||||
{currentPage > 1 && currentChunkIndex === 0 ? '(previous page)' : ''}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger
|
||||
asChild
|
||||
onFocus={(e) => e.preventDefault()}
|
||||
onBlur={(e) => e.preventDefault()}
|
||||
@@ -230,14 +230,14 @@ export function EditChunkModal({
|
||||
>
|
||||
<ChevronDown className='h-4 w-4' />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='bottom'>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='bottom'>
|
||||
Next chunk{' '}
|
||||
{currentPage < totalPages && currentChunkIndex === allChunks.length - 1
|
||||
? '(next page)'
|
||||
: ''}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -254,7 +254,7 @@ export function EditChunkModal({
|
||||
</DialogHeader>
|
||||
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
<div className='scrollbar-thin scrollbar-thumb-muted-foreground/20 hover:scrollbar-thumb-muted-foreground/25 scrollbar-track-transparent min-h-0 flex-1 overflow-y-auto px-6'>
|
||||
<div className='min-h-0 flex-1 overflow-y-auto px-6'>
|
||||
<div className='flex min-h-full flex-col py-4'>
|
||||
{/* Document Info Section - Fixed at top */}
|
||||
<div className='flex-shrink-0 space-y-4'>
|
||||
|
||||
@@ -3,14 +3,8 @@
|
||||
import { Suspense, startTransition, useCallback, useEffect, useState } from 'react'
|
||||
import { ChevronLeft, ChevronRight, Circle, CircleOff, FileText, Plus, Trash2 } from 'lucide-react'
|
||||
import { useParams, useSearchParams } from 'next/navigation'
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
SearchHighlight,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui'
|
||||
import { Tooltip } from '@/components/emcn'
|
||||
import { Button, Checkbox, SearchHighlight } from '@/components/ui'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import {
|
||||
CreateChunkModal,
|
||||
@@ -340,8 +334,8 @@ export function Document({
|
||||
</td>
|
||||
<td className='px-4 py-3'>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
@@ -358,13 +352,13 @@ export function Document({
|
||||
<CircleOff className='h-4 w-4' />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
{chunk.enabled ? 'Disable Chunk' : 'Enable Chunk'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
@@ -377,9 +371,9 @@ export function Document({
|
||||
>
|
||||
<Trash2 className='h-4 w-4' />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>Delete Chunk</TooltipContent>
|
||||
</Tooltip>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>Delete Chunk</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
Trash2,
|
||||
} from 'lucide-react'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import { Tooltip } from '@/components/emcn'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -30,7 +31,6 @@ import {
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { SearchHighlight } from '@/components/ui/search-highlight'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import type { DocumentSortField, SortOrder } from '@/lib/knowledge/documents/types'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import {
|
||||
@@ -718,8 +718,8 @@ export function KnowledgeBase({
|
||||
|
||||
<div className='flex items-center gap-2'>
|
||||
{/* Add Documents Button */}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<PrimaryButton
|
||||
onClick={handleAddDocuments}
|
||||
disabled={userPermissions.canEdit !== true}
|
||||
@@ -727,11 +727,11 @@ export function KnowledgeBase({
|
||||
<Plus className='h-3.5 w-3.5' />
|
||||
Add Documents
|
||||
</PrimaryButton>
|
||||
</TooltipTrigger>
|
||||
</Tooltip.Trigger>
|
||||
{userPermissions.canEdit !== true && (
|
||||
<TooltipContent>Write permission required to add documents</TooltipContent>
|
||||
<Tooltip.Content>Write permission required to add documents</Tooltip.Content>
|
||||
)}
|
||||
</Tooltip>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -912,17 +912,17 @@ export function KnowledgeBase({
|
||||
<td className='px-4 py-3'>
|
||||
<div className='flex items-center gap-2'>
|
||||
{getFileIcon(doc.mimeType, doc.filename)}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<span className='block truncate text-sm' title={doc.filename}>
|
||||
<SearchHighlight
|
||||
text={doc.filename}
|
||||
searchQuery={searchQuery}
|
||||
/>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>{doc.filename}</TooltipContent>
|
||||
</Tooltip>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>{doc.filename}</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -978,19 +978,19 @@ export function KnowledgeBase({
|
||||
{/* Status column */}
|
||||
<td className='px-4 py-3'>
|
||||
{doc.processingStatus === 'failed' && doc.processingError ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<div
|
||||
className={statusDisplay.className}
|
||||
style={{ cursor: 'help' }}
|
||||
>
|
||||
{statusDisplay.text}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top' className='max-w-xs'>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top' className='max-w-xs'>
|
||||
{doc.processingError}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
) : (
|
||||
<div className={statusDisplay.className}>
|
||||
{statusDisplay.text}
|
||||
@@ -1002,8 +1002,8 @@ export function KnowledgeBase({
|
||||
<td className='px-4 py-3'>
|
||||
<div className='flex items-center gap-1'>
|
||||
{doc.processingStatus === 'failed' && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
@@ -1015,13 +1015,13 @@ export function KnowledgeBase({
|
||||
>
|
||||
<RotateCcw className='h-4 w-4' />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>Retry processing</TooltipContent>
|
||||
</Tooltip>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>Retry processing</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
@@ -1042,8 +1042,8 @@ export function KnowledgeBase({
|
||||
<CircleOff className='h-4 w-4' />
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
{doc.processingStatus === 'processing' ||
|
||||
doc.processingStatus === 'pending'
|
||||
? 'Cannot modify while processing'
|
||||
@@ -1052,11 +1052,11 @@ export function KnowledgeBase({
|
||||
: doc.enabled
|
||||
? 'Disable Document'
|
||||
: 'Enable Document'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
@@ -1072,15 +1072,15 @@ export function KnowledgeBase({
|
||||
>
|
||||
<Trash2 className='h-4 w-4' />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side='top'>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
{doc.processingStatus === 'processing'
|
||||
? 'Cannot delete while processing'
|
||||
: !userPermissions.canEdit
|
||||
? 'Write permission required to delete documents'
|
||||
: 'Delete Document'}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user