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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/docs/app/robots.txt/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export async function GET() {
User-agent: *
Disallow: /.next/
Disallow: /api/internal/
Disallow: /_next/static/
Disallow: /admin/
Allow: /
Allow: /llms.txt
Expand Down
5 changes: 2 additions & 3 deletions apps/docs/components/footer/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const RESOURCES_LINKS: FooterItem[] = [
{ label: 'Contact', href: `${SIM_SITE_URL}/contact`, external: true },
]

/** Top model providers — mirrors the landing footer's top 8 catalog providers. */
/** Top model providers — mirrors the landing footer's top 7 catalog providers. */
const MODEL_LINKS: FooterItem[] = [
{ label: 'All Models', href: `${SIM_SITE_URL}/models`, external: true },
{ label: 'OpenAI', href: `${SIM_SITE_URL}/models/openai`, external: true },
Expand All @@ -51,7 +51,6 @@ const MODEL_LINKS: FooterItem[] = [
{ label: 'xAI', href: `${SIM_SITE_URL}/models/xai`, external: true },
{ label: 'Cerebras', href: `${SIM_SITE_URL}/models/cerebras`, external: true },
{ label: 'Groq', href: `${SIM_SITE_URL}/models/groq`, external: true },
{ label: 'Sakana AI', href: `${SIM_SITE_URL}/models/sakana`, external: true },
]

const BLOCK_LINKS: FooterItem[] = [
Expand Down Expand Up @@ -84,7 +83,7 @@ const SOCIAL_LINKS: FooterItem[] = [
{ label: 'X (Twitter)', href: 'https://x.com/simdotai', external: true },
{
label: 'LinkedIn',
href: 'https://www.linkedin.com/company/simstudioai/',
href: 'https://www.linkedin.com/company/simdotai/',
external: true,
},
{
Expand Down
24 changes: 18 additions & 6 deletions apps/docs/content/docs/integrations/file.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Read workspace file objects from selected files, canonical workspace file IDs, o

### File Get Content

Extract the text content of workspace files selected directly, identified by canonical file ID, or collected from one or more workspace folders.
Extract workspace file text using the same parser mode as File Search. Use the returned fileId and offset/limit to read the matching line and surrounding context. For documents and spreadsheets, line numbers refer to extracted text, not page numbers or worksheet row numbers.

#### Input

Expand All @@ -77,7 +77,7 @@ Extract the text content of workspace files selected directly, identified by can

### File Search

Search the indexed text of active workspace files for lines matching a query, and return each matching line once with its file ID and line number. By default the query is a regular expression; in exact mode it is matched verbatim and metacharacters are literal. Coverage is what the index currently holds. A term that is not found is only authoritative when "complete" is true AND "indexStatus" reports no skipped or partial files; otherwise it is unknown rather than absent, so re-check before creating something on the assumption it is missing. Narrow the search with folderPaths to confine it to one or more folder trees, which also narrows "indexStatus" to those trees.
Search the indexed text of active workspace files for lines matching a query, and return each matching line once with its file ID and line number. By default the query is a regular expression; in exact mode it is matched verbatim and metacharacters are literal. Coverage is what the index currently holds. A term that is not found is only authoritative when "complete" is true AND "indexStatus" reports no skipped files; otherwise it is unknown rather than absent, so re-check before creating something on the assumption it is missing. Narrow the search with folderPaths to confine it to one or more folder trees, which also narrows "indexStatus" to those trees.

#### Input

Expand All @@ -99,13 +99,13 @@ Search the indexed text of active workspace files for lines matching a query, an
| ↳ `text` | string | Matching line or bounded match-centered preview. |
| `count` | number | Number of returned matching lines. |
| `truncated` | boolean | Whether more matching lines exist beyond the configured hard cap. |
| `complete` | boolean | Whether indexing has no pending or failed current revisions; skipped and partial coverage is reported separately. |
| `complete` | boolean | Whether indexing has no pending or failed current revisions; excluded files are reported separately. |
| `indexStatus` | object | Current workspace search-index coverage by file status. |
| ↳ `readyFiles` | number | Files whose current revision is searchable. |
| ↳ `readyFiles` | number | Files whose entire current extracted text is searchable. |
| ↳ `pendingFiles` | number | Files still waiting to be indexed. |
| ↳ `failedFiles` | number | Files whose current indexing attempt failed. |
| ↳ `skippedFiles` | number | Files intentionally excluded because they are unsupported or oversized. |
| ↳ `partialFiles` | number | Searchable files whose extracted text was truncated by the parser or cap. |
| ↳ `skippedFiles` | number | Files excluded in full because they are oversized, unsupported, or cannot be completely extracted. |
| ↳ `partialFiles` | number | Always zero; retained for compatibility. Files are never partially indexed. |

### File Fetch

Expand Down Expand Up @@ -380,4 +380,16 @@ Move an existing workspace file into a folder. Moves the file itself; use Move F
| `fileId` | string | The file that was moved. |
| `folderPath` | string | The folder the file now lives in. |

{/* MANUAL-CONTENT-START:search_limits */}
## Search coverage and limits

Search indexes the complete extracted text of each eligible file. The source file and its extracted UTF-8 text must each be at most **25 MiB (26,214,400 bytes)**. Oversized files, unsupported binary formats, and documents that cannot be completely extracted within parser safety limits are excluded as whole files and counted in `skippedFiles`. Search never indexes only the first rows, lines, or characters. CSV search preserves decoded source text; spreadsheet search includes populated cells beyond the preview limits. Image-only documents require searchable text; search does not perform OCR.

Existing parser safeguards also apply to complete extraction. PDFs allow at most 10,000 pages, 20 MiB of extracted text, 250,000 characters on one page, and 60 seconds of extraction. Office archives allow at most 150 MiB expanded in total, 64 MiB for one archive entry, and 10,000 entries; malformed archives and excessive compression ratios are rejected. Hitting any of these limits excludes the whole file from search.

Updates are indexed asynchronously. `pendingFiles` and `failedFiles` indicate revisions that are not yet searchable; a new revision becomes searchable only when its full index is ready. An empty result proves absence only within the searched scope when `complete` is true and `skippedFiles` is zero.

Regex is evaluated against complete logical lines, including long lines, and cannot span line breaks. Returned lines may use a shortened preview. The result limit (up to 200 lines) and a 10-second query deadline limit an individual request, not the amount of text indexed. An expensive query fails explicitly instead of returning an apparently complete subset; narrow its literal text or folder scope and retry.

Search returns one result per matching logical line with `fileId`, 1-based `lineNumber`, and `text` (a bounded preview for long lines). An Agent can use **Search** to locate content, then **Get Content** with the returned `fileId`, `offset` near `lineNumber`, and a small `limit` to read surrounding context. These operations use the same complete-text parser mode. Line numbers for documents and spreadsheets refer to extracted text, not page numbers or worksheet row numbers. Re-run search if the file changes between calls.
{/* MANUAL-CONTENT-END */}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export function CoreFeatureCard({
const graphic = (
<div
aria-hidden={interactiveVisual ? undefined : true}
inert={!interactiveVisual}
className={cn(
'relative aspect-[5/6] overflow-hidden border border-[var(--border)] transition-colors duration-300 group-hover:border-[var(--border)] motion-reduce:transition-none',
LANDING_STAGE_RADIUS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { act, StrictMode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { renderToStaticMarkup } from 'react-dom/server'
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest'
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
import {
FeaturesRail,
foldScrollLeft,
Expand Down Expand Up @@ -74,6 +74,7 @@ afterEach(() => {
root = null
host?.remove()
host = null
vi.unstubAllGlobals()
})

function mount(strict = false): HTMLElement {
Expand Down Expand Up @@ -106,6 +107,34 @@ describe('foldScrollLeft', () => {
})

describe('FeaturesRail', () => {
it('waits until the rail approaches the viewport before adding the loop copies', () => {
let notify: IntersectionObserverCallback | undefined
const disconnect = vi.fn()
const observe = vi.fn()
vi.stubGlobal(
'IntersectionObserver',
class {
constructor(callback: IntersectionObserverCallback) {
notify = callback
}
observe = observe
disconnect = disconnect
}
)
const rail = mount()
expect(observe).toHaveBeenCalledWith(rail)
expect(rail.children).toHaveLength(3)
const observer = {} as IntersectionObserver
act(() => notify?.([{ isIntersecting: false } as IntersectionObserverEntry], observer))
expect(rail.children).toHaveLength(3)
act(() => notify?.([{ isIntersecting: true } as IntersectionObserverEntry], observer))
expect(rail.children).toHaveLength(9)
expect(rail.scrollLeft).toBe(SET)
expect(disconnect).toHaveBeenCalledOnce()
act(() => notify?.([{ isIntersecting: false } as IntersectionObserverEntry], observer))
expect(rail.children).toHaveLength(9)
})

it('server-renders the finite rail once, with the scroll chrome', () => {
const html = renderToStaticMarkup(
<FeaturesRail label='Core Sim features'>{cards()}</FeaturesRail>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ interface FeaturesRailProps {
/** Accessible name of the scrolling region. */
label: string
/**
* The cards, in order. Each becomes one slot; once JS runs the whole set is
* cloned on both sides so the rail loops.
* The cards, in order. Each becomes one slot; as the rail approaches the
* viewport the whole set is cloned on both sides so the rail loops.
*/
children: ReactNode
}
Expand All @@ -96,7 +96,7 @@ interface FeaturesRailProps {
* The homepage product rail: native horizontal scrolling that never ends.
*
* The server renders the set once, so the HTML - and any visit without JS - is
* the plain finite rail with the first card under the heading. After hydration
* the plain finite rail with the first card under the heading. Near the viewport
* the set is cloned once on each side, the scroll position jumps one set width
* before paint so nothing visibly moves (folded, so Strict Mode's second run of
* the effect lands on the same spot), and a passive scroll listener folds the
Expand Down Expand Up @@ -124,7 +124,22 @@ export function FeaturesRail({ label, children }: FeaturesRailProps) {
const cards = Children.toArray(children)

useEffect(() => {
setLooping(true)
const rail = railRef.current
if (!rail) return
if (typeof IntersectionObserver === 'undefined') {
setLooping(true)
return
}
const observer = new IntersectionObserver(
(entries) => {
if (!entries.some((entry) => entry.isIntersecting)) return
setLooping(true)
observer.disconnect()
},
{ rootMargin: '600px' }
)
observer.observe(rail)
return () => observer.disconnect()
}, [])

useLayoutEffect(() => {
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/(landing)/components/footer/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const RESOURCES_LINKS: FooterItem[] = [
/** Top model providers, sourced from the catalog so labels/hrefs never drift. */
const MODEL_LINKS: FooterItem[] = [
{ label: 'All Models', href: '/models' },
...MODEL_PROVIDERS_WITH_CATALOGS.slice(0, 8).map((provider) => ({
...MODEL_PROVIDERS_WITH_CATALOGS.slice(0, 7).map((provider) => ({
label: provider.name,
href: provider.href,
})),
Expand Down Expand Up @@ -119,7 +119,7 @@ const SOCIAL_LINKS: FooterItem[] = [
{ label: 'X (Twitter)', href: 'https://x.com/simdotai', external: true },
{
label: 'LinkedIn',
href: 'https://www.linkedin.com/company/simstudioai/',
href: 'https://www.linkedin.com/company/simdotai/',
external: true,
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,20 @@ const EMPTY_IDS: ReadonlySet<string> = new Set()
const ACTION_BUTTON_STYLES = [
'size-[24px] rounded-md p-0',
'border-none bg-transparent text-[var(--text-icon)]',
'hover-hover:bg-[var(--surface-5)] hover-hover:!text-[var(--text-primary)]',
'dark:hover-hover:bg-[var(--surface-4)]',
'transition-[background-color,color,opacity,transform] duration-150 active:scale-[0.96]',
'transition-[background-color,color,opacity,transform] duration-150',
'group-data-[node-selected]:text-[var(--surface-2)]',
'hover-hover:group-data-[node-selected]:bg-[var(--surface-2)]',
'hover-hover:group-data-[node-selected]:!text-[var(--text-primary)]',
].join(' ')

const FIRST_ACTION_STYLES =
"!w-[40px] [clip-path:path('M23.75_0A8_8_0_0_0_17.6_2.88L3.41_19.9A2.5_2.5_0_0_0_5.34_24L36_24A4_4_0_0_0_40_20L40_4A4_4_0_0_0_36_0Z')] [&>svg]:translate-x-[8px] [&>svg]:translate-y-px"

/** A 24px target even at MIN_ZOOM, extending above/left of the unchanged 40px painted slot. */
const RUN_ACTION_HIT_STYLES =
'group/run relative -ml-[14px] size-[54px] shrink-0 border-none bg-transparent! p-0'

/** The running run slot: graphite fill, inverse glyph - the editor's own treatment. */
const RUNNING_RUN_STYLES =
'!bg-[var(--text-secondary)] !text-[var(--text-inverse)] hover-hover:!bg-[var(--white)] hover-hover:!text-[var(--surface-inverted)]'
'!bg-[var(--text-secondary)] !text-[var(--text-inverse)] group-hover-hover/run:!bg-[var(--white)] group-hover-hover/run:!text-[var(--surface-inverted)]'
/** A bystander card's actions dim mid-run; the run/stop slot keeps its ordinary chrome. */
const BYSTANDER_ACTION_STYLES =
'!bg-transparent !opacity-25 hover-hover:!bg-transparent dark:hover-hover:!bg-transparent'
Expand Down Expand Up @@ -227,7 +227,7 @@ function PreviewActionBar({ block, running, workflowRunning, onRunToggle }: Prev
return (
<div
data-workflow-action-bar-swell=''
className='-top-[28px] pointer-events-auto absolute right-[24px] z-[40] h-[28px] w-fit overflow-hidden rounded-lg px-[0.2rem] py-0.5'
className='-top-[28px] pointer-events-auto absolute right-[24px] z-[40] h-[28px] w-fit rounded-lg px-[0.2rem] py-0.5'
>
<div className='pointer-events-none relative flex h-full flex-row items-center gap-[2px] opacity-0 transition-opacity duration-[30ms] [transition-timing-function:cubic-bezier(0.23,1,0.32,1)] group-data-[action-menu-ready]:pointer-events-auto group-data-[action-menu-ready]:opacity-100 group-data-[action-menu-ready]:duration-100'>
{sweeping && (
Expand All @@ -250,35 +250,42 @@ function PreviewActionBar({ block, running, workflowRunning, onRunToggle }: Prev
)}
<Tooltip.Root preferAbove>
<Tooltip.Trigger asChild>
<span className='inline-flex'>
<span className='inline-flex h-full items-end'>
<Button
type='button'
variant='ghost'
aria-label={workflowRunning ? 'Stop workflow' : `Run ${block.name}`}
className={cn(
ACTION_BUTTON_STYLES,
FIRST_ACTION_STYLES,
running && RUNNING_RUN_STYLES,
workflowRunning && 'group/run'
)}
className={RUN_ACTION_HIT_STYLES}
onClick={(event) => {
event.stopPropagation()
onRunToggle()
}}
>
{workflowRunning ? (
running ? (
<RunningActionIcon />
<span
className={cn(
'pointer-events-none absolute right-0 bottom-0 flex items-center justify-center',
ACTION_BUTTON_STYLES,
FIRST_ACTION_STYLES,
'group-hover-hover/run:!text-[var(--text-primary)] group-hover-hover/run:bg-[var(--surface-5)] dark:group-hover-hover/run:bg-[var(--surface-4)]',
'group-hover-hover/run:group-data-[node-selected]:!text-[var(--text-primary)] group-hover-hover/run:group-data-[node-selected]:bg-[var(--surface-2)]',
'group-active/run:scale-[0.96]',
running && RUNNING_RUN_STYLES
)}
>
{workflowRunning ? (
running ? (
<RunningActionIcon />
) : (
<Square
className='size-[11px] fill-current'
aria-hidden='true'
strokeWidth={0}
/>
)
) : (
<Square
className='size-[11px] fill-current'
aria-hidden='true'
strokeWidth={0}
/>
)
) : (
<PlayOutline className='size-[14px]' />
)}
<PlayOutline className='size-[14px]' />
)}
</span>
</Button>
</span>
</Tooltip.Trigger>
Expand All @@ -288,23 +295,22 @@ function PreviewActionBar({ block, running, workflowRunning, onRunToggle }: Prev
{inertActions.map(({ label, Icon }) => (
<Tooltip.Root key={label} preferAbove>
<Tooltip.Trigger asChild>
<Button
type='button'
variant='ghost'
aria-label={label}
className={cn(
ACTION_BUTTON_STYLES,
label === 'Delete' && LAST_ACTION_STYLES,
workflowRunning && !running && BYSTANDER_ACTION_STYLES,
sweeping && SWEEP_SLOT_STYLES
)}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
}}
>
<Icon className='size-[14px]' />
</Button>
<span className='inline-flex'>
<Button
type='button'
variant='ghost'
disabled
aria-label={`${label} unavailable in preview`}
className={cn(
ACTION_BUTTON_STYLES,
label === 'Delete' && LAST_ACTION_STYLES,
workflowRunning && !running && BYSTANDER_ACTION_STYLES,
sweeping && SWEEP_SLOT_STYLES
)}
>
<Icon className='size-[14px]' />
</Button>
</span>
</Tooltip.Trigger>
{!workflowRunning && <Tooltip.Content side='top'>{label}</Tooltip.Content>}
</Tooltip.Root>
Expand Down
Loading
Loading