Skip to content

Commit 73fd7c3

Browse files
committed
Tighten live chat and Search after the cleanup passes
Refuse a personal secret save when its pre-save read fails, since saving replaces the whole map. A query in the URL now outranks the remembered mode, and a search is recorded on submit rather than on any URL change, Home's entry included. Keep typed edits on undecided review cards. Use the owning design-system components for links, secrets, notices and rows, and distinguish history loading from empty.
1 parent f248698 commit 73fd7c3

24 files changed

Lines changed: 217 additions & 191 deletions
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { useMutation, useQueryClient } from '@tanstack/react-query'
2+
import { requestJson } from '@/lib/api/client/request'
3+
import { copilotToolPermissionContract } from '@/lib/api/contracts/copilot'
4+
import { generalSettingsKeys } from '@/hooks/queries/current-user-data'
5+
6+
type ToolDecision = 'allow' | 'allow_chat' | 'always_allow' | 'skip'
7+
8+
/** Answers gated tool calls. "For every chat" is a saved preference, so settings read fresh after. */
9+
export function useDecideToolPermissions() {
10+
const queryClient = useQueryClient()
11+
return useMutation({
12+
mutationFn: ({ ids, decision }: { ids: readonly string[]; decision: ToolDecision }) =>
13+
requestJson(copilotToolPermissionContract, {
14+
body: { decisions: ids.map((toolCallId) => ({ toolCallId, decision })) },
15+
}),
16+
onSuccess: (_result, { decision }) => {
17+
if (decision === 'always_allow') {
18+
void queryClient.invalidateQueries({ queryKey: generalSettingsKeys.settings() })
19+
}
20+
},
21+
})
22+
}

‎apps/sim/platform/app/(shell)/_components/chat/cards/assistant-review.tsx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,10 @@ export function AssistantReview({ turnId }: { turnId: string }) {
141141
setFailure('')
142142
try {
143143
await decide(ids, decision, decision === 'allow' ? edits : undefined)
144-
setEdits({})
144+
// The cards not decided stay on screen, with whatever was typed into them.
145+
setEdits((current) =>
146+
Object.fromEntries(Object.entries(current).filter(([id]) => !ids.includes(id)))
147+
)
145148
} catch (error) {
146149
setFailure(error instanceof Error ? error.message : 'Could not save this review')
147150
}

‎apps/sim/platform/app/(shell)/_components/chat/cards/cards.module.css‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@
3030
min-inline-size: 0;
3131
overflow-wrap: anywhere;
3232
}
33-
.glyph {
34-
display: inline-flex;
35-
flex: none;
36-
color: var(--ui-secondary);
37-
}
3833
.quiet,
3934
.fields dt {
4035
color: var(--ui-secondary);
@@ -47,6 +42,3 @@
4742
white-space: pre-wrap;
4843
overflow-wrap: anywhere;
4944
}
50-
.link {
51-
text-decoration: underline;
52-
}

‎apps/sim/platform/app/(shell)/_components/chat/cards/live-credential-card.tsx‎

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
'use client'
22

33
import { useState } from 'react'
4+
import Link from 'next/link'
45
import styles from '@/platform/app/(shell)/_components/chat/cards/cards.module.css'
56
import type {
67
CredentialCardProps,
78
UsageNoticeProps,
89
} from '@/platform/app/(shell)/_components/chat/message/message-segments'
910
import type { PlatformCredentialRequest } from '@/platform/app/(shell)/_components/chat/model'
10-
import { Button, InlineNotice } from '@/platform/design-system/components'
11-
import { Input } from '@/platform/design-system/components/field/inputs'
11+
import { Button, InlineNotice, SecretField } from '@/platform/design-system/components'
12+
import { ControlRow } from '@/platform/design-system/components/button/control-row'
1213
import {
1314
CredentialWorkspaceProvider,
1415
useCredentialWorkspace,
@@ -31,31 +32,35 @@ interface RowProps {
3132
onConnected: () => void
3233
}
3334

34-
function LinkRow({ item, controlId, onConnected }: RowProps) {
35+
function LinkRow({ item, url, controlId, onConnected }: RowProps & { url: string }) {
3536
const name = serviceName(item)
3637
const connection = useOAuthChipConnection({
37-
connectUrl: item.value ?? '',
38+
connectUrl: url,
3839
provider: item.provider ?? '',
3940
displayName: name,
4041
controlId,
4142
onConnected,
4243
})
4344
if (connection.connected) return <p data-type='T048'>Connected {name}</p>
4445
return (
45-
<a
46-
className={styles.link}
47-
data-type='T048'
48-
href={item.value}
49-
target='_blank'
50-
rel='noopener noreferrer'
51-
onClick={connection.onConnectClick}
52-
>
53-
{connection.status === 'pending'
54-
? `Waiting for ${name} connection`
55-
: connection.reconnectCredentialId
56-
? `Reconnect ${name}`
57-
: `Connect ${name}`}
58-
</a>
46+
<ControlRow
47+
render={
48+
<Link
49+
href={url}
50+
target='_blank'
51+
rel='noopener noreferrer'
52+
onClick={connection.onConnectClick}
53+
/>
54+
}
55+
label={
56+
connection.status === 'pending'
57+
? `Waiting for ${name} connection`
58+
: connection.reconnectCredentialId
59+
? `Reconnect ${name}`
60+
: `Connect ${name}`
61+
}
62+
trailingIcon='I030'
63+
/>
5964
)
6065
}
6166

@@ -200,10 +205,11 @@ export function LiveCredentialCard({
200205
controlId={controlId}
201206
onConnected={markConnected}
202207
/>
203-
) : canEdit ? (
208+
) : canEdit && item.safeUrl ? (
204209
<LinkRow
205210
key={controlId}
206211
item={item}
212+
url={item.safeUrl}
207213
controlId={controlId}
208214
onConnected={markConnected}
209215
/>
@@ -216,35 +222,30 @@ export function LiveCredentialCard({
216222
{item.name} added
217223
</p>
218224
) : (
219-
<Input
225+
<SecretField
220226
key={controlId}
221227
label={item.name ?? 'Secret'}
222228
placeholder={item.name}
223-
type='password'
224229
value={drafts[index] ?? ''}
225230
onChange={(value) => setDrafts((current) => ({ ...current, [index]: value }))}
226231
/>
227232
)
228233
case 'service_account':
229234
return canEdit ? (
230-
<a
235+
<ControlRow
231236
key={controlId}
232-
className={styles.link}
233-
data-type='T048'
234-
href={integrationsHref}
235-
target='_blank'
236-
rel='noopener noreferrer'
237-
>
238-
Connect {serviceName(item)} in Integrations
239-
</a>
237+
render={
238+
<Link href={integrationsHref} target='_blank' rel='noopener noreferrer' />
239+
}
240+
label={`Connect ${serviceName(item)} in Integrations`}
241+
trailingIcon='I030'
242+
/>
240243
) : null
241244
case 'folder_access':
242245
return <FolderRow key={controlId} item={item} />
243246
case 'sim_key':
244247
return item.value ? (
245-
<code key={controlId} data-type='T009'>
246-
{item.value}
247-
</code>
248+
<SecretField key={controlId} label='Sim key' value={item.value} readOnly copyable />
248249
) : null
249250
default:
250251
return null
@@ -284,9 +285,11 @@ export function LiveUsageNotice({ action, reason, message }: UsageNoticeProps) {
284285
}
285286
action={
286287
canManage ? (
287-
<a className={styles.link} data-type='T048' href={href}>
288-
{upgrade ? 'Upgrade Plan' : 'Increase Limit'}
289-
</a>
288+
<ControlRow
289+
render={<Link href={href} />}
290+
label={upgrade ? 'Upgrade Plan' : 'Increase Limit'}
291+
trailingIcon='I030'
292+
/>
290293
) : undefined
291294
}
292295
/>

‎apps/sim/platform/app/(shell)/_components/chat/cards/question-card.tsx‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useState } from 'react'
44
import styles from '@/platform/app/(shell)/_components/chat/cards/cards.module.css'
55
import type { PlatformQuestion } from '@/platform/app/(shell)/_components/chat/model'
6-
import { Button, Checkbox, IconButton } from '@/platform/design-system/components'
6+
import { Button, Checkbox, Chip, IconButton } from '@/platform/design-system/components'
77
import { Input } from '@/platform/design-system/components/field/inputs'
88

99
/** One `Prompt — Answer` line per question, the shape the transcript pairs back to this card. */
@@ -126,14 +126,13 @@ export function QuestionCard({ questions, answers: stored, onSubmit }: QuestionC
126126
onCheckedChange={() => toggle(option.label)}
127127
/>
128128
) : (
129-
<Button
129+
<Chip
130130
key={option.id}
131-
variant='muted'
132-
size='compact'
131+
selected={selected[step]?.includes(option.label) ?? false}
133132
onClick={() => choose(option.label)}
134133
>
135134
{option.label}
136-
</Button>
135+
</Chip>
137136
)
138137
)}
139138
</div>

‎apps/sim/platform/app/(shell)/_components/chat/cards/tool-approval.tsx‎

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { PlatformTool, ToolDecision } from '@/platform/app/(shell)/_compone
77
import { usePlatformNotices } from '@/platform/app/(shell)/_components/notification-tray/notification-tray'
88
import { useChatRuntime } from '@/platform/app/(shell)/_components/session/chat-runtime'
99
import { ActionMenu, Button, Icon } from '@/platform/design-system/components'
10+
import { ActivityBody } from '@/platform/design-system/components/activity-row/activity-body'
1011

1112
const BROWSER_TOOL = /^browser[_-]/
1213

@@ -17,14 +18,12 @@ const BROWSER_TOOL = /^browser[_-]/
1718
export function ToolApproval({ tool }: { tool: PlatformTool }) {
1819
const { approvals, registerApproval, decideTools } = useChatRuntime()
1920
const { notify } = usePlatformNotices()
20-
const [submitting, setSubmitting] = useState(false)
2121
const [expired, setExpired] = useState(false)
2222
useEffect(() => registerApproval(tool.id), [registerApproval, tool.id])
2323
const { outstanding, submitted } = approvals
24-
const busy = submitting || submitted.includes(tool.id)
24+
const busy = submitted.includes(tool.id)
2525
const browser = BROWSER_TOOL.test(tool.name)
2626
async function submit(decision: ToolDecision, ids: readonly string[]) {
27-
setSubmitting(true)
2827
try {
2928
const result = await decideTools(ids, decision)
3029
if (result === 'gone') setExpired(true)
@@ -34,7 +33,6 @@ export function ToolApproval({ tool }: { tool: PlatformTool }) {
3433
icon: 'I047',
3534
title: 'Could not record your choice. Please try again.',
3635
})
37-
setSubmitting(false)
3836
}
3937
}
4038
if (expired)
@@ -45,14 +43,11 @@ export function ToolApproval({ tool }: { tool: PlatformTool }) {
4543
)
4644
return (
4745
<div className={styles.card}>
48-
<div className={styles.row}>
49-
<span className={styles.glyph}>
50-
<Icon name='I084' />
51-
</span>
46+
<ActivityBody leading={<Icon name='I084' />} decorativeLeading>
5247
<span className={styles.title} data-type='T048' title={tool.input}>
5348
{tool.title}
5449
</span>
55-
</div>
50+
</ActivityBody>
5651
<div className={styles.actions}>
5752
<Button
5853
variant='primary'
@@ -69,11 +64,13 @@ export function ToolApproval({ tool }: { tool: PlatformTool }) {
6964
{
7065
id: 'chat',
7166
label: browser ? 'Browser actions in this chat' : 'For this chat',
67+
disabled: busy,
7268
onSelect: () => void submit('allow_chat', [tool.id]),
7369
},
7470
{
7571
id: 'every',
7672
label: browser ? 'Browser actions in every chat' : 'For every chat',
73+
disabled: busy,
7774
onSelect: () => void submit('always_allow', [tool.id]),
7875
},
7976
]}

‎apps/sim/platform/app/(shell)/_components/chat/message/lane-activity.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
ToolInteraction,
66
} from '@/platform/app/(shell)/_components/chat/message/tool-activity'
77
import type { PlatformLane } from '@/platform/app/(shell)/_components/chat/model'
8-
import { Disclosure } from '@/platform/design-system/components'
8+
import { Disclosure, InlineNotice } from '@/platform/design-system/components'
99

1010
/** One subagent's work under its own disclosure. A held approval renders outside the collapse. */
1111
export function LaneActivity({ lane }: { lane: PlatformLane }) {
@@ -39,7 +39,7 @@ export function LaneActivity({ lane }: { lane: PlatformLane }) {
3939
})}
4040
</div>
4141
</Disclosure>
42-
{lane.error && <p data-type='T048'>{lane.error}</p>}
42+
{lane.error && <InlineNotice intent='danger' message={lane.error} />}
4343
{held.map((tool) => (
4444
<ToolInteraction key={tool.id} tool={tool} />
4545
))}

‎apps/sim/platform/app/(shell)/_components/chat/model.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export interface PlatformCredentialRequest {
6464
workspaceId?: string
6565
/** The parsed tag item this row came from, which the submission message is built from. */
6666
source: CredentialItemData
67+
/** The connect link, present only when it is safe to open as http(s). */
68+
safeUrl?: string
6769
/** Present when this row connects a Search source rather than a workspace credential. */
6870
searchTarget?: SearchConnectionTarget
6971
}

‎apps/sim/platform/app/(shell)/_components/chat/panel/panel-tabs.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export function PanelTabs({
4545
<span className={styles.title} data-type='T046'>
4646
{active && <OverflowText text={active.label}>{active.label}</OverflowText>}
4747
</span>
48-
<IconButton icon='I074' label='New panel tab' onClick={onAdd} />
48+
{onAdd && <IconButton icon='I074' label='New panel tab' onClick={onAdd} />}
4949
<div className={styles.actions}>
5050
<div className={styles.expand}>
5151
<IconButton

‎apps/sim/platform/app/(shell)/_components/chat/panel/panels.module.css‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,3 @@
149149
display: none;
150150
}
151151
}
152-
.outlink {
153-
justify-self: center;
154-
text-decoration: underline;
155-
}

0 commit comments

Comments
 (0)