Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions apps/sim/app/(interfaces)/chat/[identifier]/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ interface ChatRequestPayload {
files?: ChatRequestFile[]
}

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

export default function ChatClient({ identifier }: { identifier: string }) {
const [messages, setMessages] = useState<ChatMessage[]>([])
const [inputValue, setInputValue] = useState('')
const [isLoading, setIsLoading] = useState(false)
const messagesEndRef = useRef<HTMLDivElement>(null)
const messagesContainerRef = useRef<HTMLDivElement>(null)
Expand Down Expand Up @@ -164,7 +160,7 @@ export default function ChatClient({ identifier }: { identifier: string }) {
}, [chatConfig, authRequired])

const handleSendMessage = async (
messageParam?: string,
messageToSend: string,
files?: Array<{
id: string
name: string
Expand All @@ -174,7 +170,6 @@ export default function ChatClient({ identifier }: { identifier: string }) {
dataUrl?: string
}>
) => {
const messageToSend = messageParam ?? inputValue
if ((!messageToSend.trim() && (!files || files.length === 0)) || isLoading) return

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

setMessages((prev) => [...prev, userMessage])
setInputValue('')
setIsLoading(true)

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

{/* Message Container component */}
<ChatMessageContainer
messages={displayMessages}
isLoading={isLoading}
Expand All @@ -337,7 +329,6 @@ export default function ChatClient({ identifier }: { identifier: string }) {
chatConfig={chatConfig}
/>

{/* Input area (free-standing at the bottom) */}
<div className='relative p-3 pb-4 md:p-4 md:pb-6'>
<div className='relative mx-auto max-w-3xl md:max-w-[748px]'>
<ChatInput
Expand Down
7 changes: 0 additions & 7 deletions apps/sim/app/(interfaces)/chat/components/input/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ export const ChatInput: React.FC<{
<Tooltip.Provider>
<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'>
<div className='w-full max-w-3xl md:max-w-[748px]'>
{/* Error Messages */}
{uploadErrors.length > 0 && (
<div className='mb-3 flex flex-col gap-2'>
{uploadErrors.map((error, idx) => (
Expand All @@ -145,7 +144,6 @@ export const ChatInput: React.FC<{
</div>
)}

{/* Input container */}
<div
role='group'
aria-label='Chat message input'
Expand Down Expand Up @@ -180,7 +178,6 @@ export const ChatInput: React.FC<{
if (!isStreaming) handleFileSelect(e.dataTransfer.files)
}}
>
{/* File thumbnails */}
{attachedFiles.length > 0 && (
<div className='mb-1.5 flex flex-wrap gap-1.5'>
{attachedFiles.map((file) => (
Expand Down Expand Up @@ -221,7 +218,6 @@ export const ChatInput: React.FC<{
</div>
)}

{/* Textarea */}
<textarea
ref={textareaRef}
value={inputValue}
Expand All @@ -232,9 +228,7 @@ export const ChatInput: React.FC<{
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'
/>

{/* Bottom row */}
<div className='flex items-center justify-between'>
{/* Left: attach */}
<div>
<Tooltip.Root>
<Tooltip.Trigger asChild>
Expand Down Expand Up @@ -266,7 +260,6 @@ export const ChatInput: React.FC<{
/>
</div>

{/* Right: send */}
<div className='flex items-center gap-1.5'>
{isStreaming ? (
<Button
Expand Down
12 changes: 0 additions & 12 deletions apps/sim/app/(interfaces)/chat/hooks/use-chat-streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ export function useChatStreaming() {
logger.info('[useChatStreaming] handleStreamedResponse called')
setIsStreamingResponse(true)

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

const messageIdMap = new Map<string, string>()
const messageId = generateId()

const UI_BATCH_MAX_MS = 50
Expand Down Expand Up @@ -328,9 +326,6 @@ export function useChatStreaming() {
}

if (isChatThinkingFrame(json)) {
if (!messageIdMap.has(json.blockId)) {
messageIdMap.set(json.blockId, messageId)
}
accumulatedThinking += json.data
accumulatedThinkingRef.current = accumulatedThinking
isThinkingStreaming = true
Expand All @@ -341,9 +336,6 @@ export function useChatStreaming() {

if (isChatToolFrame(json)) {
const { blockId } = json
if (!messageIdMap.has(blockId)) {
messageIdMap.set(blockId, messageId)
}
// Tools starting means the turn's thinking phase is over — settle
// the thinking chrome (it re-opens if more thinking streams later).
if (json.phase === 'start' && isThinkingStreaming) {
Expand Down Expand Up @@ -546,9 +538,6 @@ export function useChatStreaming() {
// Answer text only — never append thinking/tool/unknown chunk frames blindly.
if (isChatChunkFrame(json)) {
const { blockId, chunk: contentChunk } = json
if (!messageIdMap.has(blockId)) {
messageIdMap.set(blockId, messageId)
}

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

return {
isStreamingResponse,
setIsStreamingResponse,
abortControllerRef,
stopStreaming,
handleStreamedResponse,
Expand Down
7 changes: 0 additions & 7 deletions apps/sim/app/api/speech/token/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'

const {
mockRecordUsage,
mockCheckActorUsageLimits,
mockVerifyWorkspaceMembership,
mockResolveBillingAttribution,
mockCheckAttributedUsageLimits,
mockToBillingContext,
mockCheckAndBillPayerOverageThreshold,
} = vi.hoisted(() => ({
mockRecordUsage: vi.fn(),
mockCheckActorUsageLimits: vi.fn(),
mockVerifyWorkspaceMembership: vi.fn(),
mockResolveBillingAttribution: vi.fn(),
mockCheckAttributedUsageLimits: vi.fn(),
Expand All @@ -36,10 +34,6 @@ vi.mock('@/lib/billing/core/billing-attribution', () => ({
toBillingContext: mockToBillingContext,
}))

vi.mock('@/lib/billing/calculations/usage-monitor', () => ({
checkActorUsageLimits: mockCheckActorUsageLimits,
}))

vi.mock('@/lib/billing/threshold-billing', () => ({
checkAndBillPayerOverageThreshold: mockCheckAndBillPayerOverageThreshold,
}))
Expand All @@ -64,7 +58,6 @@ beforeEach(() => {
setEnv({ ELEVENLABS_API_KEY: 'test-key' })
mockGetSession.mockResolvedValue({ user: { id: 'member-1' } })
mockRecordUsage.mockResolvedValue(undefined)
mockCheckActorUsageLimits.mockResolvedValue({ isExceeded: false })
mockCheckAttributedUsageLimits.mockResolvedValue({ isExceeded: false })
mockResolveBillingAttribution.mockImplementation(
({ actorUserId, workspaceId }: { actorUserId: string; workspaceId: string }) => ({
Expand Down
20 changes: 4 additions & 16 deletions apps/sim/app/api/speech/token/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import { type NextRequest, NextResponse } from 'next/server'
import { speechTokenBodySchema } from '@/lib/api/contracts/media/speech'
import { parseOptionalJsonBody } from '@/lib/api/server'
import { getSession } from '@/lib/auth'
import { checkActorUsageLimits } from '@/lib/billing/calculations/usage-monitor'
import {
type BillingAttributionSnapshot,
checkAttributedUsageLimits,
resolveBillingAttribution,
toBillingContext,
Expand Down Expand Up @@ -55,7 +53,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
const body = speechTokenBodySchema.safeParse(parsedBody.data ?? {})

let workspaceId: string | undefined
let billingAttribution: BillingAttributionSnapshot | undefined

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

if (!billingAttribution) {
billingAttribution = await resolveBillingAttribution({
actorUserId,
workspaceId,
})
}
const billingAttribution = await resolveBillingAttribution({ actorUserId, workspaceId })

if (isBillingEnabled) {
const rateCheck = await rateLimiter.checkRateLimitDirect(
Expand Down Expand Up @@ -115,9 +107,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
* and rate limited per user, so the overshoot is a knowable ceiling against
* an identified payer.
*/
const usageCheck = billingAttribution
? await checkAttributedUsageLimits(billingAttribution)
: await checkActorUsageLimits(actorUserId)
const usageCheck = await checkAttributedUsageLimits(billingAttribution)
if (usageCheck.isExceeded) {
return NextResponse.json(
{
Expand Down Expand Up @@ -158,7 +148,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
await recordUsage({
userId: actorUserId,
workspaceId,
...(billingAttribution ? toBillingContext(billingAttribution) : {}),
...toBillingContext(billingAttribution),
entries: [
{
category: 'fixed',
Expand All @@ -169,9 +159,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
},
],
})
if (billingAttribution) {
await checkAndBillPayerOverageThreshold(billingAttribution.billingEntity)
}
await checkAndBillPayerOverageThreshold(billingAttribution.billingEntity)
} catch (err) {
logger.warn('Failed to record voice input usage, continuing:', err)
}
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/lib/api/contracts/media/speech.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { defineRouteContract } from '@/lib/api/contracts/types'

export const speechTokenBodySchema = z
.object({
/** Editor/workspace voice: the workspace the session user is recording in. */
/** Workspace the session user is recording in. */
workspaceId: z.string().optional(),
})
.passthrough()
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/lib/core/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export const env = createEnv({
COHERE_API_KEY_1: z.string().min(1).optional(), // Primary Cohere API key for rotation
COHERE_API_KEY_2: z.string().min(1).optional(), // Additional Cohere API key for load balancing
COHERE_API_KEY_3: z.string().min(1).optional(), // Additional Cohere API key for load balancing
ELEVENLABS_API_KEY: z.string().min(1).optional(), // ElevenLabs API key for workspace speech-to-text
ELEVENLABS_API_KEY: z.string().min(1).optional(), // ElevenLabs API key for workspace speech-to-text
SERPER_API_KEY: z.string().min(1).optional(), // Serper API key for online search
EXA_API_KEY: z.string().min(1).optional(), // Exa AI API key for enhanced online search
BLACKLISTED_PROVIDERS: z.string().optional(), // Comma-separated provider IDs to hide (e.g., "openai,anthropic")
Expand Down
6 changes: 1 addition & 5 deletions apps/sim/lib/speech/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ export const SAMPLE_RATE = 16000
export const CHUNK_SEND_INTERVAL_MS = 250
export const MAX_SESSION_MS = 3 * 60 * 1000

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