Skip to content

Commit 83988c1

Browse files
authored
refactor(chat): drop code the voice removal left unreachable (#6218)
Full-file cleanup pass over the files voice mode was removed from. - /api/speech/token: with the chatId branch gone, billingAttribution is always resolved, so four `billingAttribution ? ... : ...` guards and the checkActorUsageLimits fallback were unreachable. Removed, along with the now-unused imports and test mock. - chat.tsx: the inputValue state had one remaining writer (`''`) and no reader — ChatInput has owned its own input value since it went uncontrolled, and removing the voice-transcript caller left handleSendMessage always receiving an explicit message. - use-chat-streaming: messageIdMap was written in three frame handlers and never read, and setIsStreamingResponse was returned but never destructured by the only consumer. - Comments: dropped JSX section labels that restate the element beneath them and TSDoc that restates the identifier; narrowed the speech contract's workspaceId doc, which existed to contrast with the removed chatId field.
1 parent 78740c0 commit 83988c1

8 files changed

Lines changed: 8 additions & 59 deletions

File tree

apps/sim/app/(interfaces)/chat/[identifier]/chat.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@ interface ChatRequestPayload {
3939
files?: ChatRequestFile[]
4040
}
4141

42-
/**
43-
* Converts a File object to a base64 data URL
44-
*/
4542
function fileToBase64(file: File): Promise<string> {
4643
return new Promise((resolve, reject) => {
4744
const reader = new FileReader()
@@ -53,7 +50,6 @@ function fileToBase64(file: File): Promise<string> {
5350

5451
export default function ChatClient({ identifier }: { identifier: string }) {
5552
const [messages, setMessages] = useState<ChatMessage[]>([])
56-
const [inputValue, setInputValue] = useState('')
5753
const [isLoading, setIsLoading] = useState(false)
5854
const messagesEndRef = useRef<HTMLDivElement>(null)
5955
const messagesContainerRef = useRef<HTMLDivElement>(null)
@@ -164,7 +160,7 @@ export default function ChatClient({ identifier }: { identifier: string }) {
164160
}, [chatConfig, authRequired])
165161

166162
const handleSendMessage = async (
167-
messageParam?: string,
163+
messageToSend: string,
168164
files?: Array<{
169165
id: string
170166
name: string
@@ -174,7 +170,6 @@ export default function ChatClient({ identifier }: { identifier: string }) {
174170
dataUrl?: string
175171
}>
176172
) => {
177-
const messageToSend = messageParam ?? inputValue
178173
if ((!messageToSend.trim() && (!files || files.length === 0)) || isLoading) return
179174

180175
logger.info('Sending message:', {
@@ -201,7 +196,6 @@ export default function ChatClient({ identifier }: { identifier: string }) {
201196
}
202197

203198
setMessages((prev) => [...prev, userMessage])
204-
setInputValue('')
205199
setIsLoading(true)
206200

207201
setTimeout(() => {
@@ -322,10 +316,8 @@ export default function ChatClient({ identifier }: { identifier: string }) {
322316
return (
323317
<div className='light desktop-title-bar-page fixed inset-0 z-[100] flex flex-col bg-[var(--bg)] text-[var(--text-primary)]'>
324318
<DesktopTitleBarLane />
325-
{/* Header component */}
326319
<ChatHeader chatConfig={chatConfig} starCount={starCount} />
327320

328-
{/* Message Container component */}
329321
<ChatMessageContainer
330322
messages={displayMessages}
331323
isLoading={isLoading}
@@ -337,7 +329,6 @@ export default function ChatClient({ identifier }: { identifier: string }) {
337329
chatConfig={chatConfig}
338330
/>
339331

340-
{/* Input area (free-standing at the bottom) */}
341332
<div className='relative p-3 pb-4 md:p-4 md:pb-6'>
342333
<div className='relative mx-auto max-w-3xl md:max-w-[748px]'>
343334
<ChatInput

apps/sim/app/(interfaces)/chat/components/input/input.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ export const ChatInput: React.FC<{
134134
<Tooltip.Provider>
135135
<div className='fixed right-0 bottom-0 left-0 flex w-full items-center justify-center bg-gradient-to-t from-[var(--bg)] to-transparent px-4 pb-4 md:px-0 md:pb-4'>
136136
<div className='w-full max-w-3xl md:max-w-[748px]'>
137-
{/* Error Messages */}
138137
{uploadErrors.length > 0 && (
139138
<div className='mb-3 flex flex-col gap-2'>
140139
{uploadErrors.map((error, idx) => (
@@ -145,7 +144,6 @@ export const ChatInput: React.FC<{
145144
</div>
146145
)}
147146

148-
{/* Input container */}
149147
<div
150148
role='group'
151149
aria-label='Chat message input'
@@ -180,7 +178,6 @@ export const ChatInput: React.FC<{
180178
if (!isStreaming) handleFileSelect(e.dataTransfer.files)
181179
}}
182180
>
183-
{/* File thumbnails */}
184181
{attachedFiles.length > 0 && (
185182
<div className='mb-1.5 flex flex-wrap gap-1.5'>
186183
{attachedFiles.map((file) => (
@@ -221,7 +218,6 @@ export const ChatInput: React.FC<{
221218
</div>
222219
)}
223220

224-
{/* Textarea */}
225221
<textarea
226222
ref={textareaRef}
227223
value={inputValue}
@@ -232,9 +228,7 @@ export const ChatInput: React.FC<{
232228
className='m-0 h-auto min-h-[24px] w-full resize-none overflow-y-auto overflow-x-hidden border-0 bg-transparent p-1 text-[15px] text-[var(--text-primary)] leading-[24px] caret-[var(--text-primary)] outline-none [-ms-overflow-style:none] [scrollbar-width:none] placeholder:text-[var(--text-muted)] focus-visible:ring-0 focus-visible:ring-offset-0 [&::-webkit-scrollbar]:hidden'
233229
/>
234230

235-
{/* Bottom row */}
236231
<div className='flex items-center justify-between'>
237-
{/* Left: attach */}
238232
<div>
239233
<Tooltip.Root>
240234
<Tooltip.Trigger asChild>
@@ -266,7 +260,6 @@ export const ChatInput: React.FC<{
266260
/>
267261
</div>
268262

269-
{/* Right: send */}
270263
<div className='flex items-center gap-1.5'>
271264
{isStreaming ? (
272265
<Button

apps/sim/app/(interfaces)/chat/hooks/use-chat-streaming.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ export function useChatStreaming() {
146146
logger.info('[useChatStreaming] handleStreamedResponse called')
147147
setIsStreamingResponse(true)
148148

149-
// Prefer a shared controller from the caller (fetch + reader). Otherwise create one.
150149
if (streamingOptions?.abortController) {
151150
abortControllerRef.current = streamingOptions.abortController
152151
} else if (!abortControllerRef.current) {
@@ -181,7 +180,6 @@ export function useChatStreaming() {
181180
accumulatedToolCallsRef.current = snapshotToolCalls(toolCallOrder, toolCallsMap) ?? []
182181
}
183182

184-
const messageIdMap = new Map<string, string>()
185183
const messageId = generateId()
186184

187185
const UI_BATCH_MAX_MS = 50
@@ -328,9 +326,6 @@ export function useChatStreaming() {
328326
}
329327

330328
if (isChatThinkingFrame(json)) {
331-
if (!messageIdMap.has(json.blockId)) {
332-
messageIdMap.set(json.blockId, messageId)
333-
}
334329
accumulatedThinking += json.data
335330
accumulatedThinkingRef.current = accumulatedThinking
336331
isThinkingStreaming = true
@@ -341,9 +336,6 @@ export function useChatStreaming() {
341336

342337
if (isChatToolFrame(json)) {
343338
const { blockId } = json
344-
if (!messageIdMap.has(blockId)) {
345-
messageIdMap.set(blockId, messageId)
346-
}
347339
// Tools starting means the turn's thinking phase is over — settle
348340
// the thinking chrome (it re-opens if more thinking streams later).
349341
if (json.phase === 'start' && isThinkingStreaming) {
@@ -546,9 +538,6 @@ export function useChatStreaming() {
546538
// Answer text only — never append thinking/tool/unknown chunk frames blindly.
547539
if (isChatChunkFrame(json)) {
548540
const { blockId, chunk: contentChunk } = json
549-
if (!messageIdMap.has(blockId)) {
550-
messageIdMap.set(blockId, messageId)
551-
}
552541

553542
// First answer chunk settles thinking chrome (still visible, no longer “live”).
554543
if (isThinkingStreaming) {
@@ -652,7 +641,6 @@ export function useChatStreaming() {
652641

653642
return {
654643
isStreamingResponse,
655-
setIsStreamingResponse,
656644
abortControllerRef,
657645
stopStreaming,
658646
handleStreamedResponse,

apps/sim/app/api/speech/token/route.test.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'
1212

1313
const {
1414
mockRecordUsage,
15-
mockCheckActorUsageLimits,
1615
mockVerifyWorkspaceMembership,
1716
mockResolveBillingAttribution,
1817
mockCheckAttributedUsageLimits,
1918
mockToBillingContext,
2019
mockCheckAndBillPayerOverageThreshold,
2120
} = vi.hoisted(() => ({
2221
mockRecordUsage: vi.fn(),
23-
mockCheckActorUsageLimits: vi.fn(),
2422
mockVerifyWorkspaceMembership: vi.fn(),
2523
mockResolveBillingAttribution: vi.fn(),
2624
mockCheckAttributedUsageLimits: vi.fn(),
@@ -36,10 +34,6 @@ vi.mock('@/lib/billing/core/billing-attribution', () => ({
3634
toBillingContext: mockToBillingContext,
3735
}))
3836

39-
vi.mock('@/lib/billing/calculations/usage-monitor', () => ({
40-
checkActorUsageLimits: mockCheckActorUsageLimits,
41-
}))
42-
4337
vi.mock('@/lib/billing/threshold-billing', () => ({
4438
checkAndBillPayerOverageThreshold: mockCheckAndBillPayerOverageThreshold,
4539
}))
@@ -64,7 +58,6 @@ beforeEach(() => {
6458
setEnv({ ELEVENLABS_API_KEY: 'test-key' })
6559
mockGetSession.mockResolvedValue({ user: { id: 'member-1' } })
6660
mockRecordUsage.mockResolvedValue(undefined)
67-
mockCheckActorUsageLimits.mockResolvedValue({ isExceeded: false })
6861
mockCheckAttributedUsageLimits.mockResolvedValue({ isExceeded: false })
6962
mockResolveBillingAttribution.mockImplementation(
7063
({ actorUserId, workspaceId }: { actorUserId: string; workspaceId: string }) => ({

apps/sim/app/api/speech/token/route.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import { type NextRequest, NextResponse } from 'next/server'
55
import { speechTokenBodySchema } from '@/lib/api/contracts/media/speech'
66
import { parseOptionalJsonBody } from '@/lib/api/server'
77
import { getSession } from '@/lib/auth'
8-
import { checkActorUsageLimits } from '@/lib/billing/calculations/usage-monitor'
98
import {
10-
type BillingAttributionSnapshot,
119
checkAttributedUsageLimits,
1210
resolveBillingAttribution,
1311
toBillingContext,
@@ -55,7 +53,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
5553
const body = speechTokenBodySchema.safeParse(parsedBody.data ?? {})
5654

5755
let workspaceId: string | undefined
58-
let billingAttribution: BillingAttributionSnapshot | undefined
5956

6057
const session = await getSession()
6158
if (!session?.user?.id) {
@@ -80,12 +77,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
8077
return NextResponse.json({ error: 'Workspace context is required.' }, { status: 400 })
8178
}
8279

83-
if (!billingAttribution) {
84-
billingAttribution = await resolveBillingAttribution({
85-
actorUserId,
86-
workspaceId,
87-
})
88-
}
80+
const billingAttribution = await resolveBillingAttribution({ actorUserId, workspaceId })
8981

9082
if (isBillingEnabled) {
9183
const rateCheck = await rateLimiter.checkRateLimitDirect(
@@ -115,9 +107,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
115107
* and rate limited per user, so the overshoot is a knowable ceiling against
116108
* an identified payer.
117109
*/
118-
const usageCheck = billingAttribution
119-
? await checkAttributedUsageLimits(billingAttribution)
120-
: await checkActorUsageLimits(actorUserId)
110+
const usageCheck = await checkAttributedUsageLimits(billingAttribution)
121111
if (usageCheck.isExceeded) {
122112
return NextResponse.json(
123113
{
@@ -158,7 +148,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
158148
await recordUsage({
159149
userId: actorUserId,
160150
workspaceId,
161-
...(billingAttribution ? toBillingContext(billingAttribution) : {}),
151+
...toBillingContext(billingAttribution),
162152
entries: [
163153
{
164154
category: 'fixed',
@@ -169,9 +159,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
169159
},
170160
],
171161
})
172-
if (billingAttribution) {
173-
await checkAndBillPayerOverageThreshold(billingAttribution.billingEntity)
174-
}
162+
await checkAndBillPayerOverageThreshold(billingAttribution.billingEntity)
175163
} catch (err) {
176164
logger.warn('Failed to record voice input usage, continuing:', err)
177165
}

apps/sim/lib/api/contracts/media/speech.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { defineRouteContract } from '@/lib/api/contracts/types'
33

44
export const speechTokenBodySchema = z
55
.object({
6-
/** Editor/workspace voice: the workspace the session user is recording in. */
6+
/** Workspace the session user is recording in. */
77
workspaceId: z.string().optional(),
88
})
99
.passthrough()

apps/sim/lib/core/config/env.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export const env = createEnv({
183183
COHERE_API_KEY_1: z.string().min(1).optional(), // Primary Cohere API key for rotation
184184
COHERE_API_KEY_2: z.string().min(1).optional(), // Additional Cohere API key for load balancing
185185
COHERE_API_KEY_3: z.string().min(1).optional(), // Additional Cohere API key for load balancing
186-
ELEVENLABS_API_KEY: z.string().min(1).optional(), // ElevenLabs API key for workspace speech-to-text
186+
ELEVENLABS_API_KEY: z.string().min(1).optional(), // ElevenLabs API key for workspace speech-to-text
187187
SERPER_API_KEY: z.string().min(1).optional(), // Serper API key for online search
188188
EXA_API_KEY: z.string().min(1).optional(), // Exa AI API key for enhanced online search
189189
BLACKLISTED_PROVIDERS: z.string().optional(), // Comma-separated provider IDs to hide (e.g., "openai,anthropic")

apps/sim/lib/speech/config.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@ export const SAMPLE_RATE = 16000
55
export const CHUNK_SEND_INTERVAL_MS = 250
66
export const MAX_SESSION_MS = 3 * 60 * 1000
77

8-
/**
9-
* Whether a speech-to-text provider is configured.
10-
* Currently checks for `ELEVENLABS_API_KEY`.
11-
* To add a new provider: add its env check here.
12-
*/
8+
/** Whether a speech-to-text provider is configured. Add new providers' env checks here. */
139
export function hasSTTService(): boolean {
1410
return !!env.ELEVENLABS_API_KEY?.trim()
1511
}

0 commit comments

Comments
 (0)