Skip to content

Commit 5861625

Browse files
committed
fix(mothership): stabilize Home search panels and add optional Fast Search
1 parent b2992d4 commit 5861625

73 files changed

Lines changed: 3418 additions & 717 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/sim/app/o/[organizationId]/home/components/composer/composer.test.tsx‎

Lines changed: 265 additions & 52 deletions
Large diffs are not rendered by default.

‎apps/sim/app/o/[organizationId]/home/components/composer/composer.tsx‎

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useEffect, useRef, useState } from 'react'
3+
import { useEffect, useRef } from 'react'
44
import {
55
Button,
66
Chip,
@@ -13,6 +13,7 @@ import {
1313
toast,
1414
} from '@sim/emcn'
1515
import { ArrowUp, Paperclip, Plus, Slash } from '@sim/emcn/icons'
16+
import { useQueries } from '@tanstack/react-query'
1617
import {
1718
ASSISTANT_IMAGE_ACCEPT_ATTRIBUTE,
1819
isAssistantImageType,
@@ -21,19 +22,22 @@ import { MOTHERSHIP_ACCEPT_ATTRIBUTE } from '@/lib/uploads/utils/validation'
2122
import { useOrganizationContext } from '@/app/o/[organizationId]/providers/organization-provider'
2223
import { AttachedFilesList } from '@/app/workspace/[workspaceId]/home/components/user-input/components/attached-files-list/attached-files-list'
2324
import { DropOverlay } from '@/app/workspace/[workspaceId]/home/components/user-input/components/drop-overlay/drop-overlay'
25+
import { FastModeToggle } from '@/app/workspace/[workspaceId]/home/components/user-input/components/fast-mode-toggle'
2426
import { InputToolbar } from '@/app/workspace/[workspaceId]/home/components/user-input/components/input-toolbar'
2527
import { MicButton } from '@/app/workspace/[workspaceId]/home/components/user-input/components/mic-button/mic-button'
2628
import { MicrophonePermissionHelp } from '@/app/workspace/[workspaceId]/home/components/user-input/components/microphone-permission-help/microphone-permission-help'
2729
import {
2830
PromptEditor,
2931
usePromptEditor,
3032
} from '@/app/workspace/[workspaceId]/home/components/user-input/components/prompt-editor'
33+
import { organizationSkillOptions } from '@/app/workspace/[workspaceId]/home/components/user-input/components/skills-menu-dropdown/organization-skill-options'
3134
import type { ChatRequestMode } from '@/app/workspace/[workspaceId]/home/types'
3235
import type { useFileAttachments } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/hooks/use-file-attachments'
3336
import {
3437
escapeRegex,
3538
SKILL_CHIP_TRIGGER,
3639
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/utils'
40+
import { getSkillsQueryOptions } from '@/hooks/queries/skills'
3741
import { useWorkspacesQuery } from '@/hooks/queries/workspace'
3842
import { useAnimatedPlaceholder } from '@/hooks/use-animated-placeholder'
3943
import { useChatInputFocus } from '@/hooks/use-chat-input-focus'
@@ -47,9 +51,10 @@ const SEND_BUTTON_DISABLED = 'bg-[#808080] dark:bg-[#808080]'
4751

4852
interface ComposerProps {
4953
requestMode?: ChatRequestMode
54+
assistantFast?: boolean
55+
onAssistantFastChange?: (enabled: boolean) => void
5056
onModeChange?: (mode: ChatRequestMode) => void
5157
showModeSelector?: boolean
52-
modeChangeDisabled?: boolean
5358
value: string
5459
files: ReturnType<typeof useFileAttachments>
5560
/** On the empty home the placeholder types itself and the field is taller; in a chat it is the plain footer input. */
@@ -69,8 +74,9 @@ interface ComposerProps {
6974
export function Composer({
7075
requestMode = 'assistant',
7176
onModeChange,
77+
assistantFast = false,
78+
onAssistantFastChange,
7279
showModeSelector = false,
73-
modeChangeDisabled = false,
7480
value,
7581
files,
7682
isInitialView,
@@ -82,17 +88,24 @@ export function Composer({
8288
}: ComposerProps) {
8389
const imagesOnly = requestMode === 'assistant'
8490
const { organization } = useOrganizationContext()
85-
const [contextWorkspaceId, setContextWorkspaceId] = useState('')
86-
const [pendingPicker, setPendingPicker] = useState<{
87-
kind: 'resources' | 'skills'
88-
anchor: { left: number; top: number }
89-
} | null>(null)
9091
const { data: allWorkspaces = [] } = useWorkspacesQuery(!imagesOnly)
91-
const workspaces = allWorkspaces.filter(
92+
const workspaces = (imagesOnly ? [] : allWorkspaces).filter(
9293
(workspace) => workspace.organizationId === organization.id
9394
)
95+
const skillQueries = useQueries({
96+
queries: workspaces.map((workspace) => getSkillsQueryOptions(workspace.id)),
97+
})
98+
const skills = imagesOnly
99+
? []
100+
: organizationSkillOptions(
101+
workspaces.map((workspace, index) => ({
102+
...workspace,
103+
skills: skillQueries[index].isPlaceholderData ? [] : (skillQueries[index].data ?? []),
104+
}))
105+
)
94106
const editor = usePromptEditor({
95-
workspaceId: contextWorkspaceId,
107+
workspaceId: '',
108+
availableSkills: skills,
96109
organizationId: organization.id,
97110
contextsEnabled: !imagesOnly,
98111
initialValue: value,
@@ -132,12 +145,6 @@ export function Composer({
132145
editorRef.current.setValue(restoredText, { chipify: false })
133146
editorRef.current.setContexts(restoredContexts)
134147
}, [restoredContexts])
135-
useEffect(() => {
136-
if (!pendingPicker) return
137-
if (pendingPicker.kind === 'resources') editorRef.current.openResourceMenu(pendingPicker.anchor)
138-
else editorRef.current.insertSlashTrigger()
139-
setPendingPicker(null)
140-
}, [pendingPicker])
141148
useChatInputFocus({ textareaRef })
142149
const voice = useVoiceInput({
143150
organizationId: organization.id,
@@ -163,34 +170,23 @@ export function Composer({
163170
}
164171

165172
const contextPicker = (kind: 'resources' | 'skills', icon: typeof Plus, label: string) => (
166-
<DropdownMenu>
167-
<Tooltip.Root>
168-
<Tooltip.Trigger asChild>
169-
<DropdownMenuTrigger asChild>
170-
<Chip shape='round' leftIcon={icon} aria-label={label} />
171-
</DropdownMenuTrigger>
172-
</Tooltip.Trigger>
173-
<Tooltip.Content side='top'>{label}</Tooltip.Content>
174-
</Tooltip.Root>
175-
<DropdownMenuContent side='top' align='start'>
176-
<div className='px-2 py-1 text-[var(--text-muted)] text-xs'>Choose a workspace</div>
177-
{workspaces.map((workspace) => (
178-
<DropdownMenuItem
179-
key={workspace.id}
180-
onSelect={(event) => {
181-
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()
182-
setContextWorkspaceId(workspace.id)
183-
setPendingPicker({ kind, anchor: { left: rect.left, top: rect.top } })
184-
}}
185-
>
186-
{workspace.name}
187-
</DropdownMenuItem>
188-
))}
189-
{!workspaces.length && (
190-
<DropdownMenuItem disabled>No accessible workspaces</DropdownMenuItem>
191-
)}
192-
</DropdownMenuContent>
193-
</DropdownMenu>
173+
<Tooltip.Root>
174+
<Tooltip.Trigger asChild>
175+
<Chip
176+
shape='round'
177+
leftIcon={icon}
178+
aria-label={label}
179+
onClick={(event) => {
180+
if (kind === 'skills') editor.insertSlashTrigger()
181+
else {
182+
const rect = event.currentTarget.getBoundingClientRect()
183+
editor.openResourceMenu({ left: rect.left, top: rect.top })
184+
}
185+
}}
186+
/>
187+
</Tooltip.Trigger>
188+
<Tooltip.Content side='top'>{label}</Tooltip.Content>
189+
</Tooltip.Root>
194190
)
195191

196192
return (
@@ -240,10 +236,7 @@ export function Composer({
240236
{showModeSelector && (
241237
<DropdownMenu>
242238
<DropdownMenuTrigger asChild>
243-
<Chip
244-
aria-label='Conversation mode'
245-
disabled={isSending || modeChangeDisabled || !onModeChange}
246-
>
239+
<Chip aria-label='Conversation mode' disabled={!onModeChange}>
247240
{requestMode === 'assistant' ? 'Search' : 'Build'}
248241
</Chip>
249242
</DropdownMenuTrigger>
@@ -253,9 +246,8 @@ export function Composer({
253246
key={mode}
254247
role='menuitemradio'
255248
aria-checked={mode === requestMode}
256-
disabled={isSending || modeChangeDisabled}
257249
onSelect={() => {
258-
if (isSending || modeChangeDisabled || mode === requestMode) return
250+
if (mode === requestMode) return
259251
if (
260252
mode === 'assistant' &&
261253
(editor.getActiveContexts().length > 0 ||
@@ -286,6 +278,17 @@ export function Composer({
286278
/>
287279
)
288280
}
281+
beforeSubmitControl={
282+
imagesOnly &&
283+
onAssistantFastChange && (
284+
<FastModeToggle
285+
enabled={assistantFast}
286+
onChange={onAssistantFastChange}
287+
label='Search Fast mode'
288+
description='Faster and cheaper'
289+
/>
290+
)
291+
}
289292
submitControl={
290293
isSending ? (
291294
<Button

0 commit comments

Comments
 (0)