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:
Emir Karabeg
2025-11-08 10:58:31 -08:00
committed by GitHub
parent 945405c461
commit eed2072723
593 changed files with 31386 additions and 21701 deletions

19
.cursorrules Normal file
View 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

View File

@@ -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
View File

@@ -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
View 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
View File

@@ -42,9 +42,6 @@ sim-standalone.tar.gz
*.tsbuildinfo
next-env.d.ts
# cursorrules
.cursorrules
# Uploads
/uploads

View File

@@ -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

View File

@@ -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' />

View File

@@ -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')

View File

@@ -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')

View File

@@ -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

View File

@@ -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')

View File

@@ -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')

View File

@@ -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 {

View File

@@ -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',

View File

@@ -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 = [

View File

@@ -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')

View File

@@ -1,4 +1,4 @@
import { inter } from '@/app/fonts/inter'
import { inter } from '@/app/fonts/inter/inter'
interface LandingTemplatePreviewProps {
previewImage: string

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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'

View File

@@ -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',

View File

@@ -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

View File

@@ -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 || {},

View File

@@ -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)
}

View File

@@ -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'

View File

@@ -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'

View File

@@ -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>

View File

@@ -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')

View File

@@ -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')

View File

@@ -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')

View File

@@ -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 {

View File

@@ -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: {

View File

@@ -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 */}

View File

@@ -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>

View File

@@ -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>
)}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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',
})

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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 */
}
}

View 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
}

View File

@@ -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}...
</>
) : (

View File

@@ -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>

View File

@@ -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() {

View File

@@ -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'

View File

@@ -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>
)
}

View File

@@ -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>
}

View File

@@ -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'>

View File

@@ -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'>

View File

@@ -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>

View File

@@ -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