Skip to content

Commit c88b23e

Browse files
committed
fix(extension): enable voice input and resume signed-in panels safely
1 parent dd8ff9e commit c88b23e

20 files changed

Lines changed: 409 additions & 163 deletions

File tree

‎.github/scripts/http-e2e.sh‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ case "$group" in
279279
export REDIS_URL=redis://127.0.0.1:6379
280280
export NEXT_PUBLIC_FORCE_HOSTED=false
281281
export KNOWLEDGE_MEMBER_ACCESS=true CREDENTIAL_GROUPS=true ACCESS_CONTROL_ENABLED=true
282+
export SIM_LINKS=true
282283
export NEXT_PUBLIC_ACCESS_CONTROL_ENABLED=true
283284
export API_ENCRYPTION_KEY="$ENCRYPTION_KEY"
284285
start_browser_install "$group" --no-shell chromium

‎apps/docs/content/docs/browser-extension/index.mdx‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ In version 0.1.4 or later, choose **+ → Page access → Allow across websites*
1919

2020
To share a screenshot, drag the image file onto the open chat. It uploads to Sim and appears as an attachment you can remove before sending. If a website supplies only an image URL, paste the image itself or use the file picker.
2121

22+
In version 0.1.4 or later, click the microphone beside Send to dictate a message, and allow microphone access if your browser asks. Click it again to stop. You can edit the transcript before sending. If access is blocked, **Open settings** takes you to the browser's microphone controls.
23+
2224
For faster access, press **Command + Shift + S** on macOS or **Alt + Shift + S** on Windows and Linux. The shortcut attaches highlighted text when you have a selection. It won't substitute the whole page for an unreadable selection. With no selection, it attaches the page or uses your send-time reference setting. On an already-open sidebar, Chrome may keep keyboard focus on the website. Click the message field, or use Chrome’s pane navigation (on Mac, **Command + Option + Up**).
2325

2426
Change or assign the shortcut under **Extensions → Keyboard shortcuts**.
@@ -36,3 +38,5 @@ If your organization has [Links](/search/sim-links), type `sim`, press **Space**
3638
Sim reads a page or selection when you attach it, or when you send a message with **Reference current page** enabled. With version 0.1.4 or later, **Allow across websites** provides access across tabs without repeated site approvals. Granting access doesn't start background capture. The page text goes to the hosted composer when attached and becomes part of the conversation when you send it.
3739

3840
See [Sim’s browser extension privacy disclosure](https://www.sim.ai/privacy#browser-extension).
41+
42+
Voice input sends microphone audio to Sim's transcription provider while recording. It does not record the tab's audio or start when you open the panel. The transcript stays in your draft until you send it.

‎apps/docs/content/docs/search/sim-links.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ To open links as `go/handbook` or `sim/handbook`, select **Enable go/ shortcuts*
2121

2222
Open the extension sidebar’s **+ → Browser shortcuts** and enable your search provider to recover `go/handbook` or `sim/handbook` when Chrome treats it as a search. Approve access to the exact sites shown. This is optional and supports Google (`google.com`), Bing, DuckDuckGo, and Yahoo. Sim remembers the choice in this Chrome profile. Ordinary searches still use your search engine; its default is unchanged.
2323

24-
Recovery matches a whole `go/<name>` or `sim/<name>` shortcut, including simple slash-separated values such as `go/jira/123`. For values containing spaces, query parameters, unsupported search domains, or a managed browser that blocks site access, use `sim` + **Space** or the full `https://www.sim.ai/sim/<name>` address. Turn recovery off in **Browser shortcuts**; this keeps the site permission, which you can remove through **Site access** or Chrome’s extension settings.
24+
Recovery matches a whole `go/<name>` or `sim/<name>` shortcut, including simple slash-separated values such as `go/jira/123`. For values containing spaces, query parameters, unsupported search domains, or a managed browser that blocks site access, use `sim` + **Space** or the full `https://www.sim.ai/sim/<name>` address. Turn recovery off in **Browser shortcuts**; this keeps site access. In version 0.1.4, open **Page access → Manage in browser** to remove access.
2525

2626
If `sim` + **Space** sends link names to your search engine even when the address bar shows **Sim**, a Chrome site search may already use the `sim` shortcut. Enter `http://sim/handbook` to open the link directly. To restore suggestions, temporarily turn off the Sim extension, rename the old shortcut in `chrome://settings/searchEngines`, then turn Sim back on. If your organization manages the shortcut, ask your administrator to rename it.
2727

‎apps/extension/DESIGN.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ conversation persistence, streaming recovery, tool permissions, and organization
1010
or workspace authorization. The panel keeps Fast mode and omits the model selector.
1111
A conversation can continue in the full app.
1212

13+
Voice input uses the existing microphone control beside Send. The browser asks for
14+
microphone permission on first use. Transcription updates the editable draft and
15+
never sends it automatically. Stop ends recording; Send and conversation changes
16+
discard unfinished transcription. The iframe delegates microphone access only to
17+
the configured Sim origin, and blocked access offers the browser's microphone settings.
18+
1319
The user signs in in a normal Sim tab and lands in their organization chat. Choosing
1420
a workspace is optional; members without Build access use Assistant. They can open
1521
or create a conversation and explicitly attach the current page or highlighted text.

‎apps/extension/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ SIM_EXTENSION_DEV=1 SIM_EXTENSION_ORIGIN=http://localhost:3000 bun run --cwd app
3939
- Source URLs omit credentials, query parameters, and fragments. Human-readable text can still contain sensitive information; it is the user-selected material they review and send into their conversation.
4040
- Snapshots stay in memory until handed to Sim and acknowledged or expired after 30 seconds. Extension storage contains only a short-lived intent with a tab/document identity and a SHA-256 URL digest, never raw URLs, captured text, or credentials. Closing a tab or navigating invalidates its pending intent. Sim applies its normal conversation retention once a message is sent.
4141
- No browser history, bookmarks, cookie access, debugger access, remote extension JavaScript, arbitrary network proxy, or background content collection is requested. Incognito use is disabled.
42-
- The iframe bridge checks both the exact configured origin and the exact frame, then validates versioned messages. The hosted composer may request only page capture, selection capture, or the native site-access and browser-shortcut dialogs. Requests require the current conversation nonce and Chrome transient user activation; arbitrary URLs, tabs, script execution, and network operations are never accepted. Permission grants still require a click in the extension-owned page-access control and browser approval. Shortcut settings are advertised separately so older installed shells keep working without the new action.
42+
- The iframe bridge checks both the exact configured origin and the exact frame, then validates versioned messages. The hosted composer may request only page capture, selection capture, the native page-access and browser-shortcut dialogs, or the browser's fixed microphone settings page. Requests require the current conversation nonce and Chrome transient user activation; arbitrary URLs, tabs, script execution, and network operations are never accepted. Permission grants still require a click in the extension-owned page-access control and browser approval. Shortcut settings are advertised separately so older installed shells keep working without the new action.
4343
- Captures and queued toolbar intents are scoped to the receiving conversation. Changing conversation, workspace, or account discards an unfinished handoff. Captured attachments remain explicit snapshots when the source tab changes.
44-
- The complete hosted document, including portaled source dialogs and menus, is blocked from session replay. Clipboard writing is delegated only to the configured Sim origin. Clipboard reading and microphone capture are not delegated; voice input remains available in the main app.
44+
- The complete hosted document, including portaled source dialogs and menus, is blocked from session replay. Clipboard writing and microphone capture are delegated only to the configured Sim origin; clipboard reading and camera access are not delegated. Voice input starts only after the user clicks the microphone and the browser grants permission. The shared Sim voice service streams microphone audio to ElevenLabs for transcription into the editable draft. Stop ends capture while allowing the final transcript to arrive; Send and conversation changes cancel unfinished transcription. There is no tab-audio capture or background recording.
4545

4646
With **Reference current page** enabled, a composer Send requests a single fresh snapshot for that conversation. The extension stores only the preference in local storage. A tab change does not read any content. Explicit snapshots take precedence; access denial, navigation and timeouts keep the draft unsent. **Allow across websites** works even when Chrome hides the active address; no address entry is required. The grant covers HTTP and HTTPS sites across tabs until the user revokes or restricts it in browser settings. Uploaded screenshot files stay in the hosted app’s normal attachment flow.
4747

‎apps/extension/src/search-recovery-dialog.tsx‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,7 @@ export function SearchRecoveryDialog({ onClose }: SearchRecoveryDialogProps) {
140140
</ChipModalField>
141141
))}
142142
<p className='px-2 text-[var(--text-muted)] text-caption'>
143-
Chrome asks for access to the sites shown above. Page reference stays unchanged. Turning
144-
a shortcut off keeps site access.
143+
Turning shortcuts off keeps browser site access.
145144
</p>
146145
{loadError && (
147146
<div className='flex flex-wrap items-center gap-2 px-2'>

‎apps/extension/src/sidepanel.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,8 @@ function SidePanel({ origin }: SidePanelProps) {
349349
if (!navigator.userActivation.isActive) return
350350
if (parsed.data.action === 'site-access') setAccessOpen(true)
351351
else if (parsed.data.action === 'browser-shortcuts') setShortcutsOpen(true)
352+
else if (parsed.data.action === 'microphone-settings')
353+
void chrome.tabs.create({ url: 'chrome://settings/content/microphone' })
352354
else void capture(parsed.data.action === 'capture-page' ? 'page' : 'selection')
353355
} else if (pendingRef.current?.id === parsed.data.snapshotId) {
354356
pendingRef.current = null

‎apps/sim/app/(landing)/privacy/privacy-content.tsx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,12 @@ export const PRIVACY_CONFIG: LegalPageConfig = {
803803
id: 'browser-extension',
804804
heading: '10B. Sim Browser Extension',
805805
blocks: [
806+
{
807+
kind: 'paragraph',
808+
content: richText(
809+
'Versions 0.1.4 and later support voice input. When You click the microphone and grant browser permission, Sim streams microphone audio to ElevenLabs for transcription into Your editable message draft. Opening the panel does not start recording, and this feature does not capture tab audio. Stopping voice input ends microphone capture; sending a message or changing conversations cancels unfinished transcription. Submitted transcripts become conversation content under the retention and processing rules below.'
810+
),
811+
},
806812
{
807813
kind: 'paragraph',
808814
content: richText(

‎apps/sim/app/extension/[workspaceId]/chat.tsx‎

Lines changed: 94 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { useChat } from '@/app/workspace/[workspaceId]/home/hooks/use-chat'
2020
import { fetchWorkspaceResourceRef } from '@/app/workspace/[workspaceId]/home/resolve-resource-ref'
2121
import type { FileAttachmentForApi } from '@/app/workspace/[workspaceId]/home/types'
2222
import { useMothershipChats } from '@/hooks/queries/mothership-chats'
23+
import { VoiceInputPermissionContext } from '@/hooks/use-voice-input'
2324
import type { ChatContext } from '@/stores/panel'
2425

2526
interface ExtensionChatProps {
@@ -119,99 +120,101 @@ export function ExtensionChat({ workspaceId, workspaceName }: ExtensionChatProps
119120
)
120121

121122
return (
122-
<main
123-
ref={fileDropTarget}
124-
className='ph-no-capture flex h-dvh min-w-0 flex-col overflow-hidden bg-[var(--bg)] text-[var(--text-body)]'
125-
>
126-
<ExtensionHeader
127-
workspaceId={workspaceId}
128-
scopeName={workspaceName}
129-
history={history}
130-
chatId={chat.resolvedChatId}
131-
onSelectChat={selectChat}
132-
appHref={appHref}
133-
/>
134-
{chat.error && (
135-
<p role='alert' className='px-3 py-2 text-[var(--text-error)] text-caption'>
136-
{chat.error}
137-
</p>
138-
)}
139-
{composer.captureError && (
140-
<p role='alert' className='px-3 py-2 text-[var(--text-error)] text-caption'>
141-
{composer.captureError}
142-
</p>
143-
)}
144-
{!selectedChatId && chat.messages.length === 0 && (
145-
<div className='flex flex-1 items-center justify-center px-5'>
146-
<h1 className='text-lg'>How can I help?</h1>
147-
</div>
148-
)}
149-
<MothershipChat
150-
onNavigateToChat={selectChat}
151-
workspaceId={workspaceId}
152-
userId={userId}
153-
chatId={chat.resolvedChatId}
154-
messages={chat.messages}
155-
isSending={chat.isSending}
156-
isReconnecting={chat.isReconnecting}
157-
isLoading={
158-
Boolean(selectedChatId) && chat.messages.length === 0 && chat.isChatHistoryPending
159-
}
160-
onSubmit={(message, files, contexts) => submit(message, files, contexts, false)}
161-
onComposerSubmit={submit}
162-
onStopGeneration={stop}
163-
messageQueue={chat.messageQueue}
164-
editingQueuedId={chat.editingQueuedId}
165-
dispatchingHeadId={chat.dispatchingHeadId}
166-
onRemoveQueuedMessage={chat.removeFromQueue}
167-
onSendQueuedMessage={chat.sendNow}
168-
onEditQueuedMessage={chat.editQueuedMessage}
169-
onCancelQueueEdit={chat.cancelQueueEdit}
170-
onViewSources={(messageId, requestId) =>
171-
void setSourceParams(
172-
{ 'source-message': messageId, 'source-request': requestId ?? null },
173-
{ history: 'push' }
174-
)
175-
}
176-
onWorkspaceResourceSelect={(resource) => void openResource(resource)}
177-
composerKey={composerKey}
178-
fileDropTarget={fileDropTarget}
179-
draftScopeKey={`extension:${userId}:${workspaceId}:${selectedChatId ?? 'new'}`}
180-
composerAttachments={composer.attachments}
181-
composerResourceActions={composer.resourceActions}
182-
composerToolbarActions={composer.toolbarActions}
183-
composerModelSelectorOptions={composer.modelSelectorOptions}
184-
layout='copilot-view'
185-
className={cn('h-auto min-h-0', (selectedChatId || chat.messages.length > 0) && 'flex-1')}
186-
/>
187-
<ChipModal
188-
open={Boolean(sourceMessage)}
189-
onOpenChange={(open) => {
190-
if (!open) closeSources()
191-
}}
192-
srTitle='Cited sources'
193-
size='sm'
123+
<VoiceInputPermissionContext value={composer.openMicrophoneSettings}>
124+
<main
125+
ref={fileDropTarget}
126+
className='ph-no-capture flex h-dvh min-w-0 flex-col overflow-hidden bg-[var(--bg)] text-[var(--text-body)]'
194127
>
195-
<ChipModalHeader onClose={closeSources}>Cited sources</ChipModalHeader>
196-
{sourceMessage && (
197-
<div className='h-[60dvh] min-h-0'>
198-
<SourcesResourceContent
199-
chatId={chat.resolvedChatId}
200-
resource={{
201-
id: 'cited-sources',
202-
type: 'sources',
203-
title: 'Sources',
204-
sources: {
205-
messageId: sourceMessage.id,
206-
...(sourceParams['source-request']
207-
? { requestId: sourceParams['source-request'] }
208-
: {}),
209-
},
210-
}}
211-
/>
128+
<ExtensionHeader
129+
workspaceId={workspaceId}
130+
scopeName={workspaceName}
131+
history={history}
132+
chatId={chat.resolvedChatId}
133+
onSelectChat={selectChat}
134+
appHref={appHref}
135+
/>
136+
{chat.error && (
137+
<p role='alert' className='px-3 py-2 text-[var(--text-error)] text-caption'>
138+
{chat.error}
139+
</p>
140+
)}
141+
{composer.captureError && (
142+
<p role='alert' className='px-3 py-2 text-[var(--text-error)] text-caption'>
143+
{composer.captureError}
144+
</p>
145+
)}
146+
{!selectedChatId && chat.messages.length === 0 && (
147+
<div className='flex flex-1 items-center justify-center px-5'>
148+
<h1 className='text-lg'>How can I help?</h1>
212149
</div>
213150
)}
214-
</ChipModal>
215-
</main>
151+
<MothershipChat
152+
onNavigateToChat={selectChat}
153+
workspaceId={workspaceId}
154+
userId={userId}
155+
chatId={chat.resolvedChatId}
156+
messages={chat.messages}
157+
isSending={chat.isSending}
158+
isReconnecting={chat.isReconnecting}
159+
isLoading={
160+
Boolean(selectedChatId) && chat.messages.length === 0 && chat.isChatHistoryPending
161+
}
162+
onSubmit={(message, files, contexts) => submit(message, files, contexts, false)}
163+
onComposerSubmit={submit}
164+
onStopGeneration={stop}
165+
messageQueue={chat.messageQueue}
166+
editingQueuedId={chat.editingQueuedId}
167+
dispatchingHeadId={chat.dispatchingHeadId}
168+
onRemoveQueuedMessage={chat.removeFromQueue}
169+
onSendQueuedMessage={chat.sendNow}
170+
onEditQueuedMessage={chat.editQueuedMessage}
171+
onCancelQueueEdit={chat.cancelQueueEdit}
172+
onViewSources={(messageId, requestId) =>
173+
void setSourceParams(
174+
{ 'source-message': messageId, 'source-request': requestId ?? null },
175+
{ history: 'push' }
176+
)
177+
}
178+
onWorkspaceResourceSelect={(resource) => void openResource(resource)}
179+
composerKey={composerKey}
180+
fileDropTarget={fileDropTarget}
181+
draftScopeKey={`extension:${userId}:${workspaceId}:${selectedChatId ?? 'new'}`}
182+
composerAttachments={composer.attachments}
183+
composerResourceActions={composer.resourceActions}
184+
composerToolbarActions={composer.toolbarActions}
185+
composerModelSelectorOptions={composer.modelSelectorOptions}
186+
layout='copilot-view'
187+
className={cn('h-auto min-h-0', (selectedChatId || chat.messages.length > 0) && 'flex-1')}
188+
/>
189+
<ChipModal
190+
open={Boolean(sourceMessage)}
191+
onOpenChange={(open) => {
192+
if (!open) closeSources()
193+
}}
194+
srTitle='Cited sources'
195+
size='sm'
196+
>
197+
<ChipModalHeader onClose={closeSources}>Cited sources</ChipModalHeader>
198+
{sourceMessage && (
199+
<div className='h-[60dvh] min-h-0'>
200+
<SourcesResourceContent
201+
chatId={chat.resolvedChatId}
202+
resource={{
203+
id: 'cited-sources',
204+
type: 'sources',
205+
title: 'Sources',
206+
sources: {
207+
messageId: sourceMessage.id,
208+
...(sourceParams['source-request']
209+
? { requestId: sourceParams['source-request'] }
210+
: {}),
211+
},
212+
}}
213+
/>
214+
</div>
215+
)}
216+
</ChipModal>
217+
</main>
218+
</VoiceInputPermissionContext>
216219
)
217220
}

0 commit comments

Comments
 (0)