mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Break settings out from Clerk org component (#54)
This commit is contained in:
parent
357bdefb8f
commit
d5db42ddf3
24 changed files with 505 additions and 568 deletions
|
|
@ -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<typeof useQueryClient>;
|
||||
};
|
||||
|
||||
const CloudSettingsForm = ({
|
||||
orgSettings,
|
||||
queryClient,
|
||||
}: CloudSettingsFormProps) => {
|
||||
const t = useTranslations('CloudSettings');
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const form = useForm<CloudSettingsFormValues>({
|
||||
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 (
|
||||
<>
|
||||
<div className="cl-header 🔒️ cl-internal-qo3qk7">
|
||||
<div className="cl-internal-1pr5xvn">
|
||||
<h1 className="cl-headerTitle 🔒️ cl-internal-190cjq9">
|
||||
{t('cloud_section_title')}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mb-6 text-sm text-muted-foreground">
|
||||
{t('cloud_section_description')}
|
||||
</p>
|
||||
<div className="space-y-8">
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<div className="space-y-4 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Cloud className="size-5" />
|
||||
<h2 className="text-lg font-medium">Task Recording</h2>
|
||||
</div>
|
||||
<div className="mt-4 space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="recordTaskMessages"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled={isSaving}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel>{t('record_task_messages')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('record_task_messages_description')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-4 border-t pt-4">
|
||||
<Button type="submit" disabled={isSaving}>
|
||||
{isSaving ? (
|
||||
<>
|
||||
<span className="mr-2">Saving...</span>
|
||||
<span className="animate-spin">⟳</span>
|
||||
</>
|
||||
) : (
|
||||
'Save Changes'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const CloudSettings = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data: orgSettings } = useQuery({
|
||||
queryKey: ['getOrganizationSettings'],
|
||||
queryFn: getOrganizationSettings,
|
||||
});
|
||||
|
||||
if (!orgSettings) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin text-2xl">⟳</div>
|
||||
<span className="ml-2">Loading settings...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CloudSettingsForm orgSettings={orgSettings} queryClient={queryClient} />
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<div className="mx-auto">
|
||||
<OrganizationProfile
|
||||
routing="path"
|
||||
path="/org"
|
||||
afterLeaveOrganizationUrl="/select-org"
|
||||
>
|
||||
<OrganizationProfile.Page
|
||||
label={t('provider_whitelist')}
|
||||
url="provider-whitelist"
|
||||
labelIcon={<ListTodo className="size-4" />}
|
||||
>
|
||||
<ProviderWhitelist />
|
||||
</OrganizationProfile.Page>
|
||||
<OrganizationProfile.Page
|
||||
label={t('cloud_settings')}
|
||||
url="cloud-settings"
|
||||
labelIcon={<Cloud className="size-4" />}
|
||||
>
|
||||
<CloudSettings />
|
||||
</OrganizationProfile.Page>
|
||||
</OrganizationProfile>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -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<typeof useQueryClient>;
|
||||
};
|
||||
|
||||
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<string>(),
|
||||
),
|
||||
);
|
||||
const [providerModels, setProviderModels] = useState(
|
||||
fullProviderMetadata.reduce((acc, meta) => {
|
||||
acc.set(meta.id, orgSettings.allowList.providers[meta.id]?.models || []);
|
||||
return acc;
|
||||
}, new Map<ProviderName, string[]>()),
|
||||
);
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div className="cl-header 🔒️ cl-internal-qo3qk7">
|
||||
<div className="cl-internal-1pr5xvn">
|
||||
<h1 className="cl-headerTitle 🔒️ cl-internal-190cjq9">
|
||||
{t('providers_section_title')}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mb-6 text-sm text-muted-foreground">
|
||||
{t('providers_section_description')}
|
||||
</p>
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-6">
|
||||
<div className="mb-4 flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="allow-all-providers"
|
||||
checked={allowAll}
|
||||
onCheckedChange={toggleAllowAll}
|
||||
disabled={isSaving}
|
||||
/>
|
||||
<Label
|
||||
htmlFor="allow-all-providers"
|
||||
className="text-sm font-medium"
|
||||
>
|
||||
Allow All Providers
|
||||
</Label>
|
||||
</div>
|
||||
<div className={allowAll ? 'opacity-50' : ''}>
|
||||
{fullProviderMetadata.map((provider) => (
|
||||
<div key={provider.id} className="mb-4">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<div className="text-sm font-medium">{provider.label}</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label
|
||||
htmlFor={`provider-${provider.id}`}
|
||||
className="text-sm"
|
||||
>
|
||||
Allow all models
|
||||
</Label>
|
||||
<Checkbox
|
||||
id={`provider-${provider.id}`}
|
||||
checked={isProviderAllowAll(provider.id)}
|
||||
disabled={allowAll || isSaving}
|
||||
onCheckedChange={() => toggleProvider(provider.id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-full">
|
||||
<MultipleSelector
|
||||
defaultOptions={provider.models.map((model) => ({
|
||||
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),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 pt-2">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{`Policy v${orgSettings?.version || 1}`}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{isSaving
|
||||
? 'Updating provider whitelist...'
|
||||
: hasChanges
|
||||
? 'You have unsaved changes'
|
||||
: 'Changes will be pushed to SSE stream within 30 seconds'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={saveChanges}
|
||||
disabled={!hasChanges || isSaving}
|
||||
className="mt-4"
|
||||
>
|
||||
{isSaving ? (
|
||||
<>
|
||||
<span className="mr-2">Saving...</span>
|
||||
<span className="animate-spin">⟳</span>
|
||||
</>
|
||||
) : (
|
||||
'Save Changes'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ProviderWhitelist = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data: orgSettings } = useQuery({
|
||||
queryKey: ['getOrganizationSettings'],
|
||||
queryFn: getOrganizationSettings,
|
||||
});
|
||||
|
||||
if (!orgSettings) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin text-2xl">⟳</div>
|
||||
<span className="ml-2">Loading settings...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ProviderWhitelistForm
|
||||
orgSettings={orgSettings}
|
||||
queryClient={queryClient}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,5 +1,13 @@
|
|||
import { Org } from './Org';
|
||||
import { OrganizationProfile } from '@clerk/nextjs';
|
||||
|
||||
export default function Page() {
|
||||
return <Org />;
|
||||
return (
|
||||
<div className="mx-auto">
|
||||
<OrganizationProfile
|
||||
routing="path"
|
||||
path="/org"
|
||||
afterLeaveOrganizationUrl="/select-org"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
227
src/app/(authenticated)/providers/ProviderForm.tsx
Normal file
227
src/app/(authenticated)/providers/ProviderForm.tsx
Normal file
|
|
@ -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<string>(),
|
||||
),
|
||||
);
|
||||
|
||||
const [providerModels, setProviderModels] = useState(
|
||||
fullProviderMetadata.reduce((acc, meta) => {
|
||||
acc.set(meta.id, orgSettings.allowList.providers[meta.id]?.models || []);
|
||||
return acc;
|
||||
}, new Map<ProviderName, string[]>()),
|
||||
);
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div className="mb-4 flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="allow-all-providers"
|
||||
checked={allowAll}
|
||||
onCheckedChange={toggleAllowAll}
|
||||
disabled={isSaving}
|
||||
/>
|
||||
<Label htmlFor="allow-all-providers" className="text-sm font-medium">
|
||||
Allow All Providers
|
||||
</Label>
|
||||
</div>
|
||||
<div className={allowAll ? 'opacity-50' : ''}>
|
||||
{fullProviderMetadata.map((provider) => (
|
||||
<div key={provider.id} className="mb-4">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<div className="text-sm font-medium">{provider.label}</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor={`provider-${provider.id}`} className="text-sm">
|
||||
Allow all models
|
||||
</Label>
|
||||
<Checkbox
|
||||
id={`provider-${provider.id}`}
|
||||
checked={isProviderAllowAll(provider.id)}
|
||||
disabled={allowAll || isSaving}
|
||||
onCheckedChange={() => toggleProvider(provider.id)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-full">
|
||||
<MultipleSelector
|
||||
defaultOptions={provider.models.map((model) => ({
|
||||
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),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 pt-2">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{`Policy v${orgSettings?.version || 1}`}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{isSaving
|
||||
? 'Updating provider whitelist...'
|
||||
: hasChanges
|
||||
? 'You have unsaved changes'
|
||||
: 'Changes will be pushed to SSE stream within 30 seconds'}
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
onClick={saveChanges}
|
||||
disabled={!hasChanges || isSaving}
|
||||
className="mt-4"
|
||||
>
|
||||
{isSaving ? <Loading /> : 'Save Changes'}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
31
src/app/(authenticated)/providers/ProviderSettings.tsx
Normal file
31
src/app/(authenticated)/providers/ProviderSettings.tsx
Normal file
|
|
@ -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 (
|
||||
<>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('title')}</CardTitle>
|
||||
<CardDescription>{t('description')}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
{orgSettings ? <ProviderForm orgSettings={orgSettings} /> : <Loading />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
5
src/app/(authenticated)/providers/page.tsx
Normal file
5
src/app/(authenticated)/providers/page.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { ProviderSettings } from './ProviderSettings';
|
||||
|
||||
export default function Page() {
|
||||
return <ProviderSettings />;
|
||||
}
|
||||
111
src/app/(authenticated)/telemetry/TelemetryForm.tsx
Normal file
111
src/app/(authenticated)/telemetry/TelemetryForm.tsx
Normal file
|
|
@ -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<FormData>({
|
||||
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 (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<div className="space-y-4 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Cloud className="size-5" />
|
||||
<h2 className="text-lg font-medium">Task Recording</h2>
|
||||
</div>
|
||||
<div className="mt-4 space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="recordTaskMessages"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled={isSaving}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel>{t('record_task_messages')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('record_task_messages_description')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-4 border-t pt-4">
|
||||
<Button type="submit" disabled={isSaving}>
|
||||
{isSaving ? <Loading /> : 'Save Changes'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
31
src/app/(authenticated)/telemetry/TelemetrySettings.tsx
Normal file
31
src/app/(authenticated)/telemetry/TelemetrySettings.tsx
Normal file
|
|
@ -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 (
|
||||
<>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('title')}</CardTitle>
|
||||
<CardDescription>{t('description')}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
{orgSettings ? <TelemetryForm orgSettings={orgSettings} /> : <Loading />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
5
src/app/(authenticated)/telemetry/page.tsx
Normal file
5
src/app/(authenticated)/telemetry/page.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { TelemetrySettings } from './TelemetrySettings';
|
||||
|
||||
export default function Page() {
|
||||
return <TelemetrySettings />;
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export default function Page() {
|
|||
}, [router, isLoading, isSignedIn, orgId, authState.params]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<div className="flex justify-center">
|
||||
{isLoading ? (
|
||||
<LoaderCircle className="animate-spin" />
|
||||
) : isSignedIn ? (
|
||||
|
|
|
|||
|
|
@ -28,11 +28,7 @@ export const SelectOrg = () => {
|
|||
userSuggestions.isLoading;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<LoaderCircle className="animate-spin" />
|
||||
</div>
|
||||
);
|
||||
return <LoaderCircle className="animate-spin" />;
|
||||
}
|
||||
|
||||
const isBlocked =
|
||||
|
|
|
|||
7
src/components/layout/Loading.tsx
Normal file
7
src/components/layout/Loading.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { LoaderCircle } from 'lucide-react';
|
||||
|
||||
export const Loading = () => (
|
||||
<div className="flex items-center justify-center">
|
||||
<LoaderCircle className="animate-spin" />
|
||||
</div>
|
||||
);
|
||||
|
|
@ -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) => {
|
|||
<TabsTrigger value="/dashboard">Dashboard</TabsTrigger>
|
||||
<TabsTrigger value="/usage">Usage</TabsTrigger>
|
||||
<TabsTrigger value="/audit-logs">Audit Logs</TabsTrigger>
|
||||
<TabsTrigger value="/providers">Providers</TabsTrigger>
|
||||
<TabsTrigger value="/telemetry">Telemetry</TabsTrigger>
|
||||
<TabsTrigger value="/org">Organization</TabsTrigger>
|
||||
<TabsTrigger value="/hidden" className="hidden" />
|
||||
</TabsList>
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export * from './button';
|
||||
export * from './multiple-selector';
|
||||
export * from './tabs';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
export * from './badge';
|
||||
export * from './button';
|
||||
export * from './command';
|
||||
export * from './card';
|
||||
export * from './checkbox';
|
||||
export * from './drawer';
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
},
|
||||
|
|
|
|||
47
src/lib/providers.ts
Normal file
47
src/lib/providers.ts
Normal file
|
|
@ -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));
|
||||
Loading…
Add table
Reference in a new issue