import { useCallback, useState } from 'react' import { DotsHorizontalIcon } from '@radix-ui/react-icons' import { type Row } from '@tanstack/react-table' import { Trash2, Edit, Power, PowerOff, ExternalLink, ArrowRightLeft, Copy, Link, Loader2, } from 'lucide-react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { copyToClipboard } from '@/lib/copy-to-clipboard' import { Button } from '@/components/ui/button' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuShortcut, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { Tooltip, TooltipContent, TooltipTrigger, } from '@/components/ui/tooltip' import { useChatPresets } from '@/features/chat/hooks/use-chat-presets' import { resolveChatUrl, type ChatPreset } from '@/features/chat/lib/chat-links' import { sendToFluent } from '@/features/chat/lib/send-to-fluent' import { updateApiKeyStatus } from '../api' import { API_KEY_STATUS, ERROR_MESSAGES, SUCCESS_MESSAGES } from '../constants' import { apiKeySchema } from '../types' import { useApiKeys } from './api-keys-provider' function getServerAddress(): string { try { const raw = localStorage.getItem('status') if (raw) { const status = JSON.parse(raw) if (status.server_address) return status.server_address as string } } catch { /* empty */ } return window.location.origin } function encodeConnectionString(key: string, url: string): string { return JSON.stringify({ _type: 'newapi_channel_conn', key, url, }) } type DataTableRowActionsProps = { row: Row } export function DataTableRowActions({ row, }: DataTableRowActionsProps) { const { t } = useTranslation() const apiKey = apiKeySchema.parse(row.original) const { setOpen, setCurrentRow, triggerRefresh, setResolvedKey, resolveRealKey, } = useApiKeys() const isEnabled = apiKey.status === API_KEY_STATUS.ENABLED const { chatPresets, serverAddress } = useChatPresets() const [isTogglingStatus, setIsTogglingStatus] = useState(false) const hasChatPresets = chatPresets.length > 0 const handleOpenChatPreset = useCallback( async (preset: ChatPreset) => { const realKey = await resolveRealKey(apiKey.id) if (!realKey) return if (preset.type === 'fluent') { const success = sendToFluent(realKey, serverAddress) if (success) { toast.success(t('Sent the API key to FluentRead.')) } else { toast.info( t( 'FluentRead extension not detected. Please ensure it is installed and active.' ) ) } return } const resolvedUrl = resolveChatUrl({ template: preset.url, apiKey: realKey, serverAddress, }) if (!resolvedUrl) { toast.error(t('Invalid chat link. Please contact your administrator.')) return } if (typeof window === 'undefined') return try { window.open(resolvedUrl, '_blank', 'noopener') } catch { window.location.href = resolvedUrl } }, [resolveRealKey, apiKey.id, serverAddress, t] ) const handleToggleStatus = async ( e?: React.MouseEvent ) => { e?.stopPropagation() const newStatus = isEnabled ? API_KEY_STATUS.DISABLED : API_KEY_STATUS.ENABLED setIsTogglingStatus(true) try { const result = await updateApiKeyStatus(apiKey.id, newStatus) if (result.success) { const message = isEnabled ? t(SUCCESS_MESSAGES.API_KEY_DISABLED) : t(SUCCESS_MESSAGES.API_KEY_ENABLED) toast.success(message) triggerRefresh() } else { toast.error(result.message || t(ERROR_MESSAGES.STATUS_UPDATE_FAILED)) } } catch { toast.error(t(ERROR_MESSAGES.UNEXPECTED)) } finally { setIsTogglingStatus(false) } } return (
{isEnabled ? t('Disable') : t('Enable')} { const realKey = await resolveRealKey(apiKey.id) if (!realKey) return const ok = await copyToClipboard(realKey) if (ok) toast.success(t('Copied')) }} > {t('Copy Key')} { const realKey = await resolveRealKey(apiKey.id) if (!realKey) return const connStr = encodeConnectionString( realKey, getServerAddress() ) const ok = await copyToClipboard(connStr) if (ok) toast.success(t('Copied')) }} > {t('Copy Connection Info')} { setCurrentRow(apiKey) setOpen('update') }} > {t('Edit')} { const realKey = await resolveRealKey(apiKey.id) if (!realKey) return setResolvedKey(realKey) setCurrentRow(apiKey) setOpen('cc-switch') }} > {t('CC Switch')} {hasChatPresets && ( {t('Chat')} {chatPresets.map((preset) => ( handleOpenChatPreset(preset)} > {preset.name} {preset.type !== 'web' && ( )} ))} )} { setCurrentRow(apiKey) setOpen('delete') }} className='text-destructive focus:text-destructive' > {t('Delete')}
) }