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
6 changes: 4 additions & 2 deletions apps/sim/app/api/copilot/api-keys/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export async function POST(req: NextRequest) {
// Move environment variable access inside the function
const SIM_AGENT_API_URL = env.SIM_AGENT_API_URL || SIM_AGENT_API_URL_DEFAULT

await req.json().catch(() => ({}))

const res = await fetch(`${SIM_AGENT_API_URL}/api/validate-key/generate`, {
method: 'POST',
headers: {
Expand All @@ -31,14 +33,14 @@ export async function POST(req: NextRequest) {
)
}

const data = (await res.json().catch(() => null)) as { apiKey?: string } | null
const data = (await res.json().catch(() => null)) as { apiKey?: string; id?: string } | null

if (!data?.apiKey) {
return NextResponse.json({ error: 'Invalid response from Sim Agent' }, { status: 500 })
}

return NextResponse.json(
{ success: true, key: { id: 'new', apiKey: data.apiKey } },
{ success: true, key: { id: data?.id || 'new', apiKey: data.apiKey } },
{ status: 201 }
)
} catch (error) {
Expand Down
7 changes: 6 additions & 1 deletion apps/sim/app/api/copilot/api-keys/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'Invalid response from Sim Agent' }, { status: 500 })
}

const keys = apiKeys
const keys = apiKeys.map((k) => {
const value = typeof k.apiKey === 'string' ? k.apiKey : ''
const last6 = value.slice(-6)
const displayKey = `•••••${last6}`
return { id: k.id, displayKey }
})

return NextResponse.json({ keys }, { status: 200 })
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export async function POST(req: NextRequest) {
const { toolName, payload } = ExecuteSchema.parse(body)

logger.info(`[${tracker.requestId}] Executing server tool`, { toolName })
const result = await routeExecution(toolName, payload)
const result = await routeExecution(toolName, payload, { userId })

try {
const resultPreview = JSON.stringify(result).slice(0, 300)
Expand Down
2 changes: 0 additions & 2 deletions apps/sim/app/api/copilot/tools/mark-complete/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ import { SIM_AGENT_API_URL_DEFAULT } from '@/lib/sim-agent/constants'

const logger = createLogger('CopilotMarkToolCompleteAPI')

// Sim Agent API configuration
const SIM_AGENT_API_URL = env.SIM_AGENT_API_URL || SIM_AGENT_API_URL_DEFAULT

// Schema for mark-complete request
const MarkCompleteSchema = z.object({
id: z.string(),
name: z.string(),
Expand Down
1 change: 0 additions & 1 deletion apps/sim/app/api/yaml/autolayout/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {

const logger = createLogger('YamlAutoLayoutAPI')

// Sim Agent API configuration
const SIM_AGENT_API_URL = env.SIM_AGENT_API_URL || SIM_AGENT_API_URL_DEFAULT

const AutoLayoutRequestSchema = z.object({
Expand Down
1 change: 0 additions & 1 deletion apps/sim/app/api/yaml/diff/create/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {

const logger = createLogger('YamlDiffCreateAPI')

// Sim Agent API configuration
const SIM_AGENT_API_URL = env.SIM_AGENT_API_URL || SIM_AGENT_API_URL_DEFAULT

const CreateDiffRequestSchema = z.object({
Expand Down
1 change: 0 additions & 1 deletion apps/sim/app/api/yaml/diff/merge/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import {

const logger = createLogger('YamlDiffMergeAPI')

// Sim Agent API configuration
const SIM_AGENT_API_URL = env.SIM_AGENT_API_URL || SIM_AGENT_API_URL_DEFAULT

const MergeDiffRequestSchema = z.object({
Expand Down
1 change: 0 additions & 1 deletion apps/sim/app/api/yaml/generate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/w

const logger = createLogger('YamlGenerateAPI')

// Sim Agent API configuration
const SIM_AGENT_API_URL = env.SIM_AGENT_API_URL || SIM_AGENT_API_URL_DEFAULT

const GenerateRequestSchema = z.object({
Expand Down
1 change: 0 additions & 1 deletion apps/sim/app/api/yaml/health/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { generateRequestId } from '@/lib/utils'

const logger = createLogger('YamlHealthAPI')

// Sim Agent API configuration
const SIM_AGENT_API_URL = env.SIM_AGENT_API_URL || SIM_AGENT_API_URL_DEFAULT

export async function GET() {
Expand Down
1 change: 0 additions & 1 deletion apps/sim/app/api/yaml/parse/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/w

const logger = createLogger('YamlParseAPI')

// Sim Agent API configuration
const SIM_AGENT_API_URL = env.SIM_AGENT_API_URL || SIM_AGENT_API_URL_DEFAULT

const ParseRequestSchema = z.object({
Expand Down
1 change: 0 additions & 1 deletion apps/sim/app/api/yaml/to-workflow/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/w

const logger = createLogger('YamlToWorkflowAPI')

// Sim Agent API configuration
const SIM_AGENT_API_URL = env.SIM_AGENT_API_URL || SIM_AGENT_API_URL_DEFAULT

const ConvertRequestSchema = z.object({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from 'react'
import { Check, Copy, Eye, EyeOff, Plus, Search } from 'lucide-react'
import { Check, Copy, Plus, Search } from 'lucide-react'
import {
AlertDialog,
AlertDialogAction,
Expand All @@ -13,55 +13,43 @@ import {
Input,
Label,
Skeleton,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui'
import { createLogger } from '@/lib/logs/console/logger'

const logger = createLogger('CopilotSettings')

interface CopilotKey {
id: string
apiKey: string
displayKey: string
}

export function Copilot() {
const [keys, setKeys] = useState<CopilotKey[]>([])
const [isLoading, setIsLoading] = useState(true)
const [visible, setVisible] = useState<Record<string, boolean>>({})
const [searchTerm, setSearchTerm] = useState('')

// Create flow state
const [showNewKeyDialog, setShowNewKeyDialog] = useState(false)
const [newKey, setNewKey] = useState<CopilotKey | null>(null)
const [copiedKeyIds, setCopiedKeyIds] = useState<Record<string, boolean>>({})
const [newKey, setNewKey] = useState<string | null>(null)
const [isCreatingKey] = useState(false)
const [newKeyCopySuccess, setNewKeyCopySuccess] = useState(false)

// Delete flow state
const [deleteKey, setDeleteKey] = useState<CopilotKey | null>(null)
const [showDeleteDialog, setShowDeleteDialog] = useState(false)

// Filter keys based on search term
// Filter keys based on search term (by masked display value)
const filteredKeys = keys.filter((key) =>
key.apiKey.toLowerCase().includes(searchTerm.toLowerCase())
key.displayKey.toLowerCase().includes(searchTerm.toLowerCase())
)

const maskedValue = useCallback((value: string, show: boolean) => {
if (show) return value
if (!value) return ''
const last6 = value.slice(-6)
return `•••••${last6}`
}, [])

const fetchKeys = useCallback(async () => {
try {
setIsLoading(true)
const res = await fetch('/api/copilot/api-keys')
if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`)
const data = await res.json()
setKeys(Array.isArray(data.keys) ? data.keys : [])
setKeys(Array.isArray(data.keys) ? (data.keys as CopilotKey[]) : [])
} catch (error) {
logger.error('Failed to fetch copilot keys', { error })
setKeys([])
Expand All @@ -83,11 +71,11 @@ export function Copilot() {
throw new Error(body.error || 'Failed to generate API key')
}
const data = await res.json()
// Show the new key dialog with the API key (only shown once)
if (data?.key) {
setNewKey(data.key)
if (data?.key?.apiKey) {
setNewKey(data.key.apiKey)
setShowNewKeyDialog(true)
}

await fetchKeys()
} catch (error) {
logger.error('Failed to generate copilot API key', { error })
Expand Down Expand Up @@ -117,12 +105,7 @@ export function Copilot() {
const onCopy = async (value: string, keyId?: string) => {
try {
await navigator.clipboard.writeText(value)
if (keyId) {
setCopiedKeyIds((prev) => ({ ...prev, [keyId]: true }))
setTimeout(() => {
setCopiedKeyIds((prev) => ({ ...prev, [keyId]: false }))
}, 1500)
} else {
if (!keyId) {
setNewKeyCopySuccess(true)
setTimeout(() => setNewKeyCopySuccess(false), 1500)
}
Expand Down Expand Up @@ -166,77 +149,32 @@ export function Copilot() {
</div>
) : (
<div className='space-y-2'>
{filteredKeys.map((k) => {
const isVisible = !!visible[k.id]
const value = maskedValue(k.apiKey, isVisible)
return (
<div key={k.id} className='flex flex-col gap-2'>
<Label className='font-normal text-muted-foreground text-xs uppercase'>
Copilot API Key
</Label>
<div className='flex items-center justify-between gap-4'>
<div className='flex items-center gap-3'>
<div className='flex h-8 items-center rounded-[8px] bg-muted px-3'>
<code className='font-mono text-foreground text-xs'>{value}</code>
</div>
<div className='flex items-center gap-2'>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='ghost'
size='icon'
onClick={() => setVisible((v) => ({ ...v, [k.id]: !isVisible }))}
className='h-4 w-4 p-0 text-muted-foreground transition-colors hover:bg-transparent hover:text-foreground'
>
{isVisible ? (
<EyeOff className='!h-3.5 !w-3.5' />
) : (
<Eye className='!h-3.5 !w-3.5' />
)}
</Button>
</TooltipTrigger>
<TooltipContent>{isVisible ? 'Hide' : 'Reveal'}</TooltipContent>
</Tooltip>
</TooltipProvider>

<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant='ghost'
size='icon'
onClick={() => onCopy(k.apiKey, k.id)}
className='h-4 w-4 p-0 text-muted-foreground transition-colors hover:bg-transparent hover:text-foreground'
>
{copiedKeyIds[k.id] ? (
<Check className='!h-3.5 !w-3.5' />
) : (
<Copy className='!h-3.5 !w-3.5' />
)}
</Button>
</TooltipTrigger>
<TooltipContent>Copy</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
{filteredKeys.map((k) => (
<div key={k.id} className='flex flex-col gap-2'>
<Label className='font-normal text-muted-foreground text-xs uppercase'>
Copilot API Key
</Label>
<div className='flex items-center justify-between gap-4'>
<div className='flex items-center gap-3'>
<div className='flex h-8 items-center rounded-[8px] bg-muted px-3'>
<code className='font-mono text-foreground text-xs'>{k.displayKey}</code>
</div>

<Button
variant='ghost'
size='sm'
onClick={() => {
setDeleteKey(k)
setShowDeleteDialog(true)
}}
className='h-8 text-muted-foreground hover:text-foreground'
>
Delete
</Button>
</div>

<Button
variant='ghost'
size='sm'
onClick={() => {
setDeleteKey(k)
setShowDeleteDialog(true)
}}
className='h-8 text-muted-foreground hover:text-foreground'
>
Delete
</Button>
</div>
)
})}
</div>
))}
{/* Show message when search has no results but there are keys */}
{searchTerm.trim() && filteredKeys.length === 0 && keys.length > 0 && (
<div className='py-8 text-center text-muted-foreground text-sm'>
Expand Down Expand Up @@ -265,7 +203,7 @@ export function Copilot() {
disabled={isLoading}
>
<Plus className='h-4 w-4 stroke-[2px]' />
Generate Key
Create Key
</Button>
</>
)}
Expand All @@ -285,24 +223,23 @@ export function Copilot() {
>
<AlertDialogContent className='rounded-[10px] sm:max-w-lg'>
<AlertDialogHeader>
<AlertDialogTitle>New Copilot API Key</AlertDialogTitle>
<AlertDialogTitle>Your API key has been created</AlertDialogTitle>
<AlertDialogDescription>
<span className='font-semibold'>Copy it now</span> and store it securely.
This is the only time you will see your API key.{' '}
<span className='font-semibold'>Copy it now and store it securely.</span>
</AlertDialogDescription>
</AlertDialogHeader>

{newKey && (
<div className='relative'>
<div className='flex h-9 items-center rounded-[6px] border-none bg-muted px-3 pr-8'>
<code className='flex-1 truncate font-mono text-foreground text-sm'>
{newKey.apiKey}
</code>
<code className='flex-1 truncate font-mono text-foreground text-sm'>{newKey}</code>
</div>
<Button
variant='ghost'
size='icon'
className='-translate-y-1/2 absolute top-1/2 right-2 h-4 w-4 rounded-[4px] p-0 text-muted-foreground transition-colors hover:bg-transparent hover:text-foreground'
onClick={() => onCopy(newKey.apiKey)}
onClick={() => onCopy(newKey)}
>
{newKeyCopySuccess ? (
<Check className='!h-3.5 !w-3.5' />
Expand All @@ -320,7 +257,7 @@ export function Copilot() {
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<AlertDialogContent className='rounded-[10px] sm:max-w-md'>
<AlertDialogHeader>
<AlertDialogTitle>Delete Copilot API key?</AlertDialogTitle>
<AlertDialogTitle>Delete API key?</AlertDialogTitle>
<AlertDialogDescription>
Deleting this API key will immediately revoke access for any integrations using it.{' '}
<span className='text-red-500 dark:text-red-500'>This action cannot be undone.</span>
Expand Down Expand Up @@ -353,20 +290,16 @@ export function Copilot() {
)
}

// Loading skeleton for Copilot API keys
function CopilotKeySkeleton() {
return (
<div className='flex flex-col gap-2'>
<Skeleton className='h-4 w-32' /> {/* API key label */}
<Skeleton className='h-4 w-32' />
<div className='flex items-center justify-between gap-4'>
<div className='flex items-center gap-3'>
<Skeleton className='h-8 w-40 rounded-[8px]' /> {/* Key preview */}
<div className='flex items-center gap-2'>
<Skeleton className='h-4 w-4' /> {/* Show/Hide button */}
<Skeleton className='h-4 w-4' /> {/* Copy button */}
</div>
<Skeleton className='h-8 w-20 rounded-[8px]' />
<Skeleton className='h-4 w-24' />
</div>
<Skeleton className='h-8 w-16' /> {/* Delete button */}
<Skeleton className='h-8 w-16' />
</div>
</div>
)
Expand Down
Loading