Skip to content

Commit d077dce

Browse files
committed
fix(search): harden assistant review and account discovery
1 parent 875f40a commit d077dce

19 files changed

Lines changed: 1082 additions & 106 deletions

File tree

‎.github/workflows/checks.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ jobs:
299299
path: |
300300
${{ runner.temp }}/e2e/**/*.json
301301
${{ runner.temp }}/e2e/browser-extension/**/*.png
302+
${{ runner.temp }}/e2e/*-ui-report.json.artifacts/*.png
302303
if-no-files-found: ignore
303304
retention-days: 7
304305

‎apps/docs/content/docs/search/connect-your-account.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Open the app in **Integrations** and select your account. Tool permissions appea
5454
| **Ask** | Waits for you to review the exact proposal. |
5555
| **Block** | Cannot run through Assistant. |
5656

57-
Read-only operations default to Allow; write operations default to Ask. An organization can require review even when you select Allow. A group-level Allow covers the currently listed operation versions; newly added or changed operations still ask. **Reset** restores the group's current default for that operation.
57+
Read-only operations default to Allow; write operations default to Ask. An organization can require review even when you select Allow. A group-level Allow covers the currently listed operation versions; newly added or changed operations still ask.
5858

5959
These preferences belong to your account. They cannot grant access to another person's credentials or bypass an administrator's source restrictions. If a related app uses a restricted Search source, direct operations remain unavailable; Assistant can search and read within the configured scope.
6060

@@ -70,7 +70,7 @@ Search sources and apps used for actions share the same Integrations list. An ad
7070

7171
## Reconnect or disconnect
7272

73-
Open the app and select the account, then choose **Reconnect** in the header when a grant expires, is revoked, or needs more scopes. A temporary provider outage does not necessarily require reconnecting.
73+
Open the app and select the account, then choose **Reconnect** in the header when a grant expires, is revoked, or needs more scopes. Reconnect with the same provider account. To use a different account, add a separate connection. A temporary provider outage does not necessarily require reconnecting.
7474

7575
Choose **Disconnect** in the same header to stop future use of the grant. This can affect workflows sharing the account. Review the confirmation before removing it. Disconnecting does not remove your Sim membership.
7676

‎apps/sim/app/o/[organizationId]/integrations/accounts/[credentialId]/tool-permissions.tsx‎

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

3-
import { Chip, ChipInput, ChipLink, ChipSelect, ChipSwitch, Tooltip, toast } from '@sim/emcn'
3+
import { ChipInput, ChipLink, ChipSelect, ChipSwitch, Tooltip, toast } from '@sim/emcn'
44
import { ArrowLeft, Search } from '@sim/emcn/icons'
55
import { useQueryStates } from 'nuqs'
66
import type {
@@ -182,8 +182,6 @@ function ToolPermissionGroup({
182182
`${tool.name} ${tool.description}`.toLowerCase().includes(query)
183183
)
184184
if (tools.length === 0) return null
185-
const defaultPolicy = effect === 'read' ? data.readPolicy : data.writePolicy
186-
const defaultLabel = POLICY_OPTIONS.find((option) => option.value === defaultPolicy)?.label
187185
const groupValue = tools.every((tool) => tool.policy === tools[0].policy)
188186
? tools[0].policy
189187
: undefined
@@ -222,24 +220,14 @@ function ToolPermissionGroup({
222220
<Tooltip.Content>{tool.description}</Tooltip.Content>
223221
</Tooltip.Root>
224222
</div>
225-
<div className='flex max-w-full shrink-0 flex-wrap items-center gap-2'>
226-
{!tool.inherited && (
227-
<Chip
228-
aria-label={`Reset ${tool.name} to ${defaultLabel}`}
229-
disabled={pending}
230-
onClick={() => onChange({ kind: 'tool', toolId: tool.toolId, policy: null })}
231-
>
232-
Reset to {defaultLabel}
233-
</Chip>
234-
)}
235-
<ChipSwitch
236-
options={options}
237-
value={tool.policy}
238-
disabled={pending}
239-
aria-label={`${tool.name} permission`}
240-
onChange={(policy) => onChange({ kind: 'tool', toolId: tool.toolId, policy })}
241-
/>
242-
</div>
223+
<ChipSwitch
224+
className='max-w-full shrink-0'
225+
options={options}
226+
value={tool.policy}
227+
disabled={pending}
228+
aria-label={`${tool.name} permission`}
229+
onChange={(policy) => onChange({ kind: 'tool', toolId: tool.toolId, policy })}
230+
/>
243231
</div>
244232
))}
245233
</div>

‎apps/sim/app/o/[organizationId]/search/search.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ function OrganizationSearchContent({ userId, chatId }: OrganizationSearchProps)
248248
</nav>
249249
{(hasConversation || (activeMode === 'search' && q.trim())) && (
250250
<Chip
251-
rightIcon={Plus}
251+
leftIcon={Plus}
252252
aria-label={newLabel}
253253
onClick={startNew}
254254
disabled={isStartingNew}

‎apps/sim/components/assistant-actions/action-preview.tsx‎

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,43 +8,52 @@ import {
88
useScrollEdges,
99
} from '@sim/emcn'
1010
import { ChevronDown, ChevronRight } from '@sim/emcn/icons'
11-
import { assistantActionPresentation } from '@/components/assistant-actions/presentation'
11+
import {
12+
assistantActionFieldPresentation,
13+
assistantActionPresentation,
14+
} from '@/components/assistant-actions/presentation'
1215
import type { AssistantActionsResponse } from '@/lib/api/contracts/assistant-actions'
1316

1417
type ReviewField = AssistantActionsResponse['actions'][number]['fields'][number]
1518

1619
interface ActionPreviewProps {
1720
action: Pick<AssistantActionsResponse['actions'][number], 'toolId' | 'name' | 'fields'>
21+
account?: string
1822
full?: boolean
1923
}
2024

2125
/** Readable content first; supplied secondary parameters remain inspectable. */
22-
export function ActionPreview({ action, full = false }: ActionPreviewProps) {
26+
export function ActionPreview({ action, account, full = false }: ActionPreviewProps) {
2327
const [expanded, setExpanded] = useState(false)
2428
const detailsId = useId()
2529
const { targets, primary, details } = assistantActionPresentation(action)
2630
return (
2731
<div className='flex min-w-0 flex-col gap-2'>
28-
{targets.length > 0 && (
29-
<dl className='flex flex-wrap gap-x-4 gap-y-1'>
32+
{(account || targets.length > 0) && (
33+
<div className='flex flex-col gap-1'>
34+
{account && (
35+
<dl className='grid grid-cols-[minmax(0,5rem)_minmax(0,1fr)] items-baseline gap-x-3 text-small'>
36+
<dt className='text-[var(--text-muted)] text-caption'>Account</dt>
37+
<dd className='min-w-0 break-all text-[var(--text-body)]'>{account}</dd>
38+
</dl>
39+
)}
3040
{targets.map((field) => (
31-
<div key={field.name} className='flex min-w-0 flex-wrap gap-x-1.5 text-small'>
32-
<dt className='text-[var(--text-muted)]'>{field.label}</dt>
33-
<dd className='whitespace-pre-wrap break-all text-[var(--text-body)]'>
34-
{field.value}
35-
</dd>
36-
</div>
41+
<PreviewField key={field.name} field={field} compact />
3742
))}
38-
</dl>
43+
</div>
44+
)}
45+
{primary.length > 0 && (
46+
<div className='flex min-w-0 flex-col gap-2 border-[var(--border)] border-t pt-2'>
47+
{primary.map((field, index) => (
48+
<PreviewField
49+
key={field.name}
50+
field={field}
51+
compact={field.format === 'text'}
52+
prominent={index === 0 && field.format === 'text'}
53+
/>
54+
))}
55+
</div>
3956
)}
40-
{primary.map((field, index) => (
41-
<PreviewField
42-
key={field.name}
43-
field={field}
44-
primary
45-
prominent={index === 0 && field.format === 'text'}
46-
/>
47-
))}
4857
{details.length > 0 && (
4958
<>
5059
{!full && (
@@ -62,7 +71,7 @@ export function ActionPreview({ action, full = false }: ActionPreviewProps) {
6271
{(expanded || full) && (
6372
<div id={detailsId} className='flex flex-col gap-2'>
6473
{details.map((field) => (
65-
<PreviewField key={field.name} field={field} />
74+
<PreviewField key={field.name} field={field} compact />
6675
))}
6776
</div>
6877
)}
@@ -74,30 +83,36 @@ export function ActionPreview({ action, full = false }: ActionPreviewProps) {
7483

7584
interface PreviewFieldProps {
7685
field: ReviewField
77-
primary?: boolean
86+
compact?: boolean
7887
prominent?: boolean
7988
}
8089

81-
function PreviewField({ field, primary = false, prominent = false }: PreviewFieldProps) {
90+
function PreviewField({ field, compact = false, prominent = false }: PreviewFieldProps) {
8291
const scroll = useRef<HTMLDivElement>(null)
8392
const edges = useScrollEdges(scroll)
84-
let value = field.value
93+
const display = assistantActionFieldPresentation(field)
94+
let value = display.value
8595
if (field.format === 'json') {
8696
try {
8797
value = JSON.stringify(JSON.parse(value), null, 2)
8898
} catch {}
8999
}
90100
return (
91-
<dl className='flex min-w-0 flex-col gap-1'>
92-
{(!primary || field.format === 'json') && (
93-
<dt className='text-[var(--text-muted)] text-caption'>{field.label}</dt>
101+
<dl
102+
className={cn(
103+
'min-w-0',
104+
compact && field.format === 'text'
105+
? 'grid grid-cols-[minmax(0,5rem)_minmax(0,1fr)] items-baseline gap-x-3'
106+
: 'flex flex-col gap-1'
94107
)}
95-
<dd>
108+
>
109+
<dt className='break-words text-[var(--text-muted)] text-caption'>{display.label}</dt>
110+
<dd className='min-w-0'>
96111
<div
97112
ref={scroll}
98113
tabIndex={edges.top || edges.bottom ? 0 : undefined}
99114
role='region'
100-
aria-label={`${field.label} preview`}
115+
aria-label={`${display.label} preview`}
101116
className={cn(
102117
'max-h-32 overflow-auto whitespace-pre-wrap break-words text-[var(--text-body)] text-small',
103118
scrollFadeClass,

‎apps/sim/components/assistant-actions/action-receipt.tsx‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export function AssistantActionReceipt({ receipt }: AssistantActionReceiptProps)
4646
}
4747
actions={
4848
<Chip
49-
aria-label={`${expanded ? 'Hide' : 'View'} ${presentation.title} receipt`}
49+
aria-label={`${expanded ? 'Hide' : 'View'} ${presentation.title} receipt: ${STATE_LABELS[receipt.state]}`}
5050
aria-expanded={expanded}
5151
rightIcon={expanded ? ChevronDown : ChevronRight}
5252
onClick={() => setExpanded(!expanded)}
@@ -77,15 +77,17 @@ export function AssistantActionReceipt({ receipt }: AssistantActionReceiptProps)
7777
)}
7878
{expanded && (
7979
<>
80-
<p className='break-words text-[var(--text-muted)] text-small'>
81-
{receipt.account?.displayName ?? 'Account unavailable'}
82-
</p>
8380
{receipt.detailsAvailable ? (
84-
<ActionPreview action={receipt} full />
81+
<ActionPreview
82+
action={receipt}
83+
account={receipt.account?.displayName ?? 'Account unavailable'}
84+
full
85+
/>
8586
) : (
86-
<p className='text-[var(--text-muted)] text-small'>
87-
Details are no longer available for this action.
88-
</p>
87+
<div className='flex flex-col gap-1 break-words text-[var(--text-muted)] text-small'>
88+
{receipt.account && <p>{receipt.account.displayName}</p>}
89+
<p>Details are no longer available for this action.</p>
90+
</div>
8991
)}
9092
</>
9193
)}

‎apps/sim/components/assistant-actions/presentation.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,31 @@
11
import type { AssistantActionsResponse } from '@/lib/api/contracts/assistant-actions'
2+
import { humanizeDisplayIdentifier } from '@/lib/mothership/tools/display'
23

34
type PresentedAction = Pick<
45
AssistantActionsResponse['actions'][number],
56
'toolId' | 'name' | 'fields'
67
>
78

9+
const CONTENT_TYPE_LABELS = new Map([
10+
['text', 'Plain text'],
11+
['html', 'HTML'],
12+
])
13+
14+
/** Readable labels and enum text leave the reviewed invocation unchanged. */
15+
export function assistantActionFieldPresentation(field: PresentedAction['fields'][number]) {
16+
const identifier = field.name
17+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
18+
.replace(/([A-Z])([A-Z][a-z])/g, '$1_$2')
19+
return {
20+
label:
21+
field.label === field.name ? humanizeDisplayIdentifier(identifier, 'sentence') : field.label,
22+
value:
23+
field.name === 'contentType'
24+
? (CONTENT_TYPE_LABELS.get(field.value) ?? field.value)
25+
: field.value,
26+
}
27+
}
28+
829
/** Display labels and preview fields only; model-facing tool definitions remain unchanged. */
930
const PRESENTATIONS: Record<string, { title: string; primary: string[] }> = {
1031
gmail_draft_v2: { title: 'Draft email', primary: ['subject', 'body'] },

‎apps/sim/components/assistant-actions/review.tsx‎

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import { getErrorMessage } from '@sim/utils/errors'
1515
import { omit } from '@sim/utils/object'
1616
import { ActionPreview } from '@/components/assistant-actions/action-preview'
1717
import { AssistantActionReceipt } from '@/components/assistant-actions/action-receipt'
18-
import { assistantActionPresentation } from '@/components/assistant-actions/presentation'
18+
import {
19+
assistantActionFieldPresentation,
20+
assistantActionPresentation,
21+
} from '@/components/assistant-actions/presentation'
1922
import { ActivityStatus } from '@/components/ui/activity-status'
2023
import type {
2124
AssistantActionReceiptsResponse,
@@ -651,12 +654,13 @@ function ActionReview({ action, review }: ActionReviewProps) {
651654
) : undefined
652655
}
653656
>
654-
<div className='flex flex-col gap-2 px-2 pb-2'>
655-
<p className='break-words text-[var(--text-muted)] text-small'>
656-
{action.account?.displayName ?? 'Account unavailable'}
657-
</p>
657+
<div className='flex flex-col gap-3 px-2 pb-2'>
658658
{editing ? (
659659
<div className='grid gap-3 sm:grid-cols-2'>
660+
<p className='col-span-full break-words text-[var(--text-body)] text-small'>
661+
<span className='mr-3 text-[var(--text-muted)] text-caption'>Account</span>
662+
{action.account?.displayName ?? 'Account unavailable'}
663+
</p>
660664
{action.fields
661665
.filter((field) => field.value || field.editable)
662666
.map((field) => (
@@ -672,6 +676,7 @@ function ActionReview({ action, review }: ActionReviewProps) {
672676
</div>
673677
) : (
674678
<ActionPreview
679+
account={action.account?.displayName ?? 'Account unavailable'}
675680
action={{
676681
...action,
677682
fields: action.fields.map((field) => ({
@@ -694,7 +699,7 @@ function ActionReview({ action, review }: ActionReviewProps) {
694699
{error.message}
695700
</p>
696701
)}
697-
<div className='flex flex-wrap items-center gap-2'>
702+
<div className='flex flex-wrap items-center gap-2 border-[var(--border)] border-t pt-2'>
698703
<Chip
699704
variant='primary'
700705
disabled={disabled || !action.account?.active}
@@ -763,10 +768,11 @@ interface ActionReviewFieldProps {
763768

764769
function ActionReviewField({ field, actionId, value, disabled, onEdit }: ActionReviewFieldProps) {
765770
const id = `assistant-action-${actionId}-${field.name}`
771+
const display = assistantActionFieldPresentation({ ...field, value })
766772
return field.editable ? (
767773
<div className='col-span-full flex flex-col gap-[9px]'>
768774
<label htmlFor={id} className='text-[var(--text-muted)] text-small'>
769-
{field.label}
775+
{display.label}
770776
</label>
771777
{field.format === 'text' ? (
772778
<ChipInput
@@ -790,9 +796,9 @@ function ActionReviewField({ field, actionId, value, disabled, onEdit }: ActionR
790796
</div>
791797
) : (
792798
<dl className={cn('flex min-w-0 flex-col gap-1', field.format !== 'text' && 'col-span-full')}>
793-
<dt className='text-[var(--text-muted)] text-small'>{field.label}</dt>
799+
<dt className='text-[var(--text-muted)] text-small'>{display.label}</dt>
794800
<dd className='whitespace-pre-wrap break-words text-[var(--text-body)] text-small'>
795-
{value}
801+
{display.value}
796802
</dd>
797803
</dl>
798804
)

‎apps/sim/lib/assistant-actions/catalog.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export async function loadAssistantIntegrationTools(
1717
executor?: DbOrTx
1818
) {
1919
const [visibility, config] = await Promise.all([
20-
getBlockVisibility({ userId: scope.userId, orgId: scope.organizationId }),
20+
getBlockVisibility({ userId: scope.userId, orgId: scope.organizationId }, executor),
2121
getUserPermissionConfigForOrganization(scope.organizationId, executor),
2222
])
2323
const tools = projectIntegrationToolsForViewer(visibility, config)

0 commit comments

Comments
 (0)