Skip to content

Commit a353563

Browse files
authored
fix(copilot): rewrote user input popover to optimize UX (#2814)
* fix(copilot): rewrote user input popover to optimize UX * cleanup * make keyboard and moues share state * escape goes one level up on slash popover
1 parent d5bd97d commit a353563

File tree

17 files changed

+1330
-1754
lines changed

17 files changed

+1330
-1754
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/tool-call/tool-call.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { Button, Code, getCodeEditorProps, highlight, languages } from '@/compon
88
import { ClientToolCallState } from '@/lib/copilot/tools/client/base-tool'
99
import { getClientTool } from '@/lib/copilot/tools/client/manager'
1010
import { getRegisteredTools } from '@/lib/copilot/tools/client/registry'
11-
// Initialize all tool UI configs
1211
import '@/lib/copilot/tools/client/init-tool-configs'
1312
import {
1413
getSubagentLabels as getSubagentLabelsFromConfig,
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export { AttachedFilesDisplay } from './attached-files-display/attached-files-display'
22
export { ContextPills } from './context-pills/context-pills'
3-
export { MentionMenu } from './mention-menu/mention-menu'
3+
export { type MentionFolderNav, MentionMenu } from './mention-menu/mention-menu'
44
export { ModeSelector } from './mode-selector/mode-selector'
55
export { ModelSelector } from './model-selector/model-selector'
6-
export { SlashMenu } from './slash-menu/slash-menu'
6+
export { type SlashFolderNav, SlashMenu } from './slash-menu/slash-menu'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
'use client'
2+
3+
import type { ComponentType, ReactNode, SVGProps } from 'react'
4+
import { PopoverItem } from '@/components/emcn'
5+
import { formatCompactTimestamp } from '@/lib/core/utils/formatting'
6+
import {
7+
FOLDER_CONFIGS,
8+
MENU_STATE_TEXT_CLASSES,
9+
type MentionFolderId,
10+
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/constants'
11+
12+
const ICON_CONTAINER =
13+
'relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
14+
15+
export function BlockIcon({
16+
bgColor,
17+
Icon,
18+
}: {
19+
bgColor?: string
20+
Icon?: ComponentType<SVGProps<SVGSVGElement>>
21+
}) {
22+
return (
23+
<div className={ICON_CONTAINER} style={{ background: bgColor || '#6B7280' }}>
24+
{Icon && <Icon className='!h-[10px] !w-[10px] !text-white' />}
25+
</div>
26+
)
27+
}
28+
29+
export function WorkflowColorDot({ color }: { color?: string }) {
30+
return <div className={ICON_CONTAINER} style={{ backgroundColor: color || '#3972F6' }} />
31+
}
32+
33+
interface FolderContentProps {
34+
/** Folder ID to render content for */
35+
folderId: MentionFolderId
36+
/** Items to render (already filtered) */
37+
items: any[]
38+
/** Whether data is loading */
39+
isLoading: boolean
40+
/** Current search query (for determining empty vs no-match message) */
41+
currentQuery: string
42+
/** Currently active item index (for keyboard navigation) */
43+
activeIndex: number
44+
/** Callback when an item is clicked */
45+
onItemClick: (item: any) => void
46+
}
47+
48+
export function renderItemIcon(folderId: MentionFolderId, item: any): ReactNode {
49+
switch (folderId) {
50+
case 'workflows':
51+
return <WorkflowColorDot color={item.color} />
52+
case 'blocks':
53+
case 'workflow-blocks':
54+
return <BlockIcon bgColor={item.bgColor} Icon={item.iconComponent} />
55+
default:
56+
return null
57+
}
58+
}
59+
60+
function renderItemSuffix(folderId: MentionFolderId, item: any): ReactNode {
61+
switch (folderId) {
62+
case 'templates':
63+
return <span className='text-[10px] text-[var(--text-muted)]'>{item.stars}</span>
64+
case 'logs':
65+
return (
66+
<>
67+
<span className='text-[10px] text-[var(--text-tertiary)]'>·</span>
68+
<span className='whitespace-nowrap text-[10px]'>
69+
{formatCompactTimestamp(item.createdAt)}
70+
</span>
71+
<span className='text-[10px] text-[var(--text-tertiary)]'>·</span>
72+
<span className='text-[10px] capitalize'>{(item.trigger || 'manual').toLowerCase()}</span>
73+
</>
74+
)
75+
default:
76+
return null
77+
}
78+
}
79+
80+
export function FolderContent({
81+
folderId,
82+
items,
83+
isLoading,
84+
currentQuery,
85+
activeIndex,
86+
onItemClick,
87+
}: FolderContentProps) {
88+
const config = FOLDER_CONFIGS[folderId]
89+
90+
if (isLoading) {
91+
return <div className={MENU_STATE_TEXT_CLASSES}>Loading...</div>
92+
}
93+
94+
if (items.length === 0) {
95+
return (
96+
<div className={MENU_STATE_TEXT_CLASSES}>
97+
{currentQuery ? config.noMatchMessage : config.emptyMessage}
98+
</div>
99+
)
100+
}
101+
102+
return (
103+
<>
104+
{items.map((item, index) => (
105+
<PopoverItem
106+
key={config.getId(item)}
107+
onClick={() => onItemClick(item)}
108+
data-idx={index}
109+
active={index === activeIndex}
110+
>
111+
{renderItemIcon(folderId, item)}
112+
<span className={folderId === 'logs' ? 'min-w-0 flex-1 truncate' : 'truncate'}>
113+
{config.getLabel(item)}
114+
</span>
115+
{renderItemSuffix(folderId, item)}
116+
</PopoverItem>
117+
))}
118+
</>
119+
)
120+
}
121+
122+
export function FolderPreviewContent({
123+
folderId,
124+
items,
125+
isLoading,
126+
onItemClick,
127+
}: Omit<FolderContentProps, 'currentQuery' | 'activeIndex'>) {
128+
const config = FOLDER_CONFIGS[folderId]
129+
130+
if (isLoading) {
131+
return <div className={MENU_STATE_TEXT_CLASSES}>Loading...</div>
132+
}
133+
134+
if (items.length === 0) {
135+
return <div className={MENU_STATE_TEXT_CLASSES}>{config.emptyMessage}</div>
136+
}
137+
138+
return (
139+
<>
140+
{items.map((item) => (
141+
<PopoverItem key={config.getId(item)} onClick={() => onItemClick(item)}>
142+
{renderItemIcon(folderId, item)}
143+
<span className={folderId === 'logs' ? 'min-w-0 flex-1 truncate' : 'truncate'}>
144+
{config.getLabel(item)}
145+
</span>
146+
{renderItemSuffix(folderId, item)}
147+
</PopoverItem>
148+
))}
149+
</>
150+
)
151+
}

0 commit comments

Comments
 (0)