Skip to content

Commit a54dcbe

Browse files
authored
v0.6.24: copilot feedback wiring, captcha fixes
2 parents 0b9019d + c2b12cf commit a54dcbe

File tree

22 files changed

+860
-420
lines changed

22 files changed

+860
-420
lines changed

apps/sim/app/(auth)/signup/signup-form.tsx

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,6 @@ function SignupFormContent({
9999
const [showEmailValidationError, setShowEmailValidationError] = useState(false)
100100
const [formError, setFormError] = useState<string | null>(null)
101101
const turnstileRef = useRef<TurnstileInstance>(null)
102-
const captchaResolveRef = useRef<((token: string) => void) | null>(null)
103-
const captchaRejectRef = useRef<((reason: Error) => void) | null>(null)
104102
const turnstileSiteKey = useMemo(() => getEnv('NEXT_PUBLIC_TURNSTILE_SITE_KEY'), [])
105103
const redirectUrl = useMemo(
106104
() => searchParams.get('redirect') || searchParams.get('callbackUrl') || '',
@@ -258,27 +256,14 @@ function SignupFormContent({
258256
let token: string | undefined
259257
const widget = turnstileRef.current
260258
if (turnstileSiteKey && widget) {
261-
let timeoutId: ReturnType<typeof setTimeout> | undefined
262259
try {
263260
widget.reset()
264-
token = await Promise.race([
265-
new Promise<string>((resolve, reject) => {
266-
captchaResolveRef.current = resolve
267-
captchaRejectRef.current = reject
268-
widget.execute()
269-
}),
270-
new Promise<string>((_, reject) => {
271-
timeoutId = setTimeout(() => reject(new Error('Captcha timed out')), 15_000)
272-
}),
273-
])
261+
widget.execute()
262+
token = await widget.getResponsePromise()
274263
} catch {
275264
setFormError('Captcha verification failed. Please try again.')
276265
setIsLoading(false)
277266
return
278-
} finally {
279-
clearTimeout(timeoutId)
280-
captchaResolveRef.current = null
281-
captchaRejectRef.current = null
282267
}
283268
}
284269

@@ -535,10 +520,7 @@ function SignupFormContent({
535520
<Turnstile
536521
ref={turnstileRef}
537522
siteKey={turnstileSiteKey}
538-
onSuccess={(token) => captchaResolveRef.current?.(token)}
539-
onError={() => captchaRejectRef.current?.(new Error('Captcha verification failed'))}
540-
onExpire={() => captchaRejectRef.current?.(new Error('Captcha token expired'))}
541-
options={{ execution: 'execute' }}
523+
options={{ execution: 'execute', appearance: 'execute' }}
542524
/>
543525
)}
544526

apps/sim/app/(landing)/models/[provider]/[model]/page.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
formatPrice,
1919
formatTokenCount,
2020
formatUpdatedAt,
21+
getEffectiveMaxOutputTokens,
2122
getModelBySlug,
2223
getPricingBounds,
2324
getProviderBySlug,
@@ -198,7 +199,8 @@ export default async function ModelPage({
198199
</div>
199200

200201
<p className='max-w-[820px] text-[17px] text-[var(--landing-text-muted)] leading-relaxed'>
201-
{model.summary} {model.bestFor}
202+
{model.summary}
203+
{model.bestFor ? ` ${model.bestFor}` : ''}
202204
</p>
203205

204206
<div className='mt-8 flex flex-wrap gap-3'>
@@ -229,13 +231,11 @@ export default async function ModelPage({
229231
? `${formatPrice(model.pricing.cachedInput)}/1M`
230232
: 'N/A'
231233
}
232-
compact
233234
/>
234235
<StatCard label='Output price' value={`${formatPrice(model.pricing.output)}/1M`} />
235236
<StatCard
236237
label='Context window'
237238
value={model.contextWindow ? formatTokenCount(model.contextWindow) : 'Unknown'}
238-
compact
239239
/>
240240
</section>
241241

@@ -280,12 +280,12 @@ export default async function ModelPage({
280280
label='Max output'
281281
value={
282282
model.capabilities.maxOutputTokens
283-
? `${formatTokenCount(model.capabilities.maxOutputTokens)} tokens`
284-
: 'Standard defaults'
283+
? `${formatTokenCount(getEffectiveMaxOutputTokens(model.capabilities))} tokens`
284+
: 'Not published'
285285
}
286286
/>
287287
<DetailItem label='Provider' value={provider.name} />
288-
<DetailItem label='Best for' value={model.bestFor} />
288+
{model.bestFor ? <DetailItem label='Best for' value={model.bestFor} /> : null}
289289
</div>
290290
</section>
291291

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { buildModelCapabilityFacts, getEffectiveMaxOutputTokens, getModelBySlug } from './utils'
3+
4+
describe('model catalog capability facts', () => {
5+
it.concurrent(
6+
'shows structured outputs support and published max output tokens for gpt-4o',
7+
() => {
8+
const model = getModelBySlug('openai', 'gpt-4o')
9+
10+
expect(model).not.toBeNull()
11+
expect(model).toBeDefined()
12+
13+
const capabilityFacts = buildModelCapabilityFacts(model!)
14+
const structuredOutputs = capabilityFacts.find((fact) => fact.label === 'Structured outputs')
15+
const maxOutputTokens = capabilityFacts.find((fact) => fact.label === 'Max output tokens')
16+
17+
expect(getEffectiveMaxOutputTokens(model!.capabilities)).toBe(16384)
18+
expect(structuredOutputs?.value).toBe('Supported')
19+
expect(maxOutputTokens?.value).toBe('16k')
20+
}
21+
)
22+
23+
it.concurrent('preserves native structured outputs labeling for claude models', () => {
24+
const model = getModelBySlug('anthropic', 'claude-sonnet-4-6')
25+
26+
expect(model).not.toBeNull()
27+
expect(model).toBeDefined()
28+
29+
const capabilityFacts = buildModelCapabilityFacts(model!)
30+
const structuredOutputs = capabilityFacts.find((fact) => fact.label === 'Structured outputs')
31+
32+
expect(structuredOutputs?.value).toBe('Supported (native)')
33+
})
34+
35+
it.concurrent('does not invent a max output token limit when one is not published', () => {
36+
expect(getEffectiveMaxOutputTokens({})).toBeNull()
37+
})
38+
39+
it.concurrent('keeps best-for copy for clearly differentiated models only', () => {
40+
const researchModel = getModelBySlug('google', 'deep-research-pro-preview-12-2025')
41+
const generalModel = getModelBySlug('xai', 'grok-4-latest')
42+
43+
expect(researchModel).not.toBeNull()
44+
expect(generalModel).not.toBeNull()
45+
46+
expect(researchModel?.bestFor).toContain('research workflows')
47+
expect(generalModel?.bestFor).toBeUndefined()
48+
})
49+
})

apps/sim/app/(landing)/models/utils.ts

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export interface CatalogModel {
112112
capabilities: ModelCapabilities
113113
capabilityTags: string[]
114114
summary: string
115-
bestFor: string
115+
bestFor?: string
116116
searchText: string
117117
}
118118

@@ -190,6 +190,14 @@ export function formatCapabilityBoolean(
190190
return value ? positive : negative
191191
}
192192

193+
function supportsCatalogStructuredOutputs(capabilities: ModelCapabilities): boolean {
194+
return !capabilities.deepResearch
195+
}
196+
197+
export function getEffectiveMaxOutputTokens(capabilities: ModelCapabilities): number | null {
198+
return capabilities.maxOutputTokens ?? null
199+
}
200+
193201
function trimTrailingZeros(value: string): string {
194202
return value.replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1')
195203
}
@@ -326,7 +334,7 @@ function buildCapabilityTags(capabilities: ModelCapabilities): string[] {
326334
tags.push('Tool choice')
327335
}
328336

329-
if (capabilities.nativeStructuredOutputs) {
337+
if (supportsCatalogStructuredOutputs(capabilities)) {
330338
tags.push('Structured outputs')
331339
}
332340

@@ -365,7 +373,7 @@ function buildBestForLine(model: {
365373
pricing: PricingInfo
366374
capabilities: ModelCapabilities
367375
contextWindow: number | null
368-
}): string {
376+
}): string | null {
369377
const { pricing, capabilities, contextWindow } = model
370378

371379
if (capabilities.deepResearch) {
@@ -376,10 +384,6 @@ function buildBestForLine(model: {
376384
return 'Best for reasoning-heavy tasks that need more deliberate model control.'
377385
}
378386

379-
if (pricing.input <= 0.2 && pricing.output <= 1.25) {
380-
return 'Best for cost-sensitive automations, background tasks, and high-volume workloads.'
381-
}
382-
383387
if (contextWindow && contextWindow >= 1000000) {
384388
return 'Best for long-context retrieval, large documents, and high-memory workflows.'
385389
}
@@ -388,7 +392,11 @@ function buildBestForLine(model: {
388392
return 'Best for production workflows that need reliable typed outputs.'
389393
}
390394

391-
return 'Best for general-purpose AI workflows inside Sim.'
395+
if (pricing.input <= 0.2 && pricing.output <= 1.25) {
396+
return 'Best for cost-sensitive automations, background tasks, and high-volume workloads.'
397+
}
398+
399+
return null
392400
}
393401

394402
function buildModelSummary(
@@ -437,6 +445,11 @@ const rawProviders = Object.values(PROVIDER_DEFINITIONS).map((provider) => {
437445
const shortId = stripProviderPrefix(provider.id, model.id)
438446
const mergedCapabilities = { ...provider.capabilities, ...model.capabilities }
439447
const capabilityTags = buildCapabilityTags(mergedCapabilities)
448+
const bestFor = buildBestForLine({
449+
pricing: model.pricing,
450+
capabilities: mergedCapabilities,
451+
contextWindow: model.contextWindow ?? null,
452+
})
440453
const displayName = formatModelDisplayName(provider.id, model.id)
441454
const modelSlug = slugify(shortId)
442455
const href = `/models/${providerSlug}/${modelSlug}`
@@ -461,11 +474,7 @@ const rawProviders = Object.values(PROVIDER_DEFINITIONS).map((provider) => {
461474
model.contextWindow ?? null,
462475
capabilityTags
463476
),
464-
bestFor: buildBestForLine({
465-
pricing: model.pricing,
466-
capabilities: mergedCapabilities,
467-
contextWindow: model.contextWindow ?? null,
468-
}),
477+
...(bestFor ? { bestFor } : {}),
469478
searchText: [
470479
provider.name,
471480
providerDisplayName,
@@ -683,6 +692,7 @@ export function buildModelFaqs(provider: CatalogProvider, model: CatalogModel):
683692

684693
export function buildModelCapabilityFacts(model: CatalogModel): CapabilityFact[] {
685694
const { capabilities } = model
695+
const supportsStructuredOutputs = supportsCatalogStructuredOutputs(capabilities)
686696

687697
return [
688698
{
@@ -711,7 +721,11 @@ export function buildModelCapabilityFacts(model: CatalogModel): CapabilityFact[]
711721
},
712722
{
713723
label: 'Structured outputs',
714-
value: formatCapabilityBoolean(capabilities.nativeStructuredOutputs),
724+
value: supportsStructuredOutputs
725+
? capabilities.nativeStructuredOutputs
726+
? 'Supported (native)'
727+
: 'Supported'
728+
: 'Not supported',
715729
},
716730
{
717731
label: 'Tool choice',
@@ -732,8 +746,8 @@ export function buildModelCapabilityFacts(model: CatalogModel): CapabilityFact[]
732746
{
733747
label: 'Max output tokens',
734748
value: capabilities.maxOutputTokens
735-
? formatTokenCount(capabilities.maxOutputTokens)
736-
: 'Standard defaults',
749+
? formatTokenCount(getEffectiveMaxOutputTokens(capabilities))
750+
: 'Not published',
737751
},
738752
]
739753
}
@@ -752,8 +766,8 @@ export function getProviderCapabilitySummary(provider: CatalogProvider): Capabil
752766
const reasoningCount = provider.models.filter(
753767
(model) => model.capabilities.reasoningEffort || model.capabilities.thinking
754768
).length
755-
const structuredCount = provider.models.filter(
756-
(model) => model.capabilities.nativeStructuredOutputs
769+
const structuredCount = provider.models.filter((model) =>
770+
supportsCatalogStructuredOutputs(model.capabilities)
757771
).length
758772
const deepResearchCount = provider.models.filter(
759773
(model) => model.capabilities.deepResearch

apps/sim/app/llms.txt/route.ts

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,44 @@
11
import { getBaseUrl } from '@/lib/core/utils/urls'
22

3-
export async function GET() {
3+
export function GET() {
44
const baseUrl = getBaseUrl()
55

6-
const llmsContent = `# Sim
6+
const content = `# Sim
77
8-
> Sim is the open-source platform to build AI agents and run your agentic workforce. Connect 1,000+ integrations and LLMs to deploy and orchestrate agentic workflows.
8+
> Sim is the open-source platform to build AI agents and run your agentic workforce. Connect integrations and LLMs to deploy and orchestrate agentic workflows.
99
10-
Sim lets teams create agents, workflows, knowledge bases, tables, and docs. Over 100,000 builders use Sim — from startups to Fortune 500 companies. SOC2 compliant.
10+
Sim lets teams create agents, workflows, knowledge bases, tables, and docs. It supports both product discovery pages and deeper technical documentation.
1111
12-
## Core Pages
12+
## Preferred URLs
1313
14-
- [Homepage](${baseUrl}): Product overview, features, and pricing
14+
- [Homepage](${baseUrl}): Product overview and primary entry point
15+
- [Integrations directory](${baseUrl}/integrations): Public catalog of integrations and automation capabilities
16+
- [Models directory](${baseUrl}/models): Public catalog of AI models, pricing, context windows, and capabilities
17+
- [Blog](${baseUrl}/blog): Announcements, guides, and product context
1518
- [Changelog](${baseUrl}/changelog): Product updates and release notes
16-
- [Sim Blog](${baseUrl}/blog): Announcements, insights, and guides
1719
1820
## Documentation
1921
20-
- [Documentation](https://docs.sim.ai): Complete guides and API reference
21-
- [Quickstart](https://docs.sim.ai/quickstart): Get started in 5 minutes
22-
- [API Reference](https://docs.sim.ai/api): REST API documentation
22+
- [Documentation](https://docs.sim.ai): Product guides and technical reference
23+
- [Quickstart](https://docs.sim.ai/quickstart): Fastest path to getting started
24+
- [API Reference](https://docs.sim.ai/api): API documentation
2325
2426
## Key Concepts
2527
2628
- **Workspace**: Container for workflows, data sources, and executions
2729
- **Workflow**: Directed graph of blocks defining an agentic process
28-
- **Block**: Individual step (LLM call, tool call, HTTP request, code execution)
30+
- **Block**: Individual step such as an LLM call, tool call, HTTP request, or code execution
2931
- **Trigger**: Event or schedule that initiates workflow execution
3032
- **Execution**: A single run of a workflow with logs and outputs
31-
- **Knowledge Base**: Vector-indexed document store for retrieval-augmented generation
33+
- **Knowledge Base**: Document store used for retrieval-augmented generation
3234
3335
## Capabilities
3436
3537
- AI agent creation and deployment
3638
- Agentic workflow orchestration
37-
- 1,000+ integrations (Slack, Gmail, Notion, Airtable, databases, and more)
38-
- Multi-model LLM orchestration (OpenAI, Anthropic, Google, Mistral, xAI, Perplexity)
39-
- Knowledge base creation with retrieval-augmented generation (RAG)
39+
- Integrations across business tools, databases, and communication platforms
40+
- Multi-model LLM orchestration
41+
- Knowledge bases and retrieval-augmented generation
4042
- Table creation and management
4143
- Document creation and processing
4244
- Scheduled and webhook-triggered executions
@@ -45,24 +47,19 @@ Sim lets teams create agents, workflows, knowledge bases, tables, and docs. Over
4547
4648
- AI agent deployment and orchestration
4749
- Knowledge bases and RAG pipelines
48-
- Document creation and processing
4950
- Customer support automation
50-
- Internal operations (sales, marketing, legal, finance)
51+
- Internal operations workflows across sales, marketing, legal, and finance
5152
52-
## Links
53+
## Additional Links
5354
5455
- [GitHub Repository](https://github.com/simstudioai/sim): Open-source codebase
55-
- [Discord Community](https://discord.gg/Hr4UWYEcTT): Get help and connect with 100,000+ builders
56-
- [X/Twitter](https://x.com/simdotai): Product updates and announcements
57-
58-
## Optional
59-
60-
- [Careers](https://jobs.ashbyhq.com/sim): Join the Sim team
56+
- [Docs](https://docs.sim.ai): Canonical documentation source
6157
- [Terms of Service](${baseUrl}/terms): Legal terms
6258
- [Privacy Policy](${baseUrl}/privacy): Data handling practices
59+
- [Sitemap](${baseUrl}/sitemap.xml): Public URL inventory
6360
`
6461

65-
return new Response(llmsContent, {
62+
return new Response(content, {
6663
headers: {
6764
'Content-Type': 'text/markdown; charset=utf-8',
6865
'Cache-Control': 'public, max-age=86400, s-maxage=86400',

0 commit comments

Comments
 (0)