diff --git a/src/app/(authenticated)/org/[[...organization-profile]]/CloudSettings.tsx b/src/app/(authenticated)/org/[[...organization-profile]]/CloudSettings.tsx deleted file mode 100644 index a3738d0a21..0000000000 --- a/src/app/(authenticated)/org/[[...organization-profile]]/CloudSettings.tsx +++ /dev/null @@ -1,162 +0,0 @@ -'use client'; - -import { useTranslations } from 'next-intl'; -import { useState } from 'react'; -import { useForm } from 'react-hook-form'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { toast } from 'sonner'; -import { Cloud } from 'lucide-react'; - -import { - type OrganizationSettings, - type OrganizationCloudSettings, -} from '@/types'; -import { - getOrganizationSettings, - updateOrganization, -} from '@/actions/organizationSettings'; -import { - Button, - Checkbox, - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, -} from '@/components/ui'; - -type CloudSettingsFormValues = { - recordTaskMessages: boolean; -}; - -type CloudSettingsFormProps = { - orgSettings: OrganizationSettings; - queryClient: ReturnType; -}; - -const CloudSettingsForm = ({ - orgSettings, - queryClient, -}: CloudSettingsFormProps) => { - const t = useTranslations('CloudSettings'); - const [isSaving, setIsSaving] = useState(false); - - const form = useForm({ - defaultValues: { - recordTaskMessages: - orgSettings.cloudSettings?.recordTaskMessages ?? false, - }, - }); - - const onSubmit = async (data: CloudSettingsFormValues) => { - setIsSaving(true); - - try { - const cloudSettings: OrganizationCloudSettings = { - recordTaskMessages: data.recordTaskMessages, - }; - - const result = await updateOrganization({ cloudSettings }); - - if (result.success) { - queryClient.invalidateQueries({ - queryKey: ['getOrganizationSettings'], - }); - toast.success('Cloud settings saved successfully'); - } else { - throw new Error(result.error || 'An unexpected error occurred.'); - } - } catch (error) { - console.error('Failed to update cloud settings:', error); - toast.error('Failed to save cloud settings. Please try again.'); - } finally { - setIsSaving(false); - } - }; - - return ( - <> -
-
-

- {t('cloud_section_title')} -

-
-
-

- {t('cloud_section_description')} -

-
-
- -
-
- -

Task Recording

-
-
- ( - - - - -
- {t('record_task_messages')} - - {t('record_task_messages_description')} - -
-
- )} - /> -
-
- -
- -
-
- -
- - ); -}; - -export const CloudSettings = () => { - const queryClient = useQueryClient(); - - const { data: orgSettings } = useQuery({ - queryKey: ['getOrganizationSettings'], - queryFn: getOrganizationSettings, - }); - - if (!orgSettings) { - return ( -
-
- Loading settings... -
- ); - } - - return ( - - ); -}; diff --git a/src/app/(authenticated)/org/[[...organization-profile]]/Org.tsx b/src/app/(authenticated)/org/[[...organization-profile]]/Org.tsx deleted file mode 100644 index 700ef6005c..0000000000 --- a/src/app/(authenticated)/org/[[...organization-profile]]/Org.tsx +++ /dev/null @@ -1,37 +0,0 @@ -'use client'; - -import { useTranslations } from 'next-intl'; -import { OrganizationProfile } from '@clerk/nextjs'; -import { ListTodo, Cloud } from 'lucide-react'; - -import { ProviderWhitelist } from './ProviderWhitelist'; -import { CloudSettings } from './CloudSettings'; - -export const Org = () => { - const t = useTranslations('OrganizationProfile'); - - return ( -
- - } - > - - - } - > - - - -
- ); -}; diff --git a/src/app/(authenticated)/org/[[...organization-profile]]/ProviderWhitelist.tsx b/src/app/(authenticated)/org/[[...organization-profile]]/ProviderWhitelist.tsx deleted file mode 100644 index 6d8b6e726c..0000000000 --- a/src/app/(authenticated)/org/[[...organization-profile]]/ProviderWhitelist.tsx +++ /dev/null @@ -1,335 +0,0 @@ -'use client'; - -import { useTranslations } from 'next-intl'; -import { useMemo, useState } from 'react'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { toast } from 'sonner'; - -import { type ProviderName } from '@roo-code/types'; - -import { type OrganizationSettings, ORGANIZATION_ALLOW_ALL } from '@/types'; -import { - getOrganizationSettings, - updateOrganization, -} from '@/actions/organizationSettings'; -import { Badge, Button, Checkbox, Label } from '@/components/ui'; -import MultipleSelector from '@/components/ui/multiple-selector'; - -import openRouterModels from '@/lib/data/openrouter-models.json'; -import anthropicModels from '@/lib/data/anthropic.json'; -import geminiModels from '@/lib/data/gemini.json'; -import deepseekModels from '@/lib/data/deepseek.json'; -import openaiNativeModels from '@/lib/data/openai-native.json'; -import vertexModels from '@/lib/data/vertex.json'; -import bedrockModels from '@/lib/data/bedrock.json'; -import mistralModels from '@/lib/data/mistral.json'; -import requestyModels from '@/lib/data/requesty-models.json'; -import groqModels from '@/lib/data/groq.json'; -import xaiModels from '@/lib/data/xai.json'; -import unboundModels from '@/lib/data/unbound.json'; -import glamaModels from '@/lib/data/glama.json'; -import chutesModels from '@/lib/data/chutes.json'; - -type BaseProvider = { - id: ProviderName; - label: string; - models: string[]; -}; - -const providers = ( - [ - { id: 'openrouter', label: 'OpenRouter', models: openRouterModels }, - { id: 'anthropic', label: 'Anthropic', models: anthropicModels }, - { id: 'gemini', label: 'Google Gemini', models: geminiModels }, - { id: 'deepseek', label: 'DeepSeek', models: deepseekModels }, - { id: 'openai-native', label: 'OpenAI', models: openaiNativeModels }, - { id: 'openai', label: 'OpenAI Compatible', models: [] }, - { id: 'vertex', label: 'GCP Vertex AI', models: vertexModels }, - { id: 'bedrock', label: 'Amazon Bedrock', models: bedrockModels }, - { id: 'glama', label: 'Glama', models: glamaModels }, - { id: 'vscode-lm', label: 'VS Code LM API', models: [] }, - { id: 'mistral', label: 'Mistral', models: mistralModels }, - { id: 'lmstudio', label: 'LM Studio', models: [] }, - { id: 'ollama', label: 'Ollama', models: [] }, - { id: 'unbound', label: 'Unbound', models: unboundModels }, - { id: 'requesty', label: 'Requesty', models: requestyModels }, - { id: 'human-relay', label: 'Human Relay', models: [] }, - { id: 'xai', label: 'xAI (Grok)', models: xaiModels }, - { id: 'groq', label: 'Groq', models: groqModels }, - { id: 'chutes', label: 'Chutes AI', models: chutesModels }, - { id: 'litellm', label: 'LiteLLM', models: [] }, - ] satisfies BaseProvider[] -).sort((a, b) => a.label.localeCompare(b.label)); - -type ProviderWhitelistFormProps = { - orgSettings: OrganizationSettings; - queryClient: ReturnType; -}; - -const ProviderWhitelistForm = ({ - orgSettings, - queryClient, -}: ProviderWhitelistFormProps) => { - const t = useTranslations('ProviderWhitelist'); - - const fullProviderMetadata = useMemo( - () => - providers.map((provider) => { - const models = orgSettings.allowList.providers[provider.id]?.models; - if (models) { - const providerModels = new Set(provider.models); - const difference = models.filter( - (model) => !providerModels.has(model), - ); - if (difference.length > 0) { - return { - ...provider, - models: [...provider.models, ...difference], - }; - } - } - - return provider; - }), - [orgSettings], - ); - - const [allowAll, setAllowAll] = useState(orgSettings.allowList.allowAll); - const [providerAllowAll, setProviderAllowAll] = useState( - Object.entries(orgSettings.allowList.providers).reduce( - (acc, [provider, providerSettings]) => { - if (providerSettings.allowAll) { - acc.add(provider); - } - return acc; - }, - new Set(), - ), - ); - const [providerModels, setProviderModels] = useState( - fullProviderMetadata.reduce((acc, meta) => { - acc.set(meta.id, orgSettings.allowList.providers[meta.id]?.models || []); - return acc; - }, new Map()), - ); - - const [hasChanges, setHasChanges] = useState(false); - const [isSaving, setIsSaving] = useState(false); - - const isProviderAllowAll = (providerId: ProviderName) => - providerAllowAll.has(providerId); - - const toggleAllowAll = () => { - setAllowAll(!allowAll); - - setHasChanges(true); - }; - - const toggleProvider = (providerId: ProviderName) => { - const newProviderAllowAll = new Set(providerAllowAll); - if (providerAllowAll.has(providerId)) { - newProviderAllowAll.delete(providerId); - } else { - newProviderAllowAll.add(providerId); - } - setProviderAllowAll(newProviderAllowAll); - - setHasChanges(true); - }; - - const setModels = (providerId: ProviderName, models: string[]) => { - const newProviderModels = new Map(providerModels); - newProviderModels.set(providerId, models); - setProviderModels(newProviderModels); - setHasChanges(true); - }; - - const saveChanges = async () => { - setIsSaving(true); - try { - let allowList; - if (allowAll) { - allowList = ORGANIZATION_ALLOW_ALL; - } else { - allowList = { - allowAll: false, - providers: fullProviderMetadata.reduce( - (acc, meta) => { - if (providerAllowAll.has(meta.id)) { - acc[meta.id] = { - allowAll: true, - }; - } else { - const models = providerModels.get(meta.id); - if (models && models.length > 0) { - acc[meta.id] = { - allowAll: false, - models: models, - }; - } - } - return acc; - }, - {} as Record< - ProviderName, - { - allowAll: boolean; - models?: string[]; - } - >, - ), - }; - } - - const result = await updateOrganization({ - allowList: allowList, - }); - - if (!result.success) { - throw new Error(result.error || 'Failed to update settings'); - } - - queryClient.invalidateQueries({ queryKey: ['organizationSettings'] }); - - toast.success('Provider settings saved successfully'); - setHasChanges(false); - } catch (error) { - console.error('Failed to save provider settings', error); - toast.error('Failed to save provider settings'); - } finally { - setIsSaving(false); - } - }; - - return ( - <> -
-
-

- {t('providers_section_title')} -

-
-
-

- {t('providers_section_description')} -

-
-
-
- - -
-
- {fullProviderMetadata.map((provider) => ( -
-
-
{provider.label}
-
- - toggleProvider(provider.id)} - /> -
-
-
- ({ - label: model, - value: model, - disable: providerModels.get(provider.id)?.includes(model), - }))} - value={(providerModels.get(provider.id) || []).map( - (model) => ({ - label: model, - value: model, - }), - )} - creatable - disabled={allowAll || isSaving} - placeholder="Pick models..." - onChange={(options) => - setModels( - provider.id, - options.map((option) => option.value), - ) - } - /> -
-
- ))} -
-
- - {`Policy v${orgSettings?.version || 1}`} - - - {isSaving - ? 'Updating provider whitelist...' - : hasChanges - ? 'You have unsaved changes' - : 'Changes will be pushed to SSE stream within 30 seconds'} - -
- - -
-
- - ); -}; - -export const ProviderWhitelist = () => { - const queryClient = useQueryClient(); - - const { data: orgSettings } = useQuery({ - queryKey: ['getOrganizationSettings'], - queryFn: getOrganizationSettings, - }); - - if (!orgSettings) { - return ( -
-
- Loading settings... -
- ); - } - - return ( - - ); -}; diff --git a/src/app/(authenticated)/org/[[...organization-profile]]/page.tsx b/src/app/(authenticated)/org/[[...organization-profile]]/page.tsx index fc1e941712..4db4168d10 100644 --- a/src/app/(authenticated)/org/[[...organization-profile]]/page.tsx +++ b/src/app/(authenticated)/org/[[...organization-profile]]/page.tsx @@ -1,5 +1,13 @@ -import { Org } from './Org'; +import { OrganizationProfile } from '@clerk/nextjs'; export default function Page() { - return ; + return ( +
+ +
+ ); } diff --git a/src/app/(authenticated)/providers/ProviderForm.tsx b/src/app/(authenticated)/providers/ProviderForm.tsx new file mode 100644 index 0000000000..53bf61c737 --- /dev/null +++ b/src/app/(authenticated)/providers/ProviderForm.tsx @@ -0,0 +1,227 @@ +import { useMemo, useState } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; +import { toast } from 'sonner'; + +import type { ProviderName } from '@roo-code/types'; + +import { type OrganizationSettings, ORGANIZATION_ALLOW_ALL } from '@/types'; +import { providers } from '@/lib/providers'; +import { updateOrganization } from '@/actions/organizationSettings'; +import { Badge, Button, Checkbox, Label } from '@/components/ui'; +import { MultipleSelector } from '@/components/ui/ecosystem'; +import { Loading } from '@/components/layout'; + +type ProviderFormProps = { + orgSettings: OrganizationSettings; +}; + +export const ProviderForm = ({ orgSettings }: ProviderFormProps) => { + const queryClient = useQueryClient(); + + const fullProviderMetadata = useMemo( + () => + providers.map((provider) => { + const models = orgSettings.allowList.providers[provider.id]?.models; + + if (models) { + const providerModels = new Set(provider.models); + + const difference = models.filter( + (model) => !providerModels.has(model), + ); + + if (difference.length > 0) { + return { + ...provider, + models: [...provider.models, ...difference], + }; + } + } + + return provider; + }), + [orgSettings], + ); + + const [allowAll, setAllowAll] = useState(orgSettings.allowList.allowAll); + + const [providerAllowAll, setProviderAllowAll] = useState( + Object.entries(orgSettings.allowList.providers).reduce( + (acc, [provider, providerSettings]) => { + if (providerSettings.allowAll) { + acc.add(provider); + } + + return acc; + }, + new Set(), + ), + ); + + const [providerModels, setProviderModels] = useState( + fullProviderMetadata.reduce((acc, meta) => { + acc.set(meta.id, orgSettings.allowList.providers[meta.id]?.models || []); + return acc; + }, new Map()), + ); + + const [hasChanges, setHasChanges] = useState(false); + const [isSaving, setIsSaving] = useState(false); + + const isProviderAllowAll = (providerId: ProviderName) => + providerAllowAll.has(providerId); + + const toggleAllowAll = () => { + setAllowAll(!allowAll); + + setHasChanges(true); + }; + + const toggleProvider = (providerId: ProviderName) => { + const newProviderAllowAll = new Set(providerAllowAll); + + if (providerAllowAll.has(providerId)) { + newProviderAllowAll.delete(providerId); + } else { + newProviderAllowAll.add(providerId); + } + + setProviderAllowAll(newProviderAllowAll); + setHasChanges(true); + }; + + const setModels = (providerId: ProviderName, models: string[]) => { + const newProviderModels = new Map(providerModels); + newProviderModels.set(providerId, models); + setProviderModels(newProviderModels); + setHasChanges(true); + }; + + const saveChanges = async () => { + setIsSaving(true); + + try { + let allowList; + + if (allowAll) { + allowList = ORGANIZATION_ALLOW_ALL; + } else { + allowList = { + allowAll: false, + providers: fullProviderMetadata.reduce( + (acc, meta) => { + if (providerAllowAll.has(meta.id)) { + acc[meta.id] = { allowAll: true }; + } else { + const models = providerModels.get(meta.id); + + if (models && models.length > 0) { + acc[meta.id] = { allowAll: false, models: models }; + } + } + + return acc; + }, + {} as Record< + ProviderName, + { allowAll: boolean; models?: string[] } + >, + ), + }; + } + + const result = await updateOrganization({ allowList: allowList }); + + if (!result.success) { + throw new Error(result.error || 'Failed to update settings'); + } + + queryClient.invalidateQueries({ queryKey: ['organizationSettings'] }); + + toast.success('Provider settings saved successfully'); + setHasChanges(false); + } catch (error) { + console.error('Failed to save provider settings', error); + toast.error('Failed to save provider settings'); + } finally { + setIsSaving(false); + } + }; + + return ( + <> +
+ + +
+
+ {fullProviderMetadata.map((provider) => ( +
+
+
{provider.label}
+
+ + toggleProvider(provider.id)} + /> +
+
+
+ ({ + label: model, + value: model, + disable: providerModels.get(provider.id)?.includes(model), + }))} + value={(providerModels.get(provider.id) || []).map((model) => ({ + label: model, + value: model, + }))} + creatable + disabled={allowAll || isSaving} + placeholder="Pick models..." + onChange={(options) => + setModels( + provider.id, + options.map((option) => option.value), + ) + } + /> +
+
+ ))} +
+
+ + {`Policy v${orgSettings?.version || 1}`} + + + {isSaving + ? 'Updating provider whitelist...' + : hasChanges + ? 'You have unsaved changes' + : 'Changes will be pushed to SSE stream within 30 seconds'} + +
+ + + ); +}; diff --git a/src/app/(authenticated)/providers/ProviderSettings.tsx b/src/app/(authenticated)/providers/ProviderSettings.tsx new file mode 100644 index 0000000000..76ac95f47b --- /dev/null +++ b/src/app/(authenticated)/providers/ProviderSettings.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { useQuery } from '@tanstack/react-query'; + +import { getOrganizationSettings } from '@/actions/organizationSettings'; +import { Card, CardHeader, CardTitle, CardDescription } from '@/components/ui'; +import { Loading } from '@/components/layout'; + +import { ProviderForm } from './ProviderForm'; + +export const ProviderSettings = () => { + const t = useTranslations('ProviderSettings'); + + const { data: orgSettings } = useQuery({ + queryKey: ['getOrganizationSettings'], + queryFn: getOrganizationSettings, + }); + + return ( + <> + + + {t('title')} + {t('description')} + + + {orgSettings ? : } + + ); +}; diff --git a/src/app/(authenticated)/providers/page.tsx b/src/app/(authenticated)/providers/page.tsx new file mode 100644 index 0000000000..0a1976ed3e --- /dev/null +++ b/src/app/(authenticated)/providers/page.tsx @@ -0,0 +1,5 @@ +import { ProviderSettings } from './ProviderSettings'; + +export default function Page() { + return ; +} diff --git a/src/app/(authenticated)/telemetry/TelemetryForm.tsx b/src/app/(authenticated)/telemetry/TelemetryForm.tsx new file mode 100644 index 0000000000..8c1c9a6c9f --- /dev/null +++ b/src/app/(authenticated)/telemetry/TelemetryForm.tsx @@ -0,0 +1,111 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { useQueryClient } from '@tanstack/react-query'; +import { toast } from 'sonner'; +import { Cloud } from 'lucide-react'; + +import type { OrganizationSettings, OrganizationCloudSettings } from '@/types'; +import { updateOrganization } from '@/actions/organizationSettings'; +import { + Button, + Checkbox, + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, +} from '@/components/ui'; +import { Loading } from '@/components/layout'; + +type FormData = { + recordTaskMessages: boolean; +}; + +type TelemetryFormProps = { + orgSettings: OrganizationSettings; +}; + +export const TelemetryForm = ({ orgSettings }: TelemetryFormProps) => { + const queryClient = useQueryClient(); + const t = useTranslations('TelemetrySettings'); + const [isSaving, setIsSaving] = useState(false); + + const form = useForm({ + defaultValues: { + recordTaskMessages: + orgSettings.cloudSettings?.recordTaskMessages ?? false, + }, + }); + + const onSubmit = async (data: FormData) => { + setIsSaving(true); + + try { + const cloudSettings: OrganizationCloudSettings = { + recordTaskMessages: data.recordTaskMessages, + }; + + const result = await updateOrganization({ cloudSettings }); + + if (result.success) { + queryClient.invalidateQueries({ + queryKey: ['getOrganizationSettings'], + }); + toast.success('Cloud settings saved successfully'); + } else { + throw new Error(result.error || 'An unexpected error occurred.'); + } + } catch (error) { + console.error('Failed to update cloud settings:', error); + toast.error('Failed to save cloud settings. Please try again.'); + } finally { + setIsSaving(false); + } + }; + + return ( +
+ +
+
+ +

Task Recording

+
+
+ ( + + + + +
+ {t('record_task_messages')} + + {t('record_task_messages_description')} + +
+
+ )} + /> +
+
+ +
+ +
+
+ + ); +}; diff --git a/src/app/(authenticated)/telemetry/TelemetrySettings.tsx b/src/app/(authenticated)/telemetry/TelemetrySettings.tsx new file mode 100644 index 0000000000..d69fbe14d4 --- /dev/null +++ b/src/app/(authenticated)/telemetry/TelemetrySettings.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { useTranslations } from 'next-intl'; +import { useQuery } from '@tanstack/react-query'; + +import { getOrganizationSettings } from '@/actions/organizationSettings'; +import { Card, CardHeader, CardTitle, CardDescription } from '@/components/ui'; +import { Loading } from '@/components/layout'; + +import { TelemetryForm } from './TelemetryForm'; + +export const TelemetrySettings = () => { + const t = useTranslations('TelemetrySettings'); + + const { data: orgSettings } = useQuery({ + queryKey: ['getOrganizationSettings'], + queryFn: getOrganizationSettings, + }); + + return ( + <> + + + {t('title')} + {t('description')} + + + {orgSettings ? : } + + ); +}; diff --git a/src/app/(authenticated)/telemetry/page.tsx b/src/app/(authenticated)/telemetry/page.tsx new file mode 100644 index 0000000000..242deeda83 --- /dev/null +++ b/src/app/(authenticated)/telemetry/page.tsx @@ -0,0 +1,5 @@ +import { TelemetrySettings } from './TelemetrySettings'; + +export default function Page() { + return ; +} diff --git a/src/app/(authenticated)/usage/Developers.tsx b/src/app/(authenticated)/usage/Developers.tsx index 5157c4f1f6..fa280125f9 100644 --- a/src/app/(authenticated)/usage/Developers.tsx +++ b/src/app/(authenticated)/usage/Developers.tsx @@ -6,7 +6,7 @@ import { useAuth } from '@clerk/nextjs'; import { type DeveloperUsage, getDeveloperUsage } from '@/actions/analytics'; import { formatCurrency, formatNumber } from '@/lib/formatters'; import { Button, Skeleton } from '@/components/ui'; -import { DataTable } from '@/components/layout/DataTable'; +import { DataTable } from '@/components/layout'; import type { Filter } from './types'; diff --git a/src/app/(authenticated)/usage/Models.tsx b/src/app/(authenticated)/usage/Models.tsx index 2e53acd578..9872e4a62a 100644 --- a/src/app/(authenticated)/usage/Models.tsx +++ b/src/app/(authenticated)/usage/Models.tsx @@ -6,7 +6,7 @@ import { useAuth } from '@clerk/nextjs'; import { type ModelUsage, getModelUsage } from '@/actions/analytics'; import { formatCurrency, formatNumber } from '@/lib/formatters'; import { Button, Skeleton } from '@/components/ui'; -import { DataTable } from '@/components/layout/DataTable'; +import { DataTable } from '@/components/layout'; import type { Filter } from './types'; diff --git a/src/app/(authenticated)/usage/Tasks.tsx b/src/app/(authenticated)/usage/Tasks.tsx index efcf69d93c..f6d98d57ea 100644 --- a/src/app/(authenticated)/usage/Tasks.tsx +++ b/src/app/(authenticated)/usage/Tasks.tsx @@ -8,7 +8,7 @@ import type { Task } from '@/types/analytics'; import { getTasks } from '@/actions/analytics'; import { formatNumber, formatCurrency } from '@/lib/formatters'; import { Button, Skeleton } from '@/components/ui'; -import { DataTable } from '@/components/layout/DataTable'; +import { DataTable } from '@/components/layout'; import type { Filter } from './types'; import { Status } from './Status'; diff --git a/src/app/(centered)/authorized/page.tsx b/src/app/(centered)/authorized/page.tsx index a6e738781c..6ee0091c63 100644 --- a/src/app/(centered)/authorized/page.tsx +++ b/src/app/(centered)/authorized/page.tsx @@ -44,7 +44,7 @@ export default function Page() { }, [router, isLoading, isSignedIn, orgId, authState.params]); return ( -
+
{isLoading ? ( ) : isSignedIn ? ( diff --git a/src/app/(centered)/select-org/SelectOrg.tsx b/src/app/(centered)/select-org/SelectOrg.tsx index baf4d6ace8..0e6b27a4c4 100644 --- a/src/app/(centered)/select-org/SelectOrg.tsx +++ b/src/app/(centered)/select-org/SelectOrg.tsx @@ -28,11 +28,7 @@ export const SelectOrg = () => { userSuggestions.isLoading; if (isLoading) { - return ( -
- -
- ); + return ; } const isBlocked = diff --git a/src/components/layout/Loading.tsx b/src/components/layout/Loading.tsx new file mode 100644 index 0000000000..097cb24b52 --- /dev/null +++ b/src/components/layout/Loading.tsx @@ -0,0 +1,7 @@ +import { LoaderCircle } from 'lucide-react'; + +export const Loading = () => ( +
+ +
+); diff --git a/src/components/layout/NavbarMenu.tsx b/src/components/layout/NavbarMenu.tsx index 146242d43e..c98099610b 100644 --- a/src/components/layout/NavbarMenu.tsx +++ b/src/components/layout/NavbarMenu.tsx @@ -13,6 +13,8 @@ const tabValues = [ '/dashboard', '/usage', '/audit-logs', + '/providers', + '/telemetry', '/org', '/hidden', ] as const; @@ -51,6 +53,8 @@ export const NavbarMenu = (props: NavbarMenuProps) => { Dashboard Usage Audit Logs + Providers + Telemetry Organization diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts index 6f22e1e3c7..ffe95f146b 100644 --- a/src/components/layout/index.ts +++ b/src/components/layout/index.ts @@ -5,6 +5,8 @@ export { Logo, HoppingLogo } from './Logo'; export { NavbarHeader } from './NavbarHeader'; export { NavbarMenu } from './NavbarMenu'; export { Connected } from './Connected'; +export { Loading } from './Loading'; +export { DataTable } from './DataTable'; export { ThemeProvider } from './ThemeProvider'; export { AuthProvider } from './AuthProvider'; diff --git a/src/components/ui/ecosystem/index.ts b/src/components/ui/ecosystem/index.ts index e1c2216c79..a8d117b634 100644 --- a/src/components/ui/ecosystem/index.ts +++ b/src/components/ui/ecosystem/index.ts @@ -1,2 +1,3 @@ export * from './button'; +export * from './multiple-selector'; export * from './tabs'; diff --git a/src/components/ui/multiple-selector.tsx b/src/components/ui/ecosystem/multiple-selector.tsx similarity index 99% rename from src/components/ui/multiple-selector.tsx rename to src/components/ui/ecosystem/multiple-selector.tsx index f5e62684c0..d5194b2970 100644 --- a/src/components/ui/multiple-selector.tsx +++ b/src/components/ui/ecosystem/multiple-selector.tsx @@ -1,18 +1,18 @@ 'use client'; -import { Command as CommandPrimitive, useCommandState } from 'cmdk'; -import { X } from 'lucide-react'; import * as React from 'react'; import { forwardRef, useEffect } from 'react'; +import { Command as CommandPrimitive, useCommandState } from 'cmdk'; +import { X } from 'lucide-react'; -import { Badge } from '@/components/ui/badge'; +import { cn } from '@/lib/utils'; import { + Badge, Command, CommandGroup, CommandItem, CommandList, -} from '@/components/ui/command'; -import { cn } from '@/lib/utils'; +} from '@/components/ui'; export interface Option { value: string; @@ -176,7 +176,7 @@ const CommandEmpty = forwardRef< CommandEmpty.displayName = 'CommandEmpty'; -const MultipleSelector = React.forwardRef< +export const MultipleSelector = React.forwardRef< MultipleSelectorRef, MultipleSelectorProps >( @@ -634,4 +634,3 @@ const MultipleSelector = React.forwardRef< ); MultipleSelector.displayName = 'MultipleSelector'; -export default MultipleSelector; diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index cf6b70162f..10bad1cdfd 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -1,5 +1,6 @@ export * from './badge'; export * from './button'; +export * from './command'; export * from './card'; export * from './checkbox'; export * from './drawer'; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 0851446c40..2a3385d7a3 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -20,17 +20,15 @@ "cloud_settings": "Cloud Settings", "default_parameters": "Default Parameters" }, - "ProviderWhitelist": { - "title": "Provider Whitelist & Default Parameters", - "description": "Control which AI providers are allowed and set default parameters for your organization", - "providers_section_title": "Allowed Providers", - "providers_section_description": "Select which AI providers and models are allowed to be used", + "ProviderSettings": { + "title": "Provider Settings", + "description": "Select which AI providers and models are allowed to be used", "parameters_section_title": "Default Parameters", "parameters_section_description": "Set global default parameters for AI model calls" }, - "CloudSettings": { - "cloud_section_title": "Cloud Settings", - "cloud_section_description": "Configure how your organization's data is handled in the cloud.", + "TelemetrySettings": { + "title": "Telemetry Settings", + "description": "Configure how your organization's data is handled in the cloud.", "record_task_messages": "Record task messages", "record_task_messages_description": "When enabled task messages and interactions will be recorded." }, diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 03894fa6db..e20ec9e6ce 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -20,17 +20,15 @@ "cloud_settings": "Paramètres Cloud", "default_parameters": "Paramètres par défaut" }, - "ProviderWhitelist": { - "title": "Liste blanche des fournisseurs et paramètres par défaut", - "description": "Contrôlez quels fournisseurs d'IA sont autorisés et définissez les paramètres par défaut pour votre organisation", - "providers_section_title": "Fournisseurs autorisés", - "providers_section_description": "Sélectionnez quels fournisseurs et modèles d'IA sont autorisés à être utilisés", + "ProviderSettings": { + "title": "Fournisseurs autorisés", + "description": "Sélectionnez quels fournisseurs et modèles d'IA sont autorisés à être utilisés", "parameters_section_title": "Paramètres par défaut", "parameters_section_description": "Définissez les paramètres par défaut globaux pour les appels aux modèles d'IA" }, - "CloudSettings": { - "cloud_section_title": "Paramètres Cloud", - "cloud_section_description": "Configurez la manière dont les données de votre organisation sont gérées dans le cloud.", + "TelemetrySettings": { + "title": "Paramètres Cloud", + "description": "Configurez la manière dont les données de votre organisation sont gérées dans le cloud.", "record_task_messages": "Enregistrer les messages de tâche", "record_task_messages_description": "Lorsque cette option est activée, les messages et interactions de tâche seront enregistrés." }, diff --git a/src/lib/providers.ts b/src/lib/providers.ts new file mode 100644 index 0000000000..38f4a6e9aa --- /dev/null +++ b/src/lib/providers.ts @@ -0,0 +1,47 @@ +import { type ProviderName } from '@roo-code/types'; + +import openRouterModels from '@/lib/data/openrouter-models.json'; +import anthropicModels from '@/lib/data/anthropic.json'; +import geminiModels from '@/lib/data/gemini.json'; +import deepseekModels from '@/lib/data/deepseek.json'; +import openaiNativeModels from '@/lib/data/openai-native.json'; +import vertexModels from '@/lib/data/vertex.json'; +import bedrockModels from '@/lib/data/bedrock.json'; +import mistralModels from '@/lib/data/mistral.json'; +import requestyModels from '@/lib/data/requesty-models.json'; +import groqModels from '@/lib/data/groq.json'; +import xaiModels from '@/lib/data/xai.json'; +import unboundModels from '@/lib/data/unbound.json'; +import glamaModels from '@/lib/data/glama.json'; +import chutesModels from '@/lib/data/chutes.json'; + +type BaseProvider = { + id: ProviderName; + label: string; + models: string[]; +}; + +export const providers = ( + [ + { id: 'openrouter', label: 'OpenRouter', models: openRouterModels }, + { id: 'anthropic', label: 'Anthropic', models: anthropicModels }, + { id: 'gemini', label: 'Google Gemini', models: geminiModels }, + { id: 'deepseek', label: 'DeepSeek', models: deepseekModels }, + { id: 'openai-native', label: 'OpenAI', models: openaiNativeModels }, + { id: 'openai', label: 'OpenAI Compatible', models: [] }, + { id: 'vertex', label: 'GCP Vertex AI', models: vertexModels }, + { id: 'bedrock', label: 'Amazon Bedrock', models: bedrockModels }, + { id: 'glama', label: 'Glama', models: glamaModels }, + { id: 'vscode-lm', label: 'VS Code LM API', models: [] }, + { id: 'mistral', label: 'Mistral', models: mistralModels }, + { id: 'lmstudio', label: 'LM Studio', models: [] }, + { id: 'ollama', label: 'Ollama', models: [] }, + { id: 'unbound', label: 'Unbound', models: unboundModels }, + { id: 'requesty', label: 'Requesty', models: requestyModels }, + { id: 'human-relay', label: 'Human Relay', models: [] }, + { id: 'xai', label: 'xAI (Grok)', models: xaiModels }, + { id: 'groq', label: 'Groq', models: groqModels }, + { id: 'chutes', label: 'Chutes AI', models: chutesModels }, + { id: 'litellm', label: 'LiteLLM', models: [] }, + ] satisfies BaseProvider[] +).sort((a, b) => a.label.localeCompare(b.label));