diff --git a/apps/sim/app/(auth)/signup/signup-form.tsx b/apps/sim/app/(auth)/signup/signup-form.tsx
index 7ddf2abbcaa..1b0bd50c05b 100644
--- a/apps/sim/app/(auth)/signup/signup-form.tsx
+++ b/apps/sim/app/(auth)/signup/signup-form.tsx
@@ -99,8 +99,6 @@ function SignupFormContent({
const [showEmailValidationError, setShowEmailValidationError] = useState(false)
const [formError, setFormError] = useState(null)
const turnstileRef = useRef(null)
- const captchaResolveRef = useRef<((token: string) => void) | null>(null)
- const captchaRejectRef = useRef<((reason: Error) => void) | null>(null)
const turnstileSiteKey = useMemo(() => getEnv('NEXT_PUBLIC_TURNSTILE_SITE_KEY'), [])
const redirectUrl = useMemo(
() => searchParams.get('redirect') || searchParams.get('callbackUrl') || '',
@@ -258,27 +256,14 @@ function SignupFormContent({
let token: string | undefined
const widget = turnstileRef.current
if (turnstileSiteKey && widget) {
- let timeoutId: ReturnType | undefined
try {
widget.reset()
- token = await Promise.race([
- new Promise((resolve, reject) => {
- captchaResolveRef.current = resolve
- captchaRejectRef.current = reject
- widget.execute()
- }),
- new Promise((_, reject) => {
- timeoutId = setTimeout(() => reject(new Error('Captcha timed out')), 15_000)
- }),
- ])
+ widget.execute()
+ token = await widget.getResponsePromise()
} catch {
setFormError('Captcha verification failed. Please try again.')
setIsLoading(false)
return
- } finally {
- clearTimeout(timeoutId)
- captchaResolveRef.current = null
- captchaRejectRef.current = null
}
}
@@ -535,10 +520,7 @@ function SignupFormContent({
captchaResolveRef.current?.(token)}
- onError={() => captchaRejectRef.current?.(new Error('Captcha verification failed'))}
- onExpire={() => captchaRejectRef.current?.(new Error('Captcha token expired'))}
- options={{ execution: 'execute' }}
+ options={{ execution: 'execute', appearance: 'execute' }}
/>
)}
diff --git a/apps/sim/app/(landing)/models/[provider]/[model]/page.tsx b/apps/sim/app/(landing)/models/[provider]/[model]/page.tsx
index fd7557e37c7..c539d739daf 100644
--- a/apps/sim/app/(landing)/models/[provider]/[model]/page.tsx
+++ b/apps/sim/app/(landing)/models/[provider]/[model]/page.tsx
@@ -18,6 +18,7 @@ import {
formatPrice,
formatTokenCount,
formatUpdatedAt,
+ getEffectiveMaxOutputTokens,
getModelBySlug,
getPricingBounds,
getProviderBySlug,
@@ -198,7 +199,8 @@ export default async function ModelPage({
- {model.summary} {model.bestFor}
+ {model.summary}
+ {model.bestFor ? ` ${model.bestFor}` : ''}
@@ -229,13 +231,11 @@ export default async function ModelPage({
? `${formatPrice(model.pricing.cachedInput)}/1M`
: 'N/A'
}
- compact
/>
@@ -280,12 +280,12 @@ export default async function ModelPage({
label='Max output'
value={
model.capabilities.maxOutputTokens
- ? `${formatTokenCount(model.capabilities.maxOutputTokens)} tokens`
- : 'Standard defaults'
+ ? `${formatTokenCount(getEffectiveMaxOutputTokens(model.capabilities))} tokens`
+ : 'Not published'
}
/>
-
+ {model.bestFor ? : null}
diff --git a/apps/sim/app/(landing)/models/utils.test.ts b/apps/sim/app/(landing)/models/utils.test.ts
new file mode 100644
index 00000000000..894c74500c9
--- /dev/null
+++ b/apps/sim/app/(landing)/models/utils.test.ts
@@ -0,0 +1,49 @@
+import { describe, expect, it } from 'vitest'
+import { buildModelCapabilityFacts, getEffectiveMaxOutputTokens, getModelBySlug } from './utils'
+
+describe('model catalog capability facts', () => {
+ it.concurrent(
+ 'shows structured outputs support and published max output tokens for gpt-4o',
+ () => {
+ const model = getModelBySlug('openai', 'gpt-4o')
+
+ expect(model).not.toBeNull()
+ expect(model).toBeDefined()
+
+ const capabilityFacts = buildModelCapabilityFacts(model!)
+ const structuredOutputs = capabilityFacts.find((fact) => fact.label === 'Structured outputs')
+ const maxOutputTokens = capabilityFacts.find((fact) => fact.label === 'Max output tokens')
+
+ expect(getEffectiveMaxOutputTokens(model!.capabilities)).toBe(16384)
+ expect(structuredOutputs?.value).toBe('Supported')
+ expect(maxOutputTokens?.value).toBe('16k')
+ }
+ )
+
+ it.concurrent('preserves native structured outputs labeling for claude models', () => {
+ const model = getModelBySlug('anthropic', 'claude-sonnet-4-6')
+
+ expect(model).not.toBeNull()
+ expect(model).toBeDefined()
+
+ const capabilityFacts = buildModelCapabilityFacts(model!)
+ const structuredOutputs = capabilityFacts.find((fact) => fact.label === 'Structured outputs')
+
+ expect(structuredOutputs?.value).toBe('Supported (native)')
+ })
+
+ it.concurrent('does not invent a max output token limit when one is not published', () => {
+ expect(getEffectiveMaxOutputTokens({})).toBeNull()
+ })
+
+ it.concurrent('keeps best-for copy for clearly differentiated models only', () => {
+ const researchModel = getModelBySlug('google', 'deep-research-pro-preview-12-2025')
+ const generalModel = getModelBySlug('xai', 'grok-4-latest')
+
+ expect(researchModel).not.toBeNull()
+ expect(generalModel).not.toBeNull()
+
+ expect(researchModel?.bestFor).toContain('research workflows')
+ expect(generalModel?.bestFor).toBeUndefined()
+ })
+})
diff --git a/apps/sim/app/(landing)/models/utils.ts b/apps/sim/app/(landing)/models/utils.ts
index cdf79f87b7c..8e649c95c6b 100644
--- a/apps/sim/app/(landing)/models/utils.ts
+++ b/apps/sim/app/(landing)/models/utils.ts
@@ -112,7 +112,7 @@ export interface CatalogModel {
capabilities: ModelCapabilities
capabilityTags: string[]
summary: string
- bestFor: string
+ bestFor?: string
searchText: string
}
@@ -190,6 +190,14 @@ export function formatCapabilityBoolean(
return value ? positive : negative
}
+function supportsCatalogStructuredOutputs(capabilities: ModelCapabilities): boolean {
+ return !capabilities.deepResearch
+}
+
+export function getEffectiveMaxOutputTokens(capabilities: ModelCapabilities): number | null {
+ return capabilities.maxOutputTokens ?? null
+}
+
function trimTrailingZeros(value: string): string {
return value.replace(/\.0+$/, '').replace(/(\.\d*?)0+$/, '$1')
}
@@ -326,7 +334,7 @@ function buildCapabilityTags(capabilities: ModelCapabilities): string[] {
tags.push('Tool choice')
}
- if (capabilities.nativeStructuredOutputs) {
+ if (supportsCatalogStructuredOutputs(capabilities)) {
tags.push('Structured outputs')
}
@@ -365,7 +373,7 @@ function buildBestForLine(model: {
pricing: PricingInfo
capabilities: ModelCapabilities
contextWindow: number | null
-}): string {
+}): string | null {
const { pricing, capabilities, contextWindow } = model
if (capabilities.deepResearch) {
@@ -376,10 +384,6 @@ function buildBestForLine(model: {
return 'Best for reasoning-heavy tasks that need more deliberate model control.'
}
- if (pricing.input <= 0.2 && pricing.output <= 1.25) {
- return 'Best for cost-sensitive automations, background tasks, and high-volume workloads.'
- }
-
if (contextWindow && contextWindow >= 1000000) {
return 'Best for long-context retrieval, large documents, and high-memory workflows.'
}
@@ -388,7 +392,11 @@ function buildBestForLine(model: {
return 'Best for production workflows that need reliable typed outputs.'
}
- return 'Best for general-purpose AI workflows inside Sim.'
+ if (pricing.input <= 0.2 && pricing.output <= 1.25) {
+ return 'Best for cost-sensitive automations, background tasks, and high-volume workloads.'
+ }
+
+ return null
}
function buildModelSummary(
@@ -437,6 +445,11 @@ const rawProviders = Object.values(PROVIDER_DEFINITIONS).map((provider) => {
const shortId = stripProviderPrefix(provider.id, model.id)
const mergedCapabilities = { ...provider.capabilities, ...model.capabilities }
const capabilityTags = buildCapabilityTags(mergedCapabilities)
+ const bestFor = buildBestForLine({
+ pricing: model.pricing,
+ capabilities: mergedCapabilities,
+ contextWindow: model.contextWindow ?? null,
+ })
const displayName = formatModelDisplayName(provider.id, model.id)
const modelSlug = slugify(shortId)
const href = `/models/${providerSlug}/${modelSlug}`
@@ -461,11 +474,7 @@ const rawProviders = Object.values(PROVIDER_DEFINITIONS).map((provider) => {
model.contextWindow ?? null,
capabilityTags
),
- bestFor: buildBestForLine({
- pricing: model.pricing,
- capabilities: mergedCapabilities,
- contextWindow: model.contextWindow ?? null,
- }),
+ ...(bestFor ? { bestFor } : {}),
searchText: [
provider.name,
providerDisplayName,
@@ -683,6 +692,7 @@ export function buildModelFaqs(provider: CatalogProvider, model: CatalogModel):
export function buildModelCapabilityFacts(model: CatalogModel): CapabilityFact[] {
const { capabilities } = model
+ const supportsStructuredOutputs = supportsCatalogStructuredOutputs(capabilities)
return [
{
@@ -711,7 +721,11 @@ export function buildModelCapabilityFacts(model: CatalogModel): CapabilityFact[]
},
{
label: 'Structured outputs',
- value: formatCapabilityBoolean(capabilities.nativeStructuredOutputs),
+ value: supportsStructuredOutputs
+ ? capabilities.nativeStructuredOutputs
+ ? 'Supported (native)'
+ : 'Supported'
+ : 'Not supported',
},
{
label: 'Tool choice',
@@ -732,8 +746,8 @@ export function buildModelCapabilityFacts(model: CatalogModel): CapabilityFact[]
{
label: 'Max output tokens',
value: capabilities.maxOutputTokens
- ? formatTokenCount(capabilities.maxOutputTokens)
- : 'Standard defaults',
+ ? formatTokenCount(getEffectiveMaxOutputTokens(capabilities))
+ : 'Not published',
},
]
}
@@ -752,8 +766,8 @@ export function getProviderCapabilitySummary(provider: CatalogProvider): Capabil
const reasoningCount = provider.models.filter(
(model) => model.capabilities.reasoningEffort || model.capabilities.thinking
).length
- const structuredCount = provider.models.filter(
- (model) => model.capabilities.nativeStructuredOutputs
+ const structuredCount = provider.models.filter((model) =>
+ supportsCatalogStructuredOutputs(model.capabilities)
).length
const deepResearchCount = provider.models.filter(
(model) => model.capabilities.deepResearch
diff --git a/apps/sim/app/llms.txt/route.ts b/apps/sim/app/llms.txt/route.ts
index 79c79d086ec..89fbc5a67f4 100644
--- a/apps/sim/app/llms.txt/route.ts
+++ b/apps/sim/app/llms.txt/route.ts
@@ -1,42 +1,44 @@
import { getBaseUrl } from '@/lib/core/utils/urls'
-export async function GET() {
+export function GET() {
const baseUrl = getBaseUrl()
- const llmsContent = `# Sim
+ const content = `# Sim
-> 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.
+> 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.
-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.
+Sim lets teams create agents, workflows, knowledge bases, tables, and docs. It supports both product discovery pages and deeper technical documentation.
-## Core Pages
+## Preferred URLs
-- [Homepage](${baseUrl}): Product overview, features, and pricing
+- [Homepage](${baseUrl}): Product overview and primary entry point
+- [Integrations directory](${baseUrl}/integrations): Public catalog of integrations and automation capabilities
+- [Models directory](${baseUrl}/models): Public catalog of AI models, pricing, context windows, and capabilities
+- [Blog](${baseUrl}/blog): Announcements, guides, and product context
- [Changelog](${baseUrl}/changelog): Product updates and release notes
-- [Sim Blog](${baseUrl}/blog): Announcements, insights, and guides
## Documentation
-- [Documentation](https://docs.sim.ai): Complete guides and API reference
-- [Quickstart](https://docs.sim.ai/quickstart): Get started in 5 minutes
-- [API Reference](https://docs.sim.ai/api): REST API documentation
+- [Documentation](https://docs.sim.ai): Product guides and technical reference
+- [Quickstart](https://docs.sim.ai/quickstart): Fastest path to getting started
+- [API Reference](https://docs.sim.ai/api): API documentation
## Key Concepts
- **Workspace**: Container for workflows, data sources, and executions
- **Workflow**: Directed graph of blocks defining an agentic process
-- **Block**: Individual step (LLM call, tool call, HTTP request, code execution)
+- **Block**: Individual step such as an LLM call, tool call, HTTP request, or code execution
- **Trigger**: Event or schedule that initiates workflow execution
- **Execution**: A single run of a workflow with logs and outputs
-- **Knowledge Base**: Vector-indexed document store for retrieval-augmented generation
+- **Knowledge Base**: Document store used for retrieval-augmented generation
## Capabilities
- AI agent creation and deployment
- Agentic workflow orchestration
-- 1,000+ integrations (Slack, Gmail, Notion, Airtable, databases, and more)
-- Multi-model LLM orchestration (OpenAI, Anthropic, Google, Mistral, xAI, Perplexity)
-- Knowledge base creation with retrieval-augmented generation (RAG)
+- Integrations across business tools, databases, and communication platforms
+- Multi-model LLM orchestration
+- Knowledge bases and retrieval-augmented generation
- Table creation and management
- Document creation and processing
- Scheduled and webhook-triggered executions
@@ -45,24 +47,19 @@ Sim lets teams create agents, workflows, knowledge bases, tables, and docs. Over
- AI agent deployment and orchestration
- Knowledge bases and RAG pipelines
-- Document creation and processing
- Customer support automation
-- Internal operations (sales, marketing, legal, finance)
+- Internal operations workflows across sales, marketing, legal, and finance
-## Links
+## Additional Links
- [GitHub Repository](https://github.com/simstudioai/sim): Open-source codebase
-- [Discord Community](https://discord.gg/Hr4UWYEcTT): Get help and connect with 100,000+ builders
-- [X/Twitter](https://x.com/simdotai): Product updates and announcements
-
-## Optional
-
-- [Careers](https://jobs.ashbyhq.com/sim): Join the Sim team
+- [Docs](https://docs.sim.ai): Canonical documentation source
- [Terms of Service](${baseUrl}/terms): Legal terms
- [Privacy Policy](${baseUrl}/privacy): Data handling practices
+- [Sitemap](${baseUrl}/sitemap.xml): Public URL inventory
`
- return new Response(llmsContent, {
+ return new Response(content, {
headers: {
'Content-Type': 'text/markdown; charset=utf-8',
'Cache-Control': 'public, max-age=86400, s-maxage=86400',
diff --git a/apps/sim/app/sitemap.ts b/apps/sim/app/sitemap.ts
index 6c95b859370..a558525950e 100644
--- a/apps/sim/app/sitemap.ts
+++ b/apps/sim/app/sitemap.ts
@@ -8,6 +8,34 @@ export default async function sitemap(): Promise {
const baseUrl = getBaseUrl()
const now = new Date()
+ const integrationPages: MetadataRoute.Sitemap = integrations.map((integration) => ({
+ url: `${baseUrl}/integrations/${integration.slug}`,
+ lastModified: now,
+ }))
+ const modelHubPages: MetadataRoute.Sitemap = [
+ {
+ url: `${baseUrl}/integrations`,
+ lastModified: now,
+ },
+ {
+ url: `${baseUrl}/models`,
+ lastModified: now,
+ },
+ {
+ url: `${baseUrl}/partners`,
+ lastModified: now,
+ },
+ ]
+ const providerPages: MetadataRoute.Sitemap = MODEL_PROVIDERS_WITH_CATALOGS.map((provider) => ({
+ url: `${baseUrl}${provider.href}`,
+ lastModified: new Date(
+ Math.max(...provider.models.map((model) => new Date(model.pricing.updatedAt).getTime()))
+ ),
+ }))
+ const modelPages: MetadataRoute.Sitemap = ALL_CATALOG_MODELS.map((model) => ({
+ url: `${baseUrl}${model.href}`,
+ lastModified: new Date(model.pricing.updatedAt),
+ }))
const staticPages: MetadataRoute.Sitemap = [
{
@@ -26,14 +54,6 @@ export default async function sitemap(): Promise {
// url: `${baseUrl}/templates`,
// lastModified: now,
// },
- {
- url: `${baseUrl}/integrations`,
- lastModified: now,
- },
- {
- url: `${baseUrl}/models`,
- lastModified: now,
- },
{
url: `${baseUrl}/changelog`,
lastModified: now,
@@ -54,20 +74,12 @@ export default async function sitemap(): Promise {
lastModified: new Date(p.updated ?? p.date),
}))
- const integrationPages: MetadataRoute.Sitemap = integrations.map((i) => ({
- url: `${baseUrl}/integrations/${i.slug}`,
- lastModified: now,
- }))
-
- const providerPages: MetadataRoute.Sitemap = MODEL_PROVIDERS_WITH_CATALOGS.map((provider) => ({
- url: `${baseUrl}${provider.href}`,
- lastModified: now,
- }))
-
- const modelPages: MetadataRoute.Sitemap = ALL_CATALOG_MODELS.map((model) => ({
- url: `${baseUrl}${model.href}`,
- lastModified: new Date(model.pricing.updatedAt),
- }))
-
- return [...staticPages, ...blogPages, ...integrationPages, ...providerPages, ...modelPages]
+ return [
+ ...staticPages,
+ ...modelHubPages,
+ ...integrationPages,
+ ...providerPages,
+ ...modelPages,
+ ...blogPages,
+ ]
}
diff --git a/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx b/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx
index cda3f8c9ccb..77607befa95 100644
--- a/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx
@@ -1,22 +1,59 @@
'use client'
import { useCallback, useEffect, useRef, useState } from 'react'
-import { Check, Copy, Ellipsis, Hash } from 'lucide-react'
import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
+ Button,
+ Check,
+ Copy,
+ Modal,
+ ModalBody,
+ ModalContent,
+ ModalFooter,
+ ModalHeader,
+ Textarea,
+ ThumbsDown,
+ ThumbsUp,
} from '@/components/emcn'
+import { useSubmitCopilotFeedback } from '@/hooks/queries/copilot-feedback'
+
+const SPECIAL_TAGS = 'thinking|options|usage_upgrade|credential|mothership-error|file'
+
+function toPlainText(raw: string): string {
+ return (
+ raw
+ // Strip special tags and their contents
+ .replace(new RegExp(`<\\/?(${SPECIAL_TAGS})(?:>[\\s\\S]*?<\\/(${SPECIAL_TAGS})>|>)`, 'g'), '')
+ // Strip markdown
+ .replace(/^#{1,6}\s+/gm, '')
+ .replace(/\*\*(.+?)\*\*/g, '$1')
+ .replace(/\*(.+?)\*/g, '$1')
+ .replace(/`{3}[\s\S]*?`{3}/g, '')
+ .replace(/`(.+?)`/g, '$1')
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
+ .replace(/^[>\-*]\s+/gm, '')
+ .replace(/!\[[^\]]*\]\([^)]+\)/g, '')
+ // Normalize whitespace
+ .replace(/\n{3,}/g, '\n\n')
+ .trim()
+ )
+}
+
+const ICON_CLASS = 'h-[14px] w-[14px]'
+const BUTTON_CLASS =
+ 'flex h-[26px] w-[26px] items-center justify-center rounded-[6px] text-[var(--text-icon)] transition-colors hover-hover:bg-[var(--surface-hover)] focus-visible:outline-none'
interface MessageActionsProps {
content: string
- requestId?: string
+ chatId?: string
+ userQuery?: string
}
-export function MessageActions({ content, requestId }: MessageActionsProps) {
- const [copied, setCopied] = useState<'message' | 'request' | null>(null)
+export function MessageActions({ content, chatId, userQuery }: MessageActionsProps) {
+ const [copied, setCopied] = useState(false)
+ const [pendingFeedback, setPendingFeedback] = useState<'up' | 'down' | null>(null)
+ const [feedbackText, setFeedbackText] = useState('')
const resetTimeoutRef = useRef(null)
+ const submitFeedback = useSubmitCopilotFeedback()
useEffect(() => {
return () => {
@@ -26,59 +63,119 @@ export function MessageActions({ content, requestId }: MessageActionsProps) {
}
}, [])
- const copyToClipboard = useCallback(async (text: string, type: 'message' | 'request') => {
+ const copyToClipboard = useCallback(async () => {
+ if (!content) return
+ const text = toPlainText(content)
+ if (!text) return
try {
await navigator.clipboard.writeText(text)
- setCopied(type)
+ setCopied(true)
if (resetTimeoutRef.current !== null) {
window.clearTimeout(resetTimeoutRef.current)
}
- resetTimeoutRef.current = window.setTimeout(() => setCopied(null), 1500)
+ resetTimeoutRef.current = window.setTimeout(() => setCopied(false), 1500)
} catch {
+ /* clipboard unavailable */
+ }
+ }, [content])
+
+ const handleFeedbackClick = useCallback(
+ (type: 'up' | 'down') => {
+ if (chatId && userQuery) {
+ setPendingFeedback(type)
+ setFeedbackText('')
+ }
+ },
+ [chatId, userQuery]
+ )
+
+ const handleSubmitFeedback = useCallback(() => {
+ if (!pendingFeedback || !chatId || !userQuery) return
+ const text = feedbackText.trim()
+ if (!text) {
+ setPendingFeedback(null)
+ setFeedbackText('')
return
}
+ submitFeedback.mutate({
+ chatId,
+ userQuery,
+ agentResponse: content,
+ isPositiveFeedback: pendingFeedback === 'up',
+ feedback: text,
+ })
+ setPendingFeedback(null)
+ setFeedbackText('')
+ }, [pendingFeedback, chatId, userQuery, content, feedbackText])
+
+ const handleModalClose = useCallback((open: boolean) => {
+ if (!open) {
+ setPendingFeedback(null)
+ setFeedbackText('')
+ }
}, [])
- if (!content && !requestId) {
- return null
- }
+ if (!content) return null
return (
-
-
+ <>
+
-
-
- {
- event.stopPropagation()
- void copyToClipboard(content, 'message')
- }}
+
- {
- event.stopPropagation()
- if (requestId) {
- void copyToClipboard(requestId, 'request')
- }
- }}
+
+
+
-
-
+
+
+
+
+
+
+ Give feedback
+
+
+
+ {pendingFeedback === 'up' ? 'What did you like?' : 'What could be improved?'}
+
+
+
+
+
+
+
+
+
+ >
)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx
index 2cc87d84368..dc6a032bc67 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx
@@ -473,9 +473,9 @@ function MothershipErrorDisplay({ data }: { data: MothershipErrorTagData }) {
const detail = data.code ? `${data.message} (${data.code})` : data.message
return (
-
+
{detail}
-
+
)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx
index 384666e3e07..53ccff13e6d 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx
@@ -35,6 +35,7 @@ interface MothershipChatProps {
onSendQueuedMessage: (id: string) => Promise
onEditQueuedMessage: (id: string) => void
userId?: string
+ chatId?: string
onContextAdd?: (context: ChatContext) => void
editValue?: string
onEditValueConsumed?: () => void
@@ -53,7 +54,7 @@ const LAYOUT_STYLES = {
userRow: 'flex flex-col items-end gap-[6px] pt-3',
attachmentWidth: 'max-w-[70%]',
userBubble: 'max-w-[70%] overflow-hidden rounded-[16px] bg-[var(--surface-5)] px-3.5 py-2',
- assistantRow: 'group/msg relative pb-5',
+ assistantRow: 'group/msg',
footer: 'flex-shrink-0 px-[24px] pb-[16px]',
footerInner: 'mx-auto max-w-[42rem]',
},
@@ -63,7 +64,7 @@ const LAYOUT_STYLES = {
userRow: 'flex flex-col items-end gap-[6px] pt-2',
attachmentWidth: 'max-w-[85%]',
userBubble: 'max-w-[85%] overflow-hidden rounded-[16px] bg-[var(--surface-5)] px-3 py-2',
- assistantRow: 'group/msg relative pb-3',
+ assistantRow: 'group/msg',
footer: 'flex-shrink-0 px-3 pb-3',
footerInner: '',
},
@@ -80,6 +81,7 @@ export function MothershipChat({
onSendQueuedMessage,
onEditQueuedMessage,
userId,
+ chatId,
onContextAdd,
editValue,
onEditValueConsumed,
@@ -147,20 +149,28 @@ export function MothershipChat({
}
const isLastMessage = index === messages.length - 1
+ const precedingUserMsg = [...messages]
+ .slice(0, index)
+ .reverse()
+ .find((m) => m.role === 'user')
return (
- {!isThisStreaming && (msg.content || msg.contentBlocks?.length) && (
-
-
-
- )}
+ {!isThisStreaming && (msg.content || msg.contentBlocks?.length) && (
+
+
+
+ )}
)
})}
diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx
index 6338c65cfa9..d2d72250261 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx
@@ -115,7 +115,7 @@ export const MothershipView = memo(