feat(platform): New LLM Picker UI (#11726)
Add new LLM Picker for the new Builder. ### Changes 🏗️ - Enrich `LlmModelMeta` (in `llm.py`) with human readable model, creator and provider names and price tier (note: this is temporary measure and all LlmModelMeta will be removed completely once LLM Registry is ready) - Add provider icons - Add custom input field `LlmModelField` and its components&helpers ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] LLM model picker works correctly in the new Builder - [x] Legacy LLM model picker works in the old Builder
@@ -79,6 +79,10 @@ class ModelMetadata(NamedTuple):
|
||||
provider: str
|
||||
context_window: int
|
||||
max_output_tokens: int | None
|
||||
display_name: str
|
||||
provider_name: str
|
||||
creator_name: str
|
||||
price_tier: Literal[1, 2, 3]
|
||||
|
||||
|
||||
class LlmModelMeta(EnumMeta):
|
||||
@@ -171,6 +175,26 @@ class LlmModel(str, Enum, metaclass=LlmModelMeta):
|
||||
V0_1_5_LG = "v0-1.5-lg"
|
||||
V0_1_0_MD = "v0-1.0-md"
|
||||
|
||||
@classmethod
|
||||
def __get_pydantic_json_schema__(cls, schema, handler):
|
||||
json_schema = handler(schema)
|
||||
llm_model_metadata = {}
|
||||
for model in cls:
|
||||
model_name = model.value
|
||||
metadata = model.metadata
|
||||
llm_model_metadata[model_name] = {
|
||||
"creator": metadata.creator_name,
|
||||
"creator_name": metadata.creator_name,
|
||||
"title": metadata.display_name,
|
||||
"provider": metadata.provider,
|
||||
"provider_name": metadata.provider_name,
|
||||
"name": model_name,
|
||||
"price_tier": metadata.price_tier,
|
||||
}
|
||||
json_schema["llm_model"] = True
|
||||
json_schema["llm_model_metadata"] = llm_model_metadata
|
||||
return json_schema
|
||||
|
||||
@property
|
||||
def metadata(self) -> ModelMetadata:
|
||||
return MODEL_METADATA[self]
|
||||
@@ -190,119 +214,291 @@ class LlmModel(str, Enum, metaclass=LlmModelMeta):
|
||||
|
||||
MODEL_METADATA = {
|
||||
# https://platform.openai.com/docs/models
|
||||
LlmModel.O3: ModelMetadata("openai", 200000, 100000),
|
||||
LlmModel.O3_MINI: ModelMetadata("openai", 200000, 100000), # o3-mini-2025-01-31
|
||||
LlmModel.O1: ModelMetadata("openai", 200000, 100000), # o1-2024-12-17
|
||||
LlmModel.O1_MINI: ModelMetadata("openai", 128000, 65536), # o1-mini-2024-09-12
|
||||
LlmModel.O3: ModelMetadata("openai", 200000, 100000, "O3", "OpenAI", "OpenAI", 2),
|
||||
LlmModel.O3_MINI: ModelMetadata(
|
||||
"openai", 200000, 100000, "O3 Mini", "OpenAI", "OpenAI", 1
|
||||
), # o3-mini-2025-01-31
|
||||
LlmModel.O1: ModelMetadata(
|
||||
"openai", 200000, 100000, "O1", "OpenAI", "OpenAI", 3
|
||||
), # o1-2024-12-17
|
||||
LlmModel.O1_MINI: ModelMetadata(
|
||||
"openai", 128000, 65536, "O1 Mini", "OpenAI", "OpenAI", 2
|
||||
), # o1-mini-2024-09-12
|
||||
# GPT-5 models
|
||||
LlmModel.GPT5_2: ModelMetadata("openai", 400000, 128000),
|
||||
LlmModel.GPT5_1: ModelMetadata("openai", 400000, 128000),
|
||||
LlmModel.GPT5: ModelMetadata("openai", 400000, 128000),
|
||||
LlmModel.GPT5_MINI: ModelMetadata("openai", 400000, 128000),
|
||||
LlmModel.GPT5_NANO: ModelMetadata("openai", 400000, 128000),
|
||||
LlmModel.GPT5_CHAT: ModelMetadata("openai", 400000, 16384),
|
||||
LlmModel.GPT41: ModelMetadata("openai", 1047576, 32768),
|
||||
LlmModel.GPT41_MINI: ModelMetadata("openai", 1047576, 32768),
|
||||
LlmModel.GPT5_2: ModelMetadata(
|
||||
"openai", 400000, 128000, "GPT-5.2", "OpenAI", "OpenAI", 3
|
||||
),
|
||||
LlmModel.GPT5_1: ModelMetadata(
|
||||
"openai", 400000, 128000, "GPT-5.1", "OpenAI", "OpenAI", 2
|
||||
),
|
||||
LlmModel.GPT5: ModelMetadata(
|
||||
"openai", 400000, 128000, "GPT-5", "OpenAI", "OpenAI", 1
|
||||
),
|
||||
LlmModel.GPT5_MINI: ModelMetadata(
|
||||
"openai", 400000, 128000, "GPT-5 Mini", "OpenAI", "OpenAI", 1
|
||||
),
|
||||
LlmModel.GPT5_NANO: ModelMetadata(
|
||||
"openai", 400000, 128000, "GPT-5 Nano", "OpenAI", "OpenAI", 1
|
||||
),
|
||||
LlmModel.GPT5_CHAT: ModelMetadata(
|
||||
"openai", 400000, 16384, "GPT-5 Chat Latest", "OpenAI", "OpenAI", 2
|
||||
),
|
||||
LlmModel.GPT41: ModelMetadata(
|
||||
"openai", 1047576, 32768, "GPT-4.1", "OpenAI", "OpenAI", 1
|
||||
),
|
||||
LlmModel.GPT41_MINI: ModelMetadata(
|
||||
"openai", 1047576, 32768, "GPT-4.1 Mini", "OpenAI", "OpenAI", 1
|
||||
),
|
||||
LlmModel.GPT4O_MINI: ModelMetadata(
|
||||
"openai", 128000, 16384
|
||||
"openai", 128000, 16384, "GPT-4o Mini", "OpenAI", "OpenAI", 1
|
||||
), # gpt-4o-mini-2024-07-18
|
||||
LlmModel.GPT4O: ModelMetadata("openai", 128000, 16384), # gpt-4o-2024-08-06
|
||||
LlmModel.GPT4O: ModelMetadata(
|
||||
"openai", 128000, 16384, "GPT-4o", "OpenAI", "OpenAI", 2
|
||||
), # gpt-4o-2024-08-06
|
||||
LlmModel.GPT4_TURBO: ModelMetadata(
|
||||
"openai", 128000, 4096
|
||||
"openai", 128000, 4096, "GPT-4 Turbo", "OpenAI", "OpenAI", 3
|
||||
), # gpt-4-turbo-2024-04-09
|
||||
LlmModel.GPT3_5_TURBO: ModelMetadata("openai", 16385, 4096), # gpt-3.5-turbo-0125
|
||||
LlmModel.GPT3_5_TURBO: ModelMetadata(
|
||||
"openai", 16385, 4096, "GPT-3.5 Turbo", "OpenAI", "OpenAI", 1
|
||||
), # gpt-3.5-turbo-0125
|
||||
# https://docs.anthropic.com/en/docs/about-claude/models
|
||||
LlmModel.CLAUDE_4_1_OPUS: ModelMetadata(
|
||||
"anthropic", 200000, 32000
|
||||
"anthropic", 200000, 32000, "Claude Opus 4.1", "Anthropic", "Anthropic", 3
|
||||
), # claude-opus-4-1-20250805
|
||||
LlmModel.CLAUDE_4_OPUS: ModelMetadata(
|
||||
"anthropic", 200000, 32000
|
||||
"anthropic", 200000, 32000, "Claude Opus 4", "Anthropic", "Anthropic", 3
|
||||
), # claude-4-opus-20250514
|
||||
LlmModel.CLAUDE_4_SONNET: ModelMetadata(
|
||||
"anthropic", 200000, 64000
|
||||
"anthropic", 200000, 64000, "Claude Sonnet 4", "Anthropic", "Anthropic", 2
|
||||
), # claude-4-sonnet-20250514
|
||||
LlmModel.CLAUDE_4_5_OPUS: ModelMetadata(
|
||||
"anthropic", 200000, 64000
|
||||
"anthropic", 200000, 64000, "Claude Opus 4.5", "Anthropic", "Anthropic", 3
|
||||
), # claude-opus-4-5-20251101
|
||||
LlmModel.CLAUDE_4_5_SONNET: ModelMetadata(
|
||||
"anthropic", 200000, 64000
|
||||
"anthropic", 200000, 64000, "Claude Sonnet 4.5", "Anthropic", "Anthropic", 3
|
||||
), # claude-sonnet-4-5-20250929
|
||||
LlmModel.CLAUDE_4_5_HAIKU: ModelMetadata(
|
||||
"anthropic", 200000, 64000
|
||||
"anthropic", 200000, 64000, "Claude Haiku 4.5", "Anthropic", "Anthropic", 2
|
||||
), # claude-haiku-4-5-20251001
|
||||
LlmModel.CLAUDE_3_7_SONNET: ModelMetadata(
|
||||
"anthropic", 200000, 64000
|
||||
"anthropic", 200000, 64000, "Claude 3.7 Sonnet", "Anthropic", "Anthropic", 2
|
||||
), # claude-3-7-sonnet-20250219
|
||||
LlmModel.CLAUDE_3_HAIKU: ModelMetadata(
|
||||
"anthropic", 200000, 4096
|
||||
"anthropic", 200000, 4096, "Claude 3 Haiku", "Anthropic", "Anthropic", 1
|
||||
), # claude-3-haiku-20240307
|
||||
# https://docs.aimlapi.com/api-overview/model-database/text-models
|
||||
LlmModel.AIML_API_QWEN2_5_72B: ModelMetadata("aiml_api", 32000, 8000),
|
||||
LlmModel.AIML_API_LLAMA3_1_70B: ModelMetadata("aiml_api", 128000, 40000),
|
||||
LlmModel.AIML_API_LLAMA3_3_70B: ModelMetadata("aiml_api", 128000, None),
|
||||
LlmModel.AIML_API_META_LLAMA_3_1_70B: ModelMetadata("aiml_api", 131000, 2000),
|
||||
LlmModel.AIML_API_LLAMA_3_2_3B: ModelMetadata("aiml_api", 128000, None),
|
||||
# https://console.groq.com/docs/models
|
||||
LlmModel.LLAMA3_3_70B: ModelMetadata("groq", 128000, 32768),
|
||||
LlmModel.LLAMA3_1_8B: ModelMetadata("groq", 128000, 8192),
|
||||
# https://ollama.com/library
|
||||
LlmModel.OLLAMA_LLAMA3_3: ModelMetadata("ollama", 8192, None),
|
||||
LlmModel.OLLAMA_LLAMA3_2: ModelMetadata("ollama", 8192, None),
|
||||
LlmModel.OLLAMA_LLAMA3_8B: ModelMetadata("ollama", 8192, None),
|
||||
LlmModel.OLLAMA_LLAMA3_405B: ModelMetadata("ollama", 8192, None),
|
||||
LlmModel.OLLAMA_DOLPHIN: ModelMetadata("ollama", 32768, None),
|
||||
# https://openrouter.ai/models
|
||||
LlmModel.GEMINI_2_5_PRO: ModelMetadata("open_router", 1050000, 8192),
|
||||
LlmModel.GEMINI_3_PRO_PREVIEW: ModelMetadata("open_router", 1048576, 65535),
|
||||
LlmModel.GEMINI_2_5_FLASH: ModelMetadata("open_router", 1048576, 65535),
|
||||
LlmModel.GEMINI_2_0_FLASH: ModelMetadata("open_router", 1048576, 8192),
|
||||
LlmModel.GEMINI_2_5_FLASH_LITE_PREVIEW: ModelMetadata(
|
||||
"open_router", 1048576, 65535
|
||||
LlmModel.AIML_API_QWEN2_5_72B: ModelMetadata(
|
||||
"aiml_api", 32000, 8000, "Qwen 2.5 72B Instruct Turbo", "AI/ML", "Qwen", 1
|
||||
),
|
||||
LlmModel.AIML_API_LLAMA3_1_70B: ModelMetadata(
|
||||
"aiml_api",
|
||||
128000,
|
||||
40000,
|
||||
"Llama 3.1 Nemotron 70B Instruct",
|
||||
"AI/ML",
|
||||
"Nvidia",
|
||||
1,
|
||||
),
|
||||
LlmModel.AIML_API_LLAMA3_3_70B: ModelMetadata(
|
||||
"aiml_api", 128000, None, "Llama 3.3 70B Instruct Turbo", "AI/ML", "Meta", 1
|
||||
),
|
||||
LlmModel.AIML_API_META_LLAMA_3_1_70B: ModelMetadata(
|
||||
"aiml_api", 131000, 2000, "Llama 3.1 70B Instruct Turbo", "AI/ML", "Meta", 1
|
||||
),
|
||||
LlmModel.AIML_API_LLAMA_3_2_3B: ModelMetadata(
|
||||
"aiml_api", 128000, None, "Llama 3.2 3B Instruct Turbo", "AI/ML", "Meta", 1
|
||||
),
|
||||
# https://console.groq.com/docs/models
|
||||
LlmModel.LLAMA3_3_70B: ModelMetadata(
|
||||
"groq", 128000, 32768, "Llama 3.3 70B Versatile", "Groq", "Meta", 1
|
||||
),
|
||||
LlmModel.LLAMA3_1_8B: ModelMetadata(
|
||||
"groq", 128000, 8192, "Llama 3.1 8B Instant", "Groq", "Meta", 1
|
||||
),
|
||||
# https://ollama.com/library
|
||||
LlmModel.OLLAMA_LLAMA3_3: ModelMetadata(
|
||||
"ollama", 8192, None, "Llama 3.3", "Ollama", "Meta", 1
|
||||
),
|
||||
LlmModel.OLLAMA_LLAMA3_2: ModelMetadata(
|
||||
"ollama", 8192, None, "Llama 3.2", "Ollama", "Meta", 1
|
||||
),
|
||||
LlmModel.OLLAMA_LLAMA3_8B: ModelMetadata(
|
||||
"ollama", 8192, None, "Llama 3", "Ollama", "Meta", 1
|
||||
),
|
||||
LlmModel.OLLAMA_LLAMA3_405B: ModelMetadata(
|
||||
"ollama", 8192, None, "Llama 3.1 405B", "Ollama", "Meta", 1
|
||||
),
|
||||
LlmModel.OLLAMA_DOLPHIN: ModelMetadata(
|
||||
"ollama", 32768, None, "Dolphin Mistral Latest", "Ollama", "Mistral AI", 1
|
||||
),
|
||||
# https://openrouter.ai/models
|
||||
LlmModel.GEMINI_2_5_PRO: ModelMetadata(
|
||||
"open_router",
|
||||
1050000,
|
||||
8192,
|
||||
"Gemini 2.5 Pro Preview 03.25",
|
||||
"OpenRouter",
|
||||
"Google",
|
||||
2,
|
||||
),
|
||||
LlmModel.GEMINI_3_PRO_PREVIEW: ModelMetadata(
|
||||
"open_router", 1048576, 65535, "Gemini 3 Pro Preview", "OpenRouter", "Google", 2
|
||||
),
|
||||
LlmModel.GEMINI_2_5_FLASH: ModelMetadata(
|
||||
"open_router", 1048576, 65535, "Gemini 2.5 Flash", "OpenRouter", "Google", 1
|
||||
),
|
||||
LlmModel.GEMINI_2_0_FLASH: ModelMetadata(
|
||||
"open_router", 1048576, 8192, "Gemini 2.0 Flash 001", "OpenRouter", "Google", 1
|
||||
),
|
||||
LlmModel.GEMINI_2_5_FLASH_LITE_PREVIEW: ModelMetadata(
|
||||
"open_router",
|
||||
1048576,
|
||||
65535,
|
||||
"Gemini 2.5 Flash Lite Preview 06.17",
|
||||
"OpenRouter",
|
||||
"Google",
|
||||
1,
|
||||
),
|
||||
LlmModel.GEMINI_2_0_FLASH_LITE: ModelMetadata(
|
||||
"open_router",
|
||||
1048576,
|
||||
8192,
|
||||
"Gemini 2.0 Flash Lite 001",
|
||||
"OpenRouter",
|
||||
"Google",
|
||||
1,
|
||||
),
|
||||
LlmModel.MISTRAL_NEMO: ModelMetadata(
|
||||
"open_router", 128000, 4096, "Mistral Nemo", "OpenRouter", "Mistral AI", 1
|
||||
),
|
||||
LlmModel.COHERE_COMMAND_R_08_2024: ModelMetadata(
|
||||
"open_router", 128000, 4096, "Command R 08.2024", "OpenRouter", "Cohere", 1
|
||||
),
|
||||
LlmModel.COHERE_COMMAND_R_PLUS_08_2024: ModelMetadata(
|
||||
"open_router", 128000, 4096, "Command R Plus 08.2024", "OpenRouter", "Cohere", 2
|
||||
),
|
||||
LlmModel.DEEPSEEK_CHAT: ModelMetadata(
|
||||
"open_router", 64000, 2048, "DeepSeek Chat", "OpenRouter", "DeepSeek", 1
|
||||
),
|
||||
LlmModel.DEEPSEEK_R1_0528: ModelMetadata(
|
||||
"open_router", 163840, 163840, "DeepSeek R1 0528", "OpenRouter", "DeepSeek", 1
|
||||
),
|
||||
LlmModel.PERPLEXITY_SONAR: ModelMetadata(
|
||||
"open_router", 127000, 8000, "Sonar", "OpenRouter", "Perplexity", 1
|
||||
),
|
||||
LlmModel.PERPLEXITY_SONAR_PRO: ModelMetadata(
|
||||
"open_router", 200000, 8000, "Sonar Pro", "OpenRouter", "Perplexity", 2
|
||||
),
|
||||
LlmModel.GEMINI_2_0_FLASH_LITE: ModelMetadata("open_router", 1048576, 8192),
|
||||
LlmModel.MISTRAL_NEMO: ModelMetadata("open_router", 128000, 4096),
|
||||
LlmModel.COHERE_COMMAND_R_08_2024: ModelMetadata("open_router", 128000, 4096),
|
||||
LlmModel.COHERE_COMMAND_R_PLUS_08_2024: ModelMetadata("open_router", 128000, 4096),
|
||||
LlmModel.DEEPSEEK_CHAT: ModelMetadata("open_router", 64000, 2048),
|
||||
LlmModel.DEEPSEEK_R1_0528: ModelMetadata("open_router", 163840, 163840),
|
||||
LlmModel.PERPLEXITY_SONAR: ModelMetadata("open_router", 127000, 8000),
|
||||
LlmModel.PERPLEXITY_SONAR_PRO: ModelMetadata("open_router", 200000, 8000),
|
||||
LlmModel.PERPLEXITY_SONAR_DEEP_RESEARCH: ModelMetadata(
|
||||
"open_router",
|
||||
128000,
|
||||
16000,
|
||||
"Sonar Deep Research",
|
||||
"OpenRouter",
|
||||
"Perplexity",
|
||||
3,
|
||||
),
|
||||
LlmModel.NOUSRESEARCH_HERMES_3_LLAMA_3_1_405B: ModelMetadata(
|
||||
"open_router", 131000, 4096
|
||||
"open_router",
|
||||
131000,
|
||||
4096,
|
||||
"Hermes 3 Llama 3.1 405B",
|
||||
"OpenRouter",
|
||||
"Nous Research",
|
||||
1,
|
||||
),
|
||||
LlmModel.NOUSRESEARCH_HERMES_3_LLAMA_3_1_70B: ModelMetadata(
|
||||
"open_router", 12288, 12288
|
||||
"open_router",
|
||||
12288,
|
||||
12288,
|
||||
"Hermes 3 Llama 3.1 70B",
|
||||
"OpenRouter",
|
||||
"Nous Research",
|
||||
1,
|
||||
),
|
||||
LlmModel.OPENAI_GPT_OSS_120B: ModelMetadata(
|
||||
"open_router", 131072, 131072, "GPT-OSS 120B", "OpenRouter", "OpenAI", 1
|
||||
),
|
||||
LlmModel.OPENAI_GPT_OSS_20B: ModelMetadata(
|
||||
"open_router", 131072, 32768, "GPT-OSS 20B", "OpenRouter", "OpenAI", 1
|
||||
),
|
||||
LlmModel.AMAZON_NOVA_LITE_V1: ModelMetadata(
|
||||
"open_router", 300000, 5120, "Nova Lite V1", "OpenRouter", "Amazon", 1
|
||||
),
|
||||
LlmModel.AMAZON_NOVA_MICRO_V1: ModelMetadata(
|
||||
"open_router", 128000, 5120, "Nova Micro V1", "OpenRouter", "Amazon", 1
|
||||
),
|
||||
LlmModel.AMAZON_NOVA_PRO_V1: ModelMetadata(
|
||||
"open_router", 300000, 5120, "Nova Pro V1", "OpenRouter", "Amazon", 1
|
||||
),
|
||||
LlmModel.MICROSOFT_WIZARDLM_2_8X22B: ModelMetadata(
|
||||
"open_router", 65536, 4096, "WizardLM 2 8x22B", "OpenRouter", "Microsoft", 1
|
||||
),
|
||||
LlmModel.GRYPHE_MYTHOMAX_L2_13B: ModelMetadata(
|
||||
"open_router", 4096, 4096, "MythoMax L2 13B", "OpenRouter", "Gryphe", 1
|
||||
),
|
||||
LlmModel.META_LLAMA_4_SCOUT: ModelMetadata(
|
||||
"open_router", 131072, 131072, "Llama 4 Scout", "OpenRouter", "Meta", 1
|
||||
),
|
||||
LlmModel.META_LLAMA_4_MAVERICK: ModelMetadata(
|
||||
"open_router", 1048576, 1000000, "Llama 4 Maverick", "OpenRouter", "Meta", 1
|
||||
),
|
||||
LlmModel.GROK_4: ModelMetadata(
|
||||
"open_router", 256000, 256000, "Grok 4", "OpenRouter", "xAI", 3
|
||||
),
|
||||
LlmModel.GROK_4_FAST: ModelMetadata(
|
||||
"open_router", 2000000, 30000, "Grok 4 Fast", "OpenRouter", "xAI", 1
|
||||
),
|
||||
LlmModel.GROK_4_1_FAST: ModelMetadata(
|
||||
"open_router", 2000000, 30000, "Grok 4.1 Fast", "OpenRouter", "xAI", 1
|
||||
),
|
||||
LlmModel.GROK_CODE_FAST_1: ModelMetadata(
|
||||
"open_router", 256000, 10000, "Grok Code Fast 1", "OpenRouter", "xAI", 1
|
||||
),
|
||||
LlmModel.KIMI_K2: ModelMetadata(
|
||||
"open_router", 131000, 131000, "Kimi K2", "OpenRouter", "Moonshot AI", 1
|
||||
),
|
||||
LlmModel.QWEN3_235B_A22B_THINKING: ModelMetadata(
|
||||
"open_router",
|
||||
262144,
|
||||
262144,
|
||||
"Qwen 3 235B A22B Thinking 2507",
|
||||
"OpenRouter",
|
||||
"Qwen",
|
||||
1,
|
||||
),
|
||||
LlmModel.QWEN3_CODER: ModelMetadata(
|
||||
"open_router", 262144, 262144, "Qwen 3 Coder", "OpenRouter", "Qwen", 3
|
||||
),
|
||||
LlmModel.OPENAI_GPT_OSS_120B: ModelMetadata("open_router", 131072, 131072),
|
||||
LlmModel.OPENAI_GPT_OSS_20B: ModelMetadata("open_router", 131072, 32768),
|
||||
LlmModel.AMAZON_NOVA_LITE_V1: ModelMetadata("open_router", 300000, 5120),
|
||||
LlmModel.AMAZON_NOVA_MICRO_V1: ModelMetadata("open_router", 128000, 5120),
|
||||
LlmModel.AMAZON_NOVA_PRO_V1: ModelMetadata("open_router", 300000, 5120),
|
||||
LlmModel.MICROSOFT_WIZARDLM_2_8X22B: ModelMetadata("open_router", 65536, 4096),
|
||||
LlmModel.GRYPHE_MYTHOMAX_L2_13B: ModelMetadata("open_router", 4096, 4096),
|
||||
LlmModel.META_LLAMA_4_SCOUT: ModelMetadata("open_router", 131072, 131072),
|
||||
LlmModel.META_LLAMA_4_MAVERICK: ModelMetadata("open_router", 1048576, 1000000),
|
||||
LlmModel.GROK_4: ModelMetadata("open_router", 256000, 256000),
|
||||
LlmModel.GROK_4_FAST: ModelMetadata("open_router", 2000000, 30000),
|
||||
LlmModel.GROK_4_1_FAST: ModelMetadata("open_router", 2000000, 30000),
|
||||
LlmModel.GROK_CODE_FAST_1: ModelMetadata("open_router", 256000, 10000),
|
||||
LlmModel.KIMI_K2: ModelMetadata("open_router", 131000, 131000),
|
||||
LlmModel.QWEN3_235B_A22B_THINKING: ModelMetadata("open_router", 262144, 262144),
|
||||
LlmModel.QWEN3_CODER: ModelMetadata("open_router", 262144, 262144),
|
||||
# Llama API models
|
||||
LlmModel.LLAMA_API_LLAMA_4_SCOUT: ModelMetadata("llama_api", 128000, 4028),
|
||||
LlmModel.LLAMA_API_LLAMA4_MAVERICK: ModelMetadata("llama_api", 128000, 4028),
|
||||
LlmModel.LLAMA_API_LLAMA3_3_8B: ModelMetadata("llama_api", 128000, 4028),
|
||||
LlmModel.LLAMA_API_LLAMA3_3_70B: ModelMetadata("llama_api", 128000, 4028),
|
||||
LlmModel.LLAMA_API_LLAMA_4_SCOUT: ModelMetadata(
|
||||
"llama_api",
|
||||
128000,
|
||||
4028,
|
||||
"Llama 4 Scout 17B 16E Instruct FP8",
|
||||
"Llama API",
|
||||
"Meta",
|
||||
1,
|
||||
),
|
||||
LlmModel.LLAMA_API_LLAMA4_MAVERICK: ModelMetadata(
|
||||
"llama_api",
|
||||
128000,
|
||||
4028,
|
||||
"Llama 4 Maverick 17B 128E Instruct FP8",
|
||||
"Llama API",
|
||||
"Meta",
|
||||
1,
|
||||
),
|
||||
LlmModel.LLAMA_API_LLAMA3_3_8B: ModelMetadata(
|
||||
"llama_api", 128000, 4028, "Llama 3.3 8B Instruct", "Llama API", "Meta", 1
|
||||
),
|
||||
LlmModel.LLAMA_API_LLAMA3_3_70B: ModelMetadata(
|
||||
"llama_api", 128000, 4028, "Llama 3.3 70B Instruct", "Llama API", "Meta", 1
|
||||
),
|
||||
# v0 by Vercel models
|
||||
LlmModel.V0_1_5_MD: ModelMetadata("v0", 128000, 64000),
|
||||
LlmModel.V0_1_5_LG: ModelMetadata("v0", 512000, 64000),
|
||||
LlmModel.V0_1_0_MD: ModelMetadata("v0", 128000, 64000),
|
||||
LlmModel.V0_1_5_MD: ModelMetadata("v0", 128000, 64000, "v0 1.5 MD", "V0", "V0", 1),
|
||||
LlmModel.V0_1_5_LG: ModelMetadata("v0", 512000, 64000, "v0 1.5 LG", "V0", "V0", 1),
|
||||
LlmModel.V0_1_0_MD: ModelMetadata("v0", 128000, 64000, "v0 1.0 MD", "V0", "V0", 1),
|
||||
}
|
||||
|
||||
DEFAULT_LLM_MODEL = LlmModel.GPT5_2
|
||||
|
||||
@@ -99,10 +99,15 @@ MODEL_COST: dict[LlmModel, int] = {
|
||||
LlmModel.OPENAI_GPT_OSS_20B: 1,
|
||||
LlmModel.GEMINI_2_5_PRO: 4,
|
||||
LlmModel.GEMINI_3_PRO_PREVIEW: 5,
|
||||
LlmModel.GEMINI_2_5_FLASH: 1,
|
||||
LlmModel.GEMINI_2_0_FLASH: 1,
|
||||
LlmModel.GEMINI_2_5_FLASH_LITE_PREVIEW: 1,
|
||||
LlmModel.GEMINI_2_0_FLASH_LITE: 1,
|
||||
LlmModel.MISTRAL_NEMO: 1,
|
||||
LlmModel.COHERE_COMMAND_R_08_2024: 1,
|
||||
LlmModel.COHERE_COMMAND_R_PLUS_08_2024: 3,
|
||||
LlmModel.DEEPSEEK_CHAT: 2,
|
||||
LlmModel.DEEPSEEK_R1_0528: 1,
|
||||
LlmModel.PERPLEXITY_SONAR: 1,
|
||||
LlmModel.PERPLEXITY_SONAR_PRO: 5,
|
||||
LlmModel.PERPLEXITY_SONAR_DEEP_RESEARCH: 10,
|
||||
@@ -126,11 +131,6 @@ MODEL_COST: dict[LlmModel, int] = {
|
||||
LlmModel.KIMI_K2: 1,
|
||||
LlmModel.QWEN3_235B_A22B_THINKING: 1,
|
||||
LlmModel.QWEN3_CODER: 9,
|
||||
LlmModel.GEMINI_2_5_FLASH: 1,
|
||||
LlmModel.GEMINI_2_0_FLASH: 1,
|
||||
LlmModel.GEMINI_2_5_FLASH_LITE_PREVIEW: 1,
|
||||
LlmModel.GEMINI_2_0_FLASH_LITE: 1,
|
||||
LlmModel.DEEPSEEK_R1_0528: 1,
|
||||
# v0 by Vercel models
|
||||
LlmModel.V0_1_5_MD: 1,
|
||||
LlmModel.V0_1_5_LG: 2,
|
||||
|
||||
BIN
autogpt_platform/frontend/public/integrations/amazon.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 19 KiB |
BIN
autogpt_platform/frontend/public/integrations/cohere.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
autogpt_platform/frontend/public/integrations/deepseek.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
autogpt_platform/frontend/public/integrations/gemini.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
autogpt_platform/frontend/public/integrations/gryphe.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
autogpt_platform/frontend/public/integrations/microsoft.webp
Normal file
|
After Width: | Height: | Size: 374 B |
BIN
autogpt_platform/frontend/public/integrations/mistral.png
Normal file
|
After Width: | Height: | Size: 663 B |
BIN
autogpt_platform/frontend/public/integrations/moonshot.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
autogpt_platform/frontend/public/integrations/nousresearch.avif
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
autogpt_platform/frontend/public/integrations/perplexity.webp
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
autogpt_platform/frontend/public/integrations/qwen.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
autogpt_platform/frontend/public/integrations/xai.webp
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Popover = PopoverPrimitive.Root;
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger;
|
||||
|
||||
const PopoverAnchor = PopoverPrimitive.Anchor;
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-lg border border-zinc-200 bg-white p-4 text-zinc-900 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
));
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
||||
|
||||
export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };
|
||||
@@ -0,0 +1,92 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
descriptionId,
|
||||
FieldProps,
|
||||
getTemplate,
|
||||
RJSFSchema,
|
||||
titleId,
|
||||
} from "@rjsf/utils";
|
||||
import { useMemo } from "react";
|
||||
import { LlmModelPicker } from "./components/LlmModelPicker";
|
||||
import { LlmModelMetadataMap } from "./types";
|
||||
import { updateUiOption } from "../../helpers";
|
||||
|
||||
type LlmModelSchema = RJSFSchema & {
|
||||
llm_model_metadata?: LlmModelMetadataMap;
|
||||
};
|
||||
|
||||
export function LlmModelField(props: FieldProps) {
|
||||
const { schema, formData, onChange, disabled, readonly, fieldPathId } = props;
|
||||
|
||||
const metadata = useMemo(() => {
|
||||
return (schema as LlmModelSchema)?.llm_model_metadata ?? {};
|
||||
}, [schema]);
|
||||
|
||||
const models = useMemo(() => {
|
||||
return Object.values(metadata);
|
||||
}, [metadata]);
|
||||
|
||||
const selectedName =
|
||||
typeof formData === "string"
|
||||
? formData
|
||||
: typeof schema.default === "string"
|
||||
? schema.default
|
||||
: "";
|
||||
|
||||
const selectedModel = selectedName
|
||||
? (metadata[selectedName] ??
|
||||
models.find((model) => model.name === selectedName))
|
||||
: undefined;
|
||||
|
||||
const recommendedName =
|
||||
typeof schema.default === "string" ? schema.default : models[0]?.name;
|
||||
|
||||
const recommendedModel =
|
||||
recommendedName && metadata[recommendedName]
|
||||
? metadata[recommendedName]
|
||||
: undefined;
|
||||
|
||||
if (models.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const TitleFieldTemplate = getTemplate("TitleFieldTemplate", props.registry);
|
||||
const DescriptionFieldTemplate = getTemplate(
|
||||
"DescriptionFieldTemplate",
|
||||
props.registry,
|
||||
);
|
||||
|
||||
const updatedUiSchema = updateUiOption(props.uiSchema, {
|
||||
showHandles: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<TitleFieldTemplate
|
||||
id={titleId(fieldPathId)}
|
||||
title={schema.title || ""}
|
||||
required={true}
|
||||
schema={schema}
|
||||
uiSchema={updatedUiSchema}
|
||||
registry={props.registry}
|
||||
/>
|
||||
<DescriptionFieldTemplate
|
||||
id={descriptionId(fieldPathId)}
|
||||
description={schema.description || ""}
|
||||
schema={schema}
|
||||
registry={props.registry}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<LlmModelPicker
|
||||
models={models}
|
||||
selectedModel={selectedModel}
|
||||
recommendedModel={recommendedModel}
|
||||
onSelect={(value) => onChange(value, fieldPathId?.path)}
|
||||
disabled={disabled || readonly}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
|
||||
const creatorIconMap: Record<string, string> = {
|
||||
anthropic: "/integrations/anthropic-color.png",
|
||||
openai: "/integrations/openai.png",
|
||||
google: "/integrations/gemini.png",
|
||||
nvidia: "/integrations/nvidia.png",
|
||||
groq: "/integrations/groq.png",
|
||||
ollama: "/integrations/ollama.png",
|
||||
openrouter: "/integrations/open_router.png",
|
||||
v0: "/integrations/v0.png",
|
||||
xai: "/integrations/xai.webp",
|
||||
meta: "/integrations/llama_api.png",
|
||||
amazon: "/integrations/amazon.png",
|
||||
cohere: "/integrations/cohere.png",
|
||||
deepseek: "/integrations/deepseek.png",
|
||||
gryphe: "/integrations/gryphe.png",
|
||||
microsoft: "/integrations/microsoft.webp",
|
||||
moonshotai: "/integrations/moonshot.png",
|
||||
mistral: "/integrations/mistral.png",
|
||||
mistralai: "/integrations/mistral.png",
|
||||
nousresearch: "/integrations/nousresearch.avif",
|
||||
perplexity: "/integrations/perplexity.webp",
|
||||
qwen: "/integrations/qwen.png",
|
||||
};
|
||||
|
||||
type Props = {
|
||||
value: string;
|
||||
size?: number;
|
||||
};
|
||||
|
||||
export function LlmIcon({ value, size = 20 }: Props) {
|
||||
const normalized = value.trim().toLowerCase().replace(/\s+/g, "");
|
||||
const src = creatorIconMap[normalized];
|
||||
if (src) {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-center overflow-hidden rounded-xsmall"
|
||||
style={{ width: size, height: size }}
|
||||
>
|
||||
<Image
|
||||
src={src}
|
||||
alt={value}
|
||||
width={size}
|
||||
height={size}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const fallback = value?.trim().slice(0, 1).toUpperCase() || "?";
|
||||
return (
|
||||
<div
|
||||
className="flex items-center justify-center rounded-xsmall bg-zinc-100"
|
||||
style={{ width: size, height: size }}
|
||||
>
|
||||
<Text variant="small" className="text-zinc-500">
|
||||
{fallback}
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
"use client";
|
||||
|
||||
import { ArrowLeftIcon } from "@phosphor-icons/react";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
onBack: () => void;
|
||||
};
|
||||
|
||||
export function LlmMenuHeader({ label, onBack }: Props) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onBack}
|
||||
className="flex w-full items-center gap-2 px-2 py-2 text-left hover:bg-zinc-100"
|
||||
>
|
||||
<ArrowLeftIcon className="h-4 w-4 text-zinc-800" weight="bold" />
|
||||
<Text variant="body" className="text-zinc-900">
|
||||
{label}
|
||||
</Text>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import { CaretRightIcon, CheckIcon } from "@phosphor-icons/react";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
icon?: React.ReactNode;
|
||||
showChevron?: boolean;
|
||||
rightSlot?: React.ReactNode;
|
||||
onClick: () => void;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
export function LlmMenuItem({
|
||||
title,
|
||||
subtitle,
|
||||
icon,
|
||||
showChevron,
|
||||
rightSlot,
|
||||
onClick,
|
||||
isActive,
|
||||
}: Props) {
|
||||
const hasIcon = Boolean(icon);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={cn("w-full py-1 pl-2 pr-4 text-left hover:bg-zinc-100")}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
{icon}
|
||||
<Text variant="body" className="text-zinc-900">
|
||||
{title}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{isActive && (
|
||||
<CheckIcon className="h-4 w-4 text-emerald-600" weight="bold" />
|
||||
)}
|
||||
{rightSlot}
|
||||
{showChevron && (
|
||||
<CaretRightIcon className="h-4 w-4 text-zinc-900" weight="bold" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{subtitle && (
|
||||
<Text
|
||||
variant="small"
|
||||
className={cn("mb-1 text-zinc-500", hasIcon && "pl-0")}
|
||||
>
|
||||
{subtitle}
|
||||
</Text>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { CaretDownIcon } from "@phosphor-icons/react";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/molecules/Popover/Popover";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
getCreatorDisplayName,
|
||||
getModelDisplayName,
|
||||
getProviderDisplayName,
|
||||
groupByCreator,
|
||||
groupByTitle,
|
||||
} from "../helpers";
|
||||
import { LlmModelMetadata } from "../types";
|
||||
import { LlmIcon } from "./LlmIcon";
|
||||
import { LlmMenuHeader } from "./LlmMenuHeader";
|
||||
import { LlmMenuItem } from "./LlmMenuItem";
|
||||
import { LlmPriceTier } from "./LlmPriceTier";
|
||||
|
||||
type MenuView = "creator" | "model" | "provider";
|
||||
|
||||
type Props = {
|
||||
models: LlmModelMetadata[];
|
||||
selectedModel?: LlmModelMetadata;
|
||||
recommendedModel?: LlmModelMetadata;
|
||||
onSelect: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export function LlmModelPicker({
|
||||
models,
|
||||
selectedModel,
|
||||
recommendedModel,
|
||||
onSelect,
|
||||
disabled,
|
||||
}: Props) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [view, setView] = useState<MenuView>("creator");
|
||||
const [activeCreator, setActiveCreator] = useState<string | null>(null);
|
||||
const [activeTitle, setActiveTitle] = useState<string | null>(null);
|
||||
|
||||
const modelsByCreator = useMemo(() => groupByCreator(models), [models]);
|
||||
|
||||
const creators = useMemo(() => {
|
||||
return Array.from(modelsByCreator.keys()).sort((a, b) =>
|
||||
a.localeCompare(b),
|
||||
);
|
||||
}, [modelsByCreator]);
|
||||
|
||||
const creatorIconValues = useMemo(() => {
|
||||
const map = new Map<string, string>();
|
||||
for (const [creator, entries] of modelsByCreator.entries()) {
|
||||
map.set(creator, entries[0]?.creator ?? creator);
|
||||
}
|
||||
return map;
|
||||
}, [modelsByCreator]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
setView("creator");
|
||||
setActiveCreator(
|
||||
selectedModel
|
||||
? getCreatorDisplayName(selectedModel)
|
||||
: (creators[0] ?? null),
|
||||
);
|
||||
setActiveTitle(selectedModel ? getModelDisplayName(selectedModel) : null);
|
||||
}, [open, selectedModel, creators]);
|
||||
|
||||
const currentCreator = activeCreator ?? creators[0] ?? null;
|
||||
|
||||
const currentModels = useMemo(() => {
|
||||
return currentCreator ? (modelsByCreator.get(currentCreator) ?? []) : [];
|
||||
}, [currentCreator, modelsByCreator]);
|
||||
|
||||
const currentCreatorIcon = useMemo(() => {
|
||||
return currentModels[0]?.creator ?? currentCreator;
|
||||
}, [currentModels, currentCreator]);
|
||||
|
||||
const modelsByTitle = useMemo(
|
||||
() => groupByTitle(currentModels),
|
||||
[currentModels],
|
||||
);
|
||||
|
||||
const modelEntries = useMemo(() => {
|
||||
return Array.from(modelsByTitle.entries())
|
||||
.map(([title, entries]) => {
|
||||
const providers = new Set(entries.map((entry) => entry.provider));
|
||||
return {
|
||||
title,
|
||||
entries,
|
||||
providerCount: providers.size,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.title.localeCompare(b.title));
|
||||
}, [modelsByTitle]);
|
||||
|
||||
const providerEntries = useMemo(() => {
|
||||
if (!activeTitle) {
|
||||
return [];
|
||||
}
|
||||
return modelsByTitle.get(activeTitle) ?? [];
|
||||
}, [activeTitle, modelsByTitle]);
|
||||
|
||||
const handleSelectModel = useCallback(
|
||||
(modelName: string) => {
|
||||
onSelect(modelName);
|
||||
setOpen(false);
|
||||
},
|
||||
[onSelect],
|
||||
);
|
||||
|
||||
const triggerModel = selectedModel ?? recommendedModel ?? models[0];
|
||||
const triggerTitle = triggerModel
|
||||
? getModelDisplayName(triggerModel)
|
||||
: "Select model";
|
||||
const triggerCreator = triggerModel?.creator ?? "";
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"flex w-full min-w-[15rem] items-center rounded-lg border border-zinc-200 bg-white px-3 py-2 text-left",
|
||||
"hover:border-zinc-300 focus:outline-none focus:ring-2 focus:ring-zinc-200",
|
||||
disabled && "cursor-not-allowed opacity-60",
|
||||
)}
|
||||
>
|
||||
<LlmIcon value={triggerCreator} />
|
||||
<Text variant="body" className="ml-1 flex-1 text-zinc-900">
|
||||
{triggerTitle}
|
||||
</Text>
|
||||
<CaretDownIcon className="h-3 w-3 text-zinc-900" weight="bold" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="start"
|
||||
sideOffset={4}
|
||||
className="max-h-[45vh] w-[--radix-popover-trigger-width] min-w-[16rem] overflow-y-auto rounded-md border border-zinc-200 bg-white p-0 shadow-[0px_1px_4px_rgba(12,12,13,0.12)]"
|
||||
>
|
||||
{view === "creator" && (
|
||||
<div className="flex flex-col">
|
||||
{recommendedModel && (
|
||||
<>
|
||||
<LlmMenuItem
|
||||
title={getModelDisplayName(recommendedModel)}
|
||||
subtitle="Recommended"
|
||||
icon={<LlmIcon value={recommendedModel.creator} />}
|
||||
onClick={() => handleSelectModel(recommendedModel.name)}
|
||||
/>
|
||||
<div className="border-b border-zinc-200" />
|
||||
</>
|
||||
)}
|
||||
{creators.map((creator) => (
|
||||
<LlmMenuItem
|
||||
key={creator}
|
||||
title={creator}
|
||||
icon={
|
||||
<LlmIcon value={creatorIconValues.get(creator) ?? creator} />
|
||||
}
|
||||
showChevron={true}
|
||||
isActive={
|
||||
selectedModel
|
||||
? getCreatorDisplayName(selectedModel) === creator
|
||||
: false
|
||||
}
|
||||
onClick={() => {
|
||||
setActiveCreator(creator);
|
||||
setView("model");
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{view === "model" && currentCreator && (
|
||||
<div className="flex flex-col">
|
||||
<LlmMenuHeader
|
||||
label={currentCreator}
|
||||
onBack={() => setView("creator")}
|
||||
/>
|
||||
<div className="border-b border-zinc-200" />
|
||||
{modelEntries.map((entry) => (
|
||||
<LlmMenuItem
|
||||
key={entry.title}
|
||||
title={entry.title}
|
||||
icon={<LlmIcon value={currentCreatorIcon} />}
|
||||
rightSlot={<LlmPriceTier tier={entry.entries[0]?.price_tier} />}
|
||||
showChevron={entry.providerCount > 1}
|
||||
isActive={
|
||||
selectedModel
|
||||
? getModelDisplayName(selectedModel) === entry.title
|
||||
: false
|
||||
}
|
||||
onClick={() => {
|
||||
if (entry.providerCount > 1) {
|
||||
setActiveTitle(entry.title);
|
||||
setView("provider");
|
||||
return;
|
||||
}
|
||||
handleSelectModel(entry.entries[0].name);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{view === "provider" && activeTitle && (
|
||||
<div className="flex flex-col">
|
||||
<LlmMenuHeader
|
||||
label={activeTitle}
|
||||
onBack={() => setView("model")}
|
||||
/>
|
||||
<div className="border-b border-zinc-200" />
|
||||
{providerEntries.map((entry) => (
|
||||
<LlmMenuItem
|
||||
key={`${entry.title}-${entry.provider}`}
|
||||
title={getProviderDisplayName(entry)}
|
||||
icon={<LlmIcon value={entry.provider} />}
|
||||
isActive={selectedModel?.provider === entry.provider}
|
||||
onClick={() => handleSelectModel(entry.name)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import { CurrencyDollarSimpleIcon } from "@phosphor-icons/react";
|
||||
|
||||
type Props = {
|
||||
tier?: number;
|
||||
};
|
||||
|
||||
export function LlmPriceTier({ tier }: Props) {
|
||||
if (!tier || tier <= 0) {
|
||||
return null;
|
||||
}
|
||||
const clamped = Math.min(3, Math.max(1, tier));
|
||||
return (
|
||||
<div className="flex items-center text-zinc-900">
|
||||
{Array.from({ length: clamped }).map((_, index) => (
|
||||
<CurrencyDollarSimpleIcon
|
||||
key={`price-${index}`}
|
||||
className="-mr-0.5 h-3 w-3"
|
||||
weight="bold"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { LlmModelMetadata } from "./types";
|
||||
|
||||
export function groupByCreator(models: LlmModelMetadata[]) {
|
||||
const map = new Map<string, LlmModelMetadata[]>();
|
||||
for (const model of models) {
|
||||
const key = getCreatorDisplayName(model);
|
||||
const existing = map.get(key) ?? [];
|
||||
existing.push(model);
|
||||
map.set(key, existing);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export function groupByTitle(models: LlmModelMetadata[]) {
|
||||
const map = new Map<string, LlmModelMetadata[]>();
|
||||
for (const model of models) {
|
||||
const displayName = getModelDisplayName(model);
|
||||
const existing = map.get(displayName) ?? [];
|
||||
existing.push(model);
|
||||
map.set(displayName, existing);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export function getCreatorDisplayName(model: LlmModelMetadata): string {
|
||||
return model.creator_name || model.creator || "";
|
||||
}
|
||||
|
||||
export function getModelDisplayName(model: LlmModelMetadata): string {
|
||||
return model.title || model.name || "";
|
||||
}
|
||||
|
||||
export function getProviderDisplayName(model: LlmModelMetadata): string {
|
||||
return model.provider_name || model.provider || "";
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export type LlmModelMetadata = {
|
||||
creator: string;
|
||||
creator_name: string;
|
||||
title: string;
|
||||
provider: string;
|
||||
provider_name: string;
|
||||
name: string;
|
||||
price_tier?: number;
|
||||
};
|
||||
|
||||
export type LlmModelMetadataMap = Record<string, LlmModelMetadata>;
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
isMultiSelectSchema,
|
||||
} from "../utils/schema-utils";
|
||||
import { TableField } from "./TableField/TableField";
|
||||
import { LlmModelField } from "./LlmModelField/LlmModelField";
|
||||
|
||||
export interface CustomFieldDefinition {
|
||||
id: string;
|
||||
@@ -57,6 +58,15 @@ export const CUSTOM_FIELDS: CustomFieldDefinition[] = [
|
||||
},
|
||||
component: TableField,
|
||||
},
|
||||
{
|
||||
id: "custom/llm_model_field",
|
||||
matcher: (schema: any) => {
|
||||
return (
|
||||
typeof schema === "object" && schema !== null && "llm_model" in schema
|
||||
);
|
||||
},
|
||||
component: LlmModelField,
|
||||
},
|
||||
];
|
||||
|
||||
export function findCustomFieldId(schema: any): string | null {
|
||||
|
||||