Declarative providers (#5084)
Co-authored-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
+206
-37
@@ -382,7 +382,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateCustomProviderRequest"
|
||||
"$ref": "#/components/schemas/UpdateCustomProviderRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -409,6 +409,84 @@
|
||||
}
|
||||
},
|
||||
"/config/custom-providers/{id}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"super::routes::config_management"
|
||||
],
|
||||
"operationId": "get_custom_provider",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Custom provider retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoadedProvider"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Provider not found"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"tags": [
|
||||
"super::routes::config_management"
|
||||
],
|
||||
"operationId": "update_custom_provider",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateCustomProviderRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Custom provider updated successfully",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Provider not found"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"super::routes::config_management"
|
||||
@@ -2203,40 +2281,6 @@
|
||||
"$ref": "#/components/schemas/Message"
|
||||
}
|
||||
},
|
||||
"CreateCustomProviderRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"provider_type",
|
||||
"display_name",
|
||||
"api_url",
|
||||
"api_key",
|
||||
"models"
|
||||
],
|
||||
"properties": {
|
||||
"api_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"api_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"models": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"provider_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"supports_streaming": {
|
||||
"type": "boolean",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"CreateRecipeRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -2296,6 +2340,61 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"DeclarativeProviderConfig": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"engine",
|
||||
"display_name",
|
||||
"api_key_env",
|
||||
"base_url",
|
||||
"models"
|
||||
],
|
||||
"properties": {
|
||||
"api_key_env": {
|
||||
"type": "string"
|
||||
},
|
||||
"base_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"engine": {
|
||||
"$ref": "#/components/schemas/ProviderEngine"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"models": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ModelInfo"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"supports_streaming": {
|
||||
"type": "boolean",
|
||||
"nullable": true
|
||||
},
|
||||
"timeout_seconds": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"nullable": true,
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"DecodeRecipeRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -2979,6 +3078,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoadedProvider": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"config",
|
||||
"is_editable"
|
||||
],
|
||||
"properties": {
|
||||
"config": {
|
||||
"$ref": "#/components/schemas/DeclarativeProviderConfig"
|
||||
},
|
||||
"is_editable": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Message": {
|
||||
"type": "object",
|
||||
"description": "A message to or from an LLM",
|
||||
@@ -3347,7 +3461,8 @@
|
||||
"required": [
|
||||
"name",
|
||||
"metadata",
|
||||
"is_configured"
|
||||
"is_configured",
|
||||
"provider_type"
|
||||
],
|
||||
"properties": {
|
||||
"is_configured": {
|
||||
@@ -3358,9 +3473,20 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"provider_type": {
|
||||
"$ref": "#/components/schemas/ProviderType"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProviderEngine": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"openai",
|
||||
"ollama",
|
||||
"anthropic"
|
||||
]
|
||||
},
|
||||
"ProviderMetadata": {
|
||||
"type": "object",
|
||||
"description": "Metadata about a provider's configuration requirements and capabilities",
|
||||
@@ -3398,7 +3524,7 @@
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ModelInfo"
|
||||
},
|
||||
"description": "A list of currently known models with their capabilities\nTODO: eventually query the apis directly"
|
||||
"description": "A list of currently known models with their capabilities"
|
||||
},
|
||||
"model_doc_link": {
|
||||
"type": "string",
|
||||
@@ -3410,6 +3536,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProviderType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Preferred",
|
||||
"Builtin",
|
||||
"Declarative",
|
||||
"Custom"
|
||||
]
|
||||
},
|
||||
"ProvidersResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -4449,6 +4584,40 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"UpdateCustomProviderRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"engine",
|
||||
"display_name",
|
||||
"api_url",
|
||||
"api_key",
|
||||
"models"
|
||||
],
|
||||
"properties": {
|
||||
"api_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"api_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"engine": {
|
||||
"type": "string"
|
||||
},
|
||||
"models": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"supports_streaming": {
|
||||
"type": "boolean",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"UpdateProviderRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import type { Options as ClientOptions, TDataShape, Client } from './client';
|
||||
import type { AddSubRecipesData, AddSubRecipesResponses, AddSubRecipesErrors, ExtendPromptData, ExtendPromptResponses, ExtendPromptErrors, ResumeAgentData, ResumeAgentResponses, ResumeAgentErrors, UpdateSessionConfigData, UpdateSessionConfigResponses, UpdateSessionConfigErrors, StartAgentData, StartAgentResponses, StartAgentErrors, GetToolsData, GetToolsResponses, GetToolsErrors, UpdateAgentProviderData, UpdateAgentProviderResponses, UpdateAgentProviderErrors, UpdateRouterToolSelectorData, UpdateRouterToolSelectorResponses, UpdateRouterToolSelectorErrors, ReadAllConfigData, ReadAllConfigResponses, BackupConfigData, BackupConfigResponses, BackupConfigErrors, CreateCustomProviderData, CreateCustomProviderResponses, CreateCustomProviderErrors, RemoveCustomProviderData, RemoveCustomProviderResponses, RemoveCustomProviderErrors, GetExtensionsData, GetExtensionsResponses, GetExtensionsErrors, AddExtensionData, AddExtensionResponses, AddExtensionErrors, RemoveExtensionData, RemoveExtensionResponses, RemoveExtensionErrors, InitConfigData, InitConfigResponses, InitConfigErrors, UpsertPermissionsData, UpsertPermissionsResponses, UpsertPermissionsErrors, ProvidersData, ProvidersResponses, GetProviderModelsData, GetProviderModelsResponses, GetProviderModelsErrors, ReadConfigData, ReadConfigResponses, ReadConfigErrors, RecoverConfigData, RecoverConfigResponses, RecoverConfigErrors, RemoveConfigData, RemoveConfigResponses, RemoveConfigErrors, UpsertConfigData, UpsertConfigResponses, UpsertConfigErrors, ValidateConfigData, ValidateConfigResponses, ValidateConfigErrors, ConfirmPermissionData, ConfirmPermissionResponses, ConfirmPermissionErrors, ManageContextData, ManageContextResponses, ManageContextErrors, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, CreateRecipeData, CreateRecipeResponses, CreateRecipeErrors, DecodeRecipeData, DecodeRecipeResponses, DecodeRecipeErrors, DeleteRecipeData, DeleteRecipeResponses, DeleteRecipeErrors, EncodeRecipeData, EncodeRecipeResponses, EncodeRecipeErrors, ListRecipesData, ListRecipesResponses, ListRecipesErrors, ParseRecipeData, ParseRecipeResponses, ParseRecipeErrors, SaveRecipeData, SaveRecipeResponses, SaveRecipeErrors, ScanRecipeData, ScanRecipeResponses, ReplyData, ReplyResponses, ReplyErrors, CreateScheduleData, CreateScheduleResponses, CreateScheduleErrors, DeleteScheduleData, DeleteScheduleResponses, DeleteScheduleErrors, ListSchedulesData, ListSchedulesResponses, ListSchedulesErrors, UpdateScheduleData, UpdateScheduleResponses, UpdateScheduleErrors, InspectRunningJobData, InspectRunningJobResponses, InspectRunningJobErrors, KillRunningJobData, KillRunningJobResponses, PauseScheduleData, PauseScheduleResponses, PauseScheduleErrors, RunNowHandlerData, RunNowHandlerResponses, RunNowHandlerErrors, SessionsHandlerData, SessionsHandlerResponses, SessionsHandlerErrors, UnpauseScheduleData, UnpauseScheduleResponses, UnpauseScheduleErrors, ListSessionsData, ListSessionsResponses, ListSessionsErrors, ImportSessionData, ImportSessionResponses, ImportSessionErrors, GetSessionInsightsData, GetSessionInsightsResponses, GetSessionInsightsErrors, DeleteSessionData, DeleteSessionResponses, DeleteSessionErrors, GetSessionData, GetSessionResponses, GetSessionErrors, UpdateSessionDescriptionData, UpdateSessionDescriptionResponses, UpdateSessionDescriptionErrors, ExportSessionData, ExportSessionResponses, ExportSessionErrors, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesResponses, UpdateSessionUserRecipeValuesErrors, StatusData, StatusResponses } from './types.gen';
|
||||
import type { AddSubRecipesData, AddSubRecipesResponses, AddSubRecipesErrors, ExtendPromptData, ExtendPromptResponses, ExtendPromptErrors, ResumeAgentData, ResumeAgentResponses, ResumeAgentErrors, UpdateSessionConfigData, UpdateSessionConfigResponses, UpdateSessionConfigErrors, StartAgentData, StartAgentResponses, StartAgentErrors, GetToolsData, GetToolsResponses, GetToolsErrors, UpdateAgentProviderData, UpdateAgentProviderResponses, UpdateAgentProviderErrors, UpdateRouterToolSelectorData, UpdateRouterToolSelectorResponses, UpdateRouterToolSelectorErrors, ReadAllConfigData, ReadAllConfigResponses, BackupConfigData, BackupConfigResponses, BackupConfigErrors, CreateCustomProviderData, CreateCustomProviderResponses, CreateCustomProviderErrors, RemoveCustomProviderData, RemoveCustomProviderResponses, RemoveCustomProviderErrors, GetCustomProviderData, GetCustomProviderResponses, GetCustomProviderErrors, UpdateCustomProviderData, UpdateCustomProviderResponses, UpdateCustomProviderErrors, GetExtensionsData, GetExtensionsResponses, GetExtensionsErrors, AddExtensionData, AddExtensionResponses, AddExtensionErrors, RemoveExtensionData, RemoveExtensionResponses, RemoveExtensionErrors, InitConfigData, InitConfigResponses, InitConfigErrors, UpsertPermissionsData, UpsertPermissionsResponses, UpsertPermissionsErrors, ProvidersData, ProvidersResponses, GetProviderModelsData, GetProviderModelsResponses, GetProviderModelsErrors, ReadConfigData, ReadConfigResponses, ReadConfigErrors, RecoverConfigData, RecoverConfigResponses, RecoverConfigErrors, RemoveConfigData, RemoveConfigResponses, RemoveConfigErrors, UpsertConfigData, UpsertConfigResponses, UpsertConfigErrors, ValidateConfigData, ValidateConfigResponses, ValidateConfigErrors, ConfirmPermissionData, ConfirmPermissionResponses, ConfirmPermissionErrors, ManageContextData, ManageContextResponses, ManageContextErrors, StartOpenrouterSetupData, StartOpenrouterSetupResponses, StartTetrateSetupData, StartTetrateSetupResponses, CreateRecipeData, CreateRecipeResponses, CreateRecipeErrors, DecodeRecipeData, DecodeRecipeResponses, DecodeRecipeErrors, DeleteRecipeData, DeleteRecipeResponses, DeleteRecipeErrors, EncodeRecipeData, EncodeRecipeResponses, EncodeRecipeErrors, ListRecipesData, ListRecipesResponses, ListRecipesErrors, ParseRecipeData, ParseRecipeResponses, ParseRecipeErrors, SaveRecipeData, SaveRecipeResponses, SaveRecipeErrors, ScanRecipeData, ScanRecipeResponses, ReplyData, ReplyResponses, ReplyErrors, CreateScheduleData, CreateScheduleResponses, CreateScheduleErrors, DeleteScheduleData, DeleteScheduleResponses, DeleteScheduleErrors, ListSchedulesData, ListSchedulesResponses, ListSchedulesErrors, UpdateScheduleData, UpdateScheduleResponses, UpdateScheduleErrors, InspectRunningJobData, InspectRunningJobResponses, InspectRunningJobErrors, KillRunningJobData, KillRunningJobResponses, PauseScheduleData, PauseScheduleResponses, PauseScheduleErrors, RunNowHandlerData, RunNowHandlerResponses, RunNowHandlerErrors, SessionsHandlerData, SessionsHandlerResponses, SessionsHandlerErrors, UnpauseScheduleData, UnpauseScheduleResponses, UnpauseScheduleErrors, ListSessionsData, ListSessionsResponses, ListSessionsErrors, ImportSessionData, ImportSessionResponses, ImportSessionErrors, GetSessionInsightsData, GetSessionInsightsResponses, GetSessionInsightsErrors, DeleteSessionData, DeleteSessionResponses, DeleteSessionErrors, GetSessionData, GetSessionResponses, GetSessionErrors, UpdateSessionDescriptionData, UpdateSessionDescriptionResponses, UpdateSessionDescriptionErrors, ExportSessionData, ExportSessionResponses, ExportSessionErrors, UpdateSessionUserRecipeValuesData, UpdateSessionUserRecipeValuesResponses, UpdateSessionUserRecipeValuesErrors, StatusData, StatusResponses } from './types.gen';
|
||||
import { client as _heyApiClient } from './client.gen';
|
||||
|
||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
|
||||
@@ -134,6 +134,24 @@ export const removeCustomProvider = <ThrowOnError extends boolean = false>(optio
|
||||
});
|
||||
};
|
||||
|
||||
export const getCustomProvider = <ThrowOnError extends boolean = false>(options: Options<GetCustomProviderData, ThrowOnError>) => {
|
||||
return (options.client ?? _heyApiClient).get<GetCustomProviderResponses, GetCustomProviderErrors, ThrowOnError>({
|
||||
url: '/config/custom-providers/{id}',
|
||||
...options
|
||||
});
|
||||
};
|
||||
|
||||
export const updateCustomProvider = <ThrowOnError extends boolean = false>(options: Options<UpdateCustomProviderData, ThrowOnError>) => {
|
||||
return (options.client ?? _heyApiClient).put<UpdateCustomProviderResponses, UpdateCustomProviderErrors, ThrowOnError>({
|
||||
url: '/config/custom-providers/{id}',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const getExtensions = <ThrowOnError extends boolean = false>(options?: Options<GetExtensionsData, ThrowOnError>) => {
|
||||
return (options?.client ?? _heyApiClient).get<GetExtensionsResponses, GetExtensionsErrors, ThrowOnError>({
|
||||
url: '/config/extensions',
|
||||
|
||||
@@ -110,15 +110,6 @@ export type ContextManageResponse = {
|
||||
|
||||
export type Conversation = Array<Message>;
|
||||
|
||||
export type CreateCustomProviderRequest = {
|
||||
api_key: string;
|
||||
api_url: string;
|
||||
display_name: string;
|
||||
models: Array<string>;
|
||||
provider_type: string;
|
||||
supports_streaming?: boolean | null;
|
||||
};
|
||||
|
||||
export type CreateRecipeRequest = {
|
||||
author?: AuthorRequest | null;
|
||||
session_id: string;
|
||||
@@ -136,6 +127,21 @@ export type CreateScheduleRequest = {
|
||||
recipe_source: string;
|
||||
};
|
||||
|
||||
export type DeclarativeProviderConfig = {
|
||||
api_key_env: string;
|
||||
base_url: string;
|
||||
description?: string | null;
|
||||
display_name: string;
|
||||
engine: ProviderEngine;
|
||||
headers?: {
|
||||
[key: string]: string;
|
||||
} | null;
|
||||
models: Array<ModelInfo>;
|
||||
name: string;
|
||||
supports_streaming?: boolean | null;
|
||||
timeout_seconds?: number | null;
|
||||
};
|
||||
|
||||
export type DecodeRecipeRequest = {
|
||||
deeplink: string;
|
||||
};
|
||||
@@ -367,6 +373,11 @@ export type ListSchedulesResponse = {
|
||||
jobs: Array<ScheduledJob>;
|
||||
};
|
||||
|
||||
export type LoadedProvider = {
|
||||
config: DeclarativeProviderConfig;
|
||||
is_editable: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* A message to or from an LLM
|
||||
*/
|
||||
@@ -473,8 +484,11 @@ export type ProviderDetails = {
|
||||
is_configured: boolean;
|
||||
metadata: ProviderMetadata;
|
||||
name: string;
|
||||
provider_type: ProviderType;
|
||||
};
|
||||
|
||||
export type ProviderEngine = 'openai' | 'ollama' | 'anthropic';
|
||||
|
||||
/**
|
||||
* Metadata about a provider's configuration requirements and capabilities
|
||||
*/
|
||||
@@ -497,7 +511,6 @@ export type ProviderMetadata = {
|
||||
display_name: string;
|
||||
/**
|
||||
* A list of currently known models with their capabilities
|
||||
* TODO: eventually query the apis directly
|
||||
*/
|
||||
known_models: Array<ModelInfo>;
|
||||
/**
|
||||
@@ -510,6 +523,8 @@ export type ProviderMetadata = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ProviderType = 'Preferred' | 'Builtin' | 'Declarative' | 'Custom';
|
||||
|
||||
export type ProvidersResponse = {
|
||||
providers: Array<ProviderDetails>;
|
||||
};
|
||||
@@ -852,6 +867,15 @@ export type ToolResponse = {
|
||||
};
|
||||
};
|
||||
|
||||
export type UpdateCustomProviderRequest = {
|
||||
api_key: string;
|
||||
api_url: string;
|
||||
display_name: string;
|
||||
engine: string;
|
||||
models: Array<string>;
|
||||
supports_streaming?: boolean | null;
|
||||
};
|
||||
|
||||
export type UpdateProviderRequest = {
|
||||
model?: string | null;
|
||||
provider: string;
|
||||
@@ -1183,7 +1207,7 @@ export type BackupConfigResponses = {
|
||||
export type BackupConfigResponse = BackupConfigResponses[keyof BackupConfigResponses];
|
||||
|
||||
export type CreateCustomProviderData = {
|
||||
body: CreateCustomProviderRequest;
|
||||
body: UpdateCustomProviderRequest;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/config/custom-providers';
|
||||
@@ -1238,6 +1262,64 @@ export type RemoveCustomProviderResponses = {
|
||||
|
||||
export type RemoveCustomProviderResponse = RemoveCustomProviderResponses[keyof RemoveCustomProviderResponses];
|
||||
|
||||
export type GetCustomProviderData = {
|
||||
body?: never;
|
||||
path: {
|
||||
id: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/config/custom-providers/{id}';
|
||||
};
|
||||
|
||||
export type GetCustomProviderErrors = {
|
||||
/**
|
||||
* Provider not found
|
||||
*/
|
||||
404: unknown;
|
||||
/**
|
||||
* Internal server error
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type GetCustomProviderResponses = {
|
||||
/**
|
||||
* Custom provider retrieved successfully
|
||||
*/
|
||||
200: LoadedProvider;
|
||||
};
|
||||
|
||||
export type GetCustomProviderResponse = GetCustomProviderResponses[keyof GetCustomProviderResponses];
|
||||
|
||||
export type UpdateCustomProviderData = {
|
||||
body: UpdateCustomProviderRequest;
|
||||
path: {
|
||||
id: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/config/custom-providers/{id}';
|
||||
};
|
||||
|
||||
export type UpdateCustomProviderErrors = {
|
||||
/**
|
||||
* Provider not found
|
||||
*/
|
||||
404: unknown;
|
||||
/**
|
||||
* Internal server error
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type UpdateCustomProviderResponses = {
|
||||
/**
|
||||
* Custom provider updated successfully
|
||||
*/
|
||||
200: string;
|
||||
};
|
||||
|
||||
export type UpdateCustomProviderResponse = UpdateCustomProviderResponses[keyof UpdateCustomProviderResponses];
|
||||
|
||||
export type GetExtensionsData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
|
||||
@@ -18,6 +18,7 @@ import { useModelAndProvider } from '../../../ModelAndProviderContext';
|
||||
import type { View } from '../../../../utils/navigationUtils';
|
||||
import Model, { getProviderMetadata } from '../modelInterface';
|
||||
import { getPredefinedModelsFromEnv, shouldShowPredefinedModels } from '../predefinedModelsUtils';
|
||||
import { ProviderType } from '../../../../api';
|
||||
|
||||
type SwitchModelModalProps = {
|
||||
sessionId: string | null;
|
||||
@@ -165,8 +166,9 @@ export const SwitchModelModal = ({ sessionId, onClose, setView }: SwitchModelMod
|
||||
const results = await Promise.all(modelPromises);
|
||||
|
||||
// Process results and build grouped options
|
||||
const groupedOptions: { options: { value: string; label: string; provider: string }[] }[] =
|
||||
[];
|
||||
const groupedOptions: {
|
||||
options: { value: string; label: string; provider: string; providerType: ProviderType }[];
|
||||
}[] = [];
|
||||
const errors: string[] = [];
|
||||
|
||||
results.forEach(({ provider: p, models, error }) => {
|
||||
@@ -178,13 +180,19 @@ export const SwitchModelModal = ({ sessionId, onClose, setView }: SwitchModelMod
|
||||
options: p.metadata.known_models.map(({ name }) => ({
|
||||
value: name,
|
||||
label: name,
|
||||
providerType: p.provider_type,
|
||||
provider: p.name,
|
||||
})),
|
||||
});
|
||||
}
|
||||
} else if (models && models.length > 0) {
|
||||
groupedOptions.push({
|
||||
options: models.map((m) => ({ value: m, label: m, provider: p.name })),
|
||||
options: models.map((m) => ({
|
||||
value: m,
|
||||
label: m,
|
||||
provider: p.name,
|
||||
providerType: p.provider_type,
|
||||
})),
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -196,12 +204,14 @@ export const SwitchModelModal = ({ sessionId, onClose, setView }: SwitchModelMod
|
||||
|
||||
// Add the "Custom model" option to each provider group
|
||||
groupedOptions.forEach((group) => {
|
||||
const providerName = group.options[0]?.provider;
|
||||
if (providerName && !providerName.startsWith('custom_')) {
|
||||
const option = group.options[0];
|
||||
const providerName = option?.provider;
|
||||
if (providerName && option?.providerType !== 'Custom') {
|
||||
group.options.push({
|
||||
value: 'custom',
|
||||
label: 'Use custom model',
|
||||
provider: providerName,
|
||||
providerType: option?.providerType,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,7 +3,11 @@ import { ProviderCard } from './subcomponents/ProviderCard';
|
||||
import CardContainer from './subcomponents/CardContainer';
|
||||
import { ProviderModalProvider, useProviderModal } from './modal/ProviderModalProvider';
|
||||
import ProviderConfigurationModal from './modal/ProviderConfiguationModal';
|
||||
import { ProviderDetails, CreateCustomProviderRequest } from '../../../api';
|
||||
import {
|
||||
DeclarativeProviderConfig,
|
||||
ProviderDetails,
|
||||
UpdateCustomProviderRequest,
|
||||
} from '../../../api';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../ui/dialog';
|
||||
import CustomProviderForm from './modal/subcomponents/forms/CustomProviderForm';
|
||||
@@ -43,7 +47,6 @@ const CustomProviderCard = memo(function CustomProviderCard({ onClick }: { onCli
|
||||
);
|
||||
});
|
||||
|
||||
// Memoize the ProviderCards component
|
||||
const ProviderCards = memo(function ProviderCards({
|
||||
providers,
|
||||
isOnboarding,
|
||||
@@ -57,28 +60,69 @@ const ProviderCards = memo(function ProviderCards({
|
||||
}) {
|
||||
const { openModal } = useProviderModal();
|
||||
const [showCustomProviderModal, setShowCustomProviderModal] = useState(false);
|
||||
const [editingProvider, setEditingProvider] = useState<{
|
||||
id: string;
|
||||
config: DeclarativeProviderConfig;
|
||||
isEditable: boolean;
|
||||
} | null>(null);
|
||||
|
||||
// Memoize these functions so they don't get recreated on every render
|
||||
const configureProviderViaModal = useCallback(
|
||||
(provider: ProviderDetails) => {
|
||||
openModal(provider, {
|
||||
onSubmit: () => {
|
||||
// Only refresh if the function is provided
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
}
|
||||
},
|
||||
onDelete: (_values: unknown) => {
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
}
|
||||
},
|
||||
formProps: {},
|
||||
});
|
||||
async (provider: ProviderDetails) => {
|
||||
if (provider.provider_type === 'Custom' || provider.provider_type === 'Declarative') {
|
||||
const { getCustomProvider } = await import('../../../api');
|
||||
const result = await getCustomProvider({ path: { id: provider.name }, throwOnError: true });
|
||||
|
||||
if (result.data) {
|
||||
setEditingProvider({
|
||||
id: provider.name,
|
||||
config: result.data.config,
|
||||
isEditable: result.data.is_editable,
|
||||
});
|
||||
setShowCustomProviderModal(true);
|
||||
}
|
||||
} else {
|
||||
openModal(provider, {
|
||||
onSubmit: () => {
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
}
|
||||
},
|
||||
onDelete: (_values: unknown) => {
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
}
|
||||
},
|
||||
formProps: {},
|
||||
});
|
||||
}
|
||||
},
|
||||
[openModal, refreshProviders]
|
||||
);
|
||||
|
||||
const handleUpdateCustomProvider = useCallback(
|
||||
async (data: UpdateCustomProviderRequest) => {
|
||||
if (!editingProvider) return;
|
||||
|
||||
const { updateCustomProvider } = await import('../../../api');
|
||||
await updateCustomProvider({
|
||||
path: { id: editingProvider.id },
|
||||
body: data,
|
||||
throwOnError: true,
|
||||
});
|
||||
setShowCustomProviderModal(false);
|
||||
setEditingProvider(null);
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
}
|
||||
},
|
||||
[editingProvider, refreshProviders]
|
||||
);
|
||||
|
||||
const handleCloseModal = useCallback(() => {
|
||||
setShowCustomProviderModal(false);
|
||||
setEditingProvider(null);
|
||||
}, []);
|
||||
|
||||
const deleteProviderConfigViaModal = useCallback(
|
||||
(provider: ProviderDetails) => {
|
||||
openModal(provider, {
|
||||
@@ -95,22 +139,17 @@ const ProviderCards = memo(function ProviderCards({
|
||||
);
|
||||
|
||||
const handleCreateCustomProvider = useCallback(
|
||||
async (data: CreateCustomProviderRequest) => {
|
||||
try {
|
||||
const { createCustomProvider } = await import('../../../api');
|
||||
await createCustomProvider({ body: data });
|
||||
setShowCustomProviderModal(false);
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to create custom provider:', error);
|
||||
async (data: UpdateCustomProviderRequest) => {
|
||||
const { createCustomProvider } = await import('../../../api');
|
||||
await createCustomProvider({ body: data, throwOnError: true });
|
||||
setShowCustomProviderModal(false);
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
}
|
||||
},
|
||||
[refreshProviders]
|
||||
);
|
||||
|
||||
// Use useMemo to memoize the cards array
|
||||
const providerCards = useMemo(() => {
|
||||
// providers needs to be an array
|
||||
const providersArray = Array.isArray(providers) ? providers : [];
|
||||
@@ -138,21 +177,33 @@ const ProviderCards = memo(function ProviderCards({
|
||||
onProviderLaunch,
|
||||
]);
|
||||
|
||||
const initialData = editingProvider && {
|
||||
engine: editingProvider.config.engine.toLowerCase() + '_compatible',
|
||||
display_name: editingProvider.config.display_name,
|
||||
api_url: editingProvider.config.base_url,
|
||||
api_key: '',
|
||||
models: editingProvider.config.models.map((m) => m.name),
|
||||
supports_streaming: editingProvider.config.supports_streaming ?? true,
|
||||
};
|
||||
|
||||
const editable = editingProvider ? editingProvider.isEditable : true;
|
||||
const title = (editingProvider ? (editable ? 'Edit' : 'Configure') : 'Add') + ' Provider';
|
||||
return (
|
||||
<>
|
||||
{providerCards}
|
||||
|
||||
<Dialog open={showCustomProviderModal} onOpenChange={setShowCustomProviderModal}>
|
||||
<Dialog open={showCustomProviderModal} onOpenChange={handleCloseModal}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add Custom Provider</DialogTitle>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<CustomProviderForm
|
||||
onSubmit={handleCreateCustomProvider}
|
||||
onCancel={() => setShowCustomProviderModal(false)}
|
||||
initialData={initialData}
|
||||
isEditable={editable}
|
||||
onSubmit={editingProvider ? handleUpdateCustomProvider : handleCreateCustomProvider}
|
||||
onCancel={handleCloseModal}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</Dialog>{' '}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -162,7 +162,7 @@ export default function ProviderConfigurationModal() {
|
||||
}
|
||||
|
||||
try {
|
||||
const isCustomProvider = currentProvider.name.startsWith('custom_');
|
||||
const isCustomProvider = currentProvider.provider_type === 'Custom';
|
||||
|
||||
if (isCustomProvider) {
|
||||
await removeCustomProvider({
|
||||
|
||||
+171
-150
@@ -1,24 +1,25 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Input } from '../../../../../ui/input';
|
||||
import { Select } from '../../../../../ui/Select';
|
||||
import { Button } from '../../../../../ui/button';
|
||||
import { SecureStorageNotice } from '../SecureStorageNotice';
|
||||
import { Checkbox } from '@radix-ui/themes';
|
||||
import { UpdateCustomProviderRequest } from '../../../../../../api';
|
||||
|
||||
interface CustomProviderFormProps {
|
||||
onSubmit: (data: {
|
||||
provider_type: string;
|
||||
display_name: string;
|
||||
api_url: string;
|
||||
api_key: string;
|
||||
models: string[];
|
||||
supports_streaming: boolean;
|
||||
}) => void;
|
||||
onSubmit: (data: UpdateCustomProviderRequest) => void;
|
||||
onCancel: () => void;
|
||||
initialData: UpdateCustomProviderRequest | null;
|
||||
isEditable?: boolean;
|
||||
}
|
||||
|
||||
export default function CustomProviderForm({ onSubmit, onCancel }: CustomProviderFormProps) {
|
||||
const [providerType, setProviderType] = useState('openai_compatible');
|
||||
export default function CustomProviderForm({
|
||||
onSubmit,
|
||||
onCancel,
|
||||
initialData,
|
||||
isEditable,
|
||||
}: CustomProviderFormProps) {
|
||||
const [engine, setEngine] = useState('openai_compatible');
|
||||
const [displayName, setDisplayName] = useState('');
|
||||
const [apiUrl, setApiUrl] = useState('');
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
@@ -27,6 +28,22 @@ export default function CustomProviderForm({ onSubmit, onCancel }: CustomProvide
|
||||
const [supportsStreaming, setSupportsStreaming] = useState(true);
|
||||
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData) {
|
||||
const engineMap: Record<string, string> = {
|
||||
openai: 'openai_compatible',
|
||||
anthropic: 'anthropic_compatible',
|
||||
ollama: 'ollama_compatible',
|
||||
};
|
||||
|
||||
setEngine(engineMap[initialData.engine.toLowerCase()] || 'openai_compatible');
|
||||
setDisplayName(initialData.display_name);
|
||||
setApiUrl(initialData.api_url);
|
||||
setModels(initialData.models.join(', '));
|
||||
setSupportsStreaming(initialData.supports_streaming ?? true);
|
||||
}
|
||||
}, [initialData]);
|
||||
|
||||
const handleLocalModels = (checked: boolean) => {
|
||||
setIsLocalModel(checked);
|
||||
if (checked) {
|
||||
@@ -42,7 +59,7 @@ export default function CustomProviderForm({ onSubmit, onCancel }: CustomProvide
|
||||
const errors: Record<string, string> = {};
|
||||
if (!displayName) errors.displayName = 'Display name is required';
|
||||
if (!apiUrl) errors.apiUrl = 'API URL is required';
|
||||
if (!isLocalModel && !apiKey) errors.apiKey = 'API key is required';
|
||||
if (!isLocalModel && !apiKey && !initialData) errors.apiKey = 'API key is required';
|
||||
if (!models) errors.models = 'At least one model is required';
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
@@ -56,7 +73,7 @@ export default function CustomProviderForm({ onSubmit, onCancel }: CustomProvide
|
||||
.filter((m) => m);
|
||||
|
||||
onSubmit({
|
||||
provider_type: providerType,
|
||||
engine,
|
||||
display_name: displayName,
|
||||
api_url: apiUrl,
|
||||
api_key: apiKey,
|
||||
@@ -67,92 +84,94 @@ export default function CustomProviderForm({ onSubmit, onCancel }: CustomProvide
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="mt-4 space-y-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="provider-select"
|
||||
className="flex items-center text-sm font-medium text-textStandard mb-2"
|
||||
>
|
||||
Provider Type
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
</label>
|
||||
<Select
|
||||
id="provider-select"
|
||||
aria-invalid={!!validationErrors.providerType}
|
||||
aria-describedby={validationErrors.providerType ? 'provider-select-error' : undefined}
|
||||
options={[
|
||||
{ value: 'openai_compatible', label: 'OpenAI Compatible' },
|
||||
{ value: 'anthropic_compatible', label: 'Anthropic Compatible' },
|
||||
{ value: 'ollama_compatible', label: 'Ollama Compatible' },
|
||||
]}
|
||||
value={{
|
||||
value: providerType,
|
||||
label:
|
||||
providerType === 'openai_compatible'
|
||||
? 'OpenAI Compatible'
|
||||
: providerType === 'anthropic_compatible'
|
||||
? 'Anthropic Compatible'
|
||||
: 'Ollama Compatible',
|
||||
}}
|
||||
onChange={(option: unknown) => {
|
||||
const selectedOption = option as { value: string; label: string } | null;
|
||||
if (selectedOption) setProviderType(selectedOption.value);
|
||||
}}
|
||||
isSearchable={false}
|
||||
/>
|
||||
{validationErrors.providerType && (
|
||||
<p id="provider-select-error" className="text-red-500 text-sm mt-1">
|
||||
{validationErrors.providerType}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="display-name"
|
||||
className="flex items-center text-sm font-medium text-textStandard mb-2"
|
||||
>
|
||||
Display Name
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
</label>
|
||||
<Input
|
||||
id="display-name"
|
||||
value={displayName}
|
||||
onChange={(e) => setDisplayName(e.target.value)}
|
||||
placeholder="Your Provider Name"
|
||||
aria-invalid={!!validationErrors.displayName}
|
||||
aria-describedby={validationErrors.displayName ? 'display-name-error' : undefined}
|
||||
className={validationErrors.displayName ? 'border-red-500' : ''}
|
||||
/>
|
||||
{validationErrors.displayName && (
|
||||
<p id="display-name-error" className="text-red-500 text-sm mt-1">
|
||||
{validationErrors.displayName}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="api-url"
|
||||
className="flex items-center text-sm font-medium text-textStandard mb-2"
|
||||
>
|
||||
API URL
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
</label>
|
||||
<Input
|
||||
id="api-url"
|
||||
value={apiUrl}
|
||||
onChange={(e) => setApiUrl(e.target.value)}
|
||||
placeholder="https://api.example.com/v1/messages"
|
||||
aria-invalid={!!validationErrors.apiUrl}
|
||||
aria-describedby={validationErrors.apiUrl ? 'api-url-error' : undefined}
|
||||
className={validationErrors.apiUrl ? 'border-red-500' : ''}
|
||||
/>
|
||||
{validationErrors.apiUrl && (
|
||||
<p id="api-url-error" className="text-red-500 text-sm mt-1">
|
||||
{validationErrors.apiUrl}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{isEditable && (
|
||||
<>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="provider-select"
|
||||
className="flex items-center text-sm font-medium text-textStandard mb-2"
|
||||
>
|
||||
Provider Type
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
</label>
|
||||
<Select
|
||||
id="provider-select"
|
||||
aria-invalid={!!validationErrors.providerType}
|
||||
aria-describedby={validationErrors.providerType ? 'provider-select-error' : undefined}
|
||||
options={[
|
||||
{ value: 'openai_compatible', label: 'OpenAI Compatible' },
|
||||
{ value: 'anthropic_compatible', label: 'Anthropic Compatible' },
|
||||
{ value: 'ollama_compatible', label: 'Ollama Compatible' },
|
||||
]}
|
||||
value={{
|
||||
value: engine,
|
||||
label:
|
||||
engine === 'openai_compatible'
|
||||
? 'OpenAI Compatible'
|
||||
: engine === 'anthropic_compatible'
|
||||
? 'Anthropic Compatible'
|
||||
: 'Ollama Compatible',
|
||||
}}
|
||||
onChange={(option: unknown) => {
|
||||
const selectedOption = option as { value: string; label: string } | null;
|
||||
if (selectedOption) setEngine(selectedOption.value);
|
||||
}}
|
||||
isSearchable={false}
|
||||
/>
|
||||
{validationErrors.providerType && (
|
||||
<p id="provider-select-error" className="text-red-500 text-sm mt-1">
|
||||
{validationErrors.providerType}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="display-name"
|
||||
className="flex items-center text-sm font-medium text-textStandard mb-2"
|
||||
>
|
||||
Display Name
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
</label>
|
||||
<Input
|
||||
id="display-name"
|
||||
value={displayName}
|
||||
onChange={(e) => setDisplayName(e.target.value)}
|
||||
placeholder="Your Provider Name"
|
||||
aria-invalid={!!validationErrors.displayName}
|
||||
aria-describedby={validationErrors.displayName ? 'display-name-error' : undefined}
|
||||
className={validationErrors.displayName ? 'border-red-500' : ''}
|
||||
/>
|
||||
{validationErrors.displayName && (
|
||||
<p id="display-name-error" className="text-red-500 text-sm mt-1">
|
||||
{validationErrors.displayName}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="api-url"
|
||||
className="flex items-center text-sm font-medium text-textStandard mb-2"
|
||||
>
|
||||
API URL
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
</label>
|
||||
<Input
|
||||
id="api-url"
|
||||
value={apiUrl}
|
||||
onChange={(e) => setApiUrl(e.target.value)}
|
||||
placeholder="https://api.example.com/v1/messages"
|
||||
aria-invalid={!!validationErrors.apiUrl}
|
||||
aria-describedby={validationErrors.apiUrl ? 'api-url-error' : undefined}
|
||||
className={validationErrors.apiUrl ? 'border-red-500' : ''}
|
||||
/>
|
||||
{validationErrors.apiUrl && (
|
||||
<p id="api-url-error" className="text-red-500 text-sm mt-1">
|
||||
{validationErrors.apiUrl}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label
|
||||
@@ -160,14 +179,14 @@ export default function CustomProviderForm({ onSubmit, onCancel }: CustomProvide
|
||||
className="flex items-center text-sm font-medium text-textStandard mb-2"
|
||||
>
|
||||
API Key
|
||||
{!isLocalModel && <span className="text-red-500 ml-1">*</span>}
|
||||
{!isLocalModel && !initialData && <span className="text-red-500 ml-1">*</span>}
|
||||
</label>
|
||||
<Input
|
||||
id="api-key"
|
||||
type="password"
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
placeholder="Your API key"
|
||||
placeholder={initialData ? 'Leave blank to keep existing key' : 'Your API key'}
|
||||
aria-invalid={!!validationErrors.apiKey}
|
||||
aria-describedby={validationErrors.apiKey ? 'api-key-error' : undefined}
|
||||
className={validationErrors.apiKey ? 'border-red-500' : ''}
|
||||
@@ -179,62 +198,64 @@ export default function CustomProviderForm({ onSubmit, onCancel }: CustomProvide
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex items-center space-x-2 mt-2">
|
||||
<Checkbox id="local-model" checked={isLocalModel} onCheckedChange={handleLocalModels} />
|
||||
<label
|
||||
htmlFor="local-model"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-textSubtle"
|
||||
>
|
||||
This is a local model (no auth required)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="available-models"
|
||||
className="flex items-center text-sm font-medium text-textStandard mb-2"
|
||||
>
|
||||
Available Models (comma-separated)
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
</label>
|
||||
<Input
|
||||
id="available-models"
|
||||
value={models}
|
||||
onChange={(e) => setModels(e.target.value)}
|
||||
placeholder="model-a, model-b, model-c"
|
||||
aria-invalid={!!validationErrors.models}
|
||||
aria-describedby={validationErrors.models ? 'available-models-error' : undefined}
|
||||
className={validationErrors.models ? 'border-red-500' : ''}
|
||||
/>
|
||||
{validationErrors.models && (
|
||||
<p id="available-models-error" className="text-red-500 text-sm mt-1">
|
||||
{validationErrors.models}
|
||||
</p>
|
||||
{!initialData && (
|
||||
<div className="flex items-center space-x-2 mt-2">
|
||||
<Checkbox id="local-model" checked={isLocalModel} onCheckedChange={handleLocalModels} />
|
||||
<label
|
||||
htmlFor="local-model"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-textSubtle"
|
||||
>
|
||||
This is a local model (no auth required)
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2 mb-10">
|
||||
<Checkbox
|
||||
id="supports-streaming"
|
||||
checked={supportsStreaming}
|
||||
onCheckedChange={(checked) => setSupportsStreaming(checked as boolean)}
|
||||
/>
|
||||
<label
|
||||
htmlFor="supports-streaming"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-textSubtle"
|
||||
>
|
||||
Provider supports streaming responses
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{isEditable && (
|
||||
<>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="available-models"
|
||||
className="flex items-center text-sm font-medium text-textStandard mb-2"
|
||||
>
|
||||
Available Models (comma-separated)
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
</label>
|
||||
<Input
|
||||
id="available-models"
|
||||
value={models}
|
||||
onChange={(e) => setModels(e.target.value)}
|
||||
placeholder="model-a, model-b, model-c"
|
||||
aria-invalid={!!validationErrors.models}
|
||||
aria-describedby={validationErrors.models ? 'available-models-error' : undefined}
|
||||
className={validationErrors.models ? 'border-red-500' : ''}
|
||||
/>
|
||||
{validationErrors.models && (
|
||||
<p id="available-models-error" className="text-red-500 text-sm mt-1">
|
||||
{validationErrors.models}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 mb-10">
|
||||
<Checkbox
|
||||
id="supports-streaming"
|
||||
checked={supportsStreaming}
|
||||
onCheckedChange={(checked) => setSupportsStreaming(checked as boolean)}
|
||||
/>
|
||||
<label
|
||||
htmlFor="supports-streaming"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-textSubtle"
|
||||
>
|
||||
Provider supports streaming responses
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<SecureStorageNotice />
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-4">
|
||||
<Button type="button" variant="outline" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">Create Provider</Button>
|
||||
<Button type="submit">{initialData ? 'Update Provider' : 'Create Provider'}</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user