Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ OPENROUTERAPIKEY='<add_your_openrouter_api_key_here>'
# If you want to use IO.net, add your key here
IONETAPIKEY='<add_your_ionet_api_key_here>'

# If you want to use Morpheus decentralized AI, add your key here
MORPHEUSAPIKEY='<add_your_morpheus_api_key_here>'

########################################
# API Rate Limiting Configuration
RATE_LIMIT_ENABLED='false' # Enable/disable API key rate limiting
Expand Down
22 changes: 21 additions & 1 deletion backend/node/create_nodes/providers_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"anthropic",
"xai",
"google",
"openrouter"
"openrouter",
"morpheus"
]
},
"model": {
Expand Down Expand Up @@ -212,6 +213,25 @@
}
}
},
{
"if": {
"properties": {
"provider": { "const": "morpheus" }
}
},
"then": {
"x-models-url": "https://api.mor.org/api/v1/models",
"x-plugin-config-defaults": {
"api_key_env_var": "MORPHEUSAPIKEY",
"api_url": "https://api.mor.org/api/v1"
},
"properties": {
"plugin": {
"const": "openai-compatible"
}
}
}
},
{
"if": {
"properties": {
Expand Down
22 changes: 21 additions & 1 deletion frontend/src/assets/schemas/providers_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"openai",
"anthropic",
"xai",
"google"
"google",
"morpheus"
]
},
"model": {
Expand Down Expand Up @@ -150,6 +151,25 @@
}
}
},
{
"if": {
"properties": {
"provider": { "const": "morpheus" }
}
},
"then": {
"x-models-url": "https://api.mor.org/api/v1/models",
"x-plugin-config-defaults": {
"api_key_env_var": "MORPHEUSAPIKEY",
"api_url": "https://api.mor.org/api/v1"
},
"properties": {
"plugin": {
"const": "openai-compatible"
}
}
}
},
{
"if": {
"properties": {
Expand Down
58 changes: 56 additions & 2 deletions frontend/src/components/Simulator/ProviderModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ const pluginOptions = ref<string[]>([]);
const modelOptions = ref<string[]>([]);
const isPluginLocked = ref(false);
const customProvider = ref(false);
const isLoadingModels = ref(false);
const modelFetchError = ref('');
const providerOptions = ref<string[]>([]);

const availablePluginOptions = computed(() => {
Expand Down Expand Up @@ -201,6 +203,30 @@ function extractDefaults(
return defaults;
}

const fetchDynamicModels = async (modelsUrl: string) => {
isLoadingModels.value = true;
modelFetchError.value = '';
try {
const response = await fetch(modelsUrl);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
if (data?.data && Array.isArray(data.data)) {
modelOptions.value = data.data
.filter((m: any) => !m.modelType || m.modelType === 'LLM')
.map((m: any) => m.id);
if (modelOptions.value.length > 0 && isCreateMode.value) {
newProviderData.model = modelOptions.value[0];
}
}
} catch (err) {
console.error('Failed to fetch models:', err);
modelFetchError.value =
'Could not fetch available models. You can type a model name manually.';
} finally {
isLoadingModels.value = false;
}
};

const pluginConfigProperties = ref<Record<string, any>>({});
const configProperties = ref<Record<string, any>>({});

Expand Down Expand Up @@ -259,6 +285,27 @@ const checkRules = () => {
}
}
});

// Provider-specific post-processing (x-models-url, x-plugin-config-defaults)
schema.allOf.forEach((rule: any) => {
if (rule.if?.properties?.provider?.const === newProviderData.provider) {
// Override plugin_config with provider-specific defaults
if (rule.then?.['x-plugin-config-defaults'] && isCreateMode.value) {
Object.assign(
newProviderData.plugin_config,
rule.then['x-plugin-config-defaults'],
);
}
// Fetch models dynamically for providers with x-models-url
if (
rule.then?.['x-models-url'] &&
modelOptions.value.length === 0 &&
isCreateMode.value
) {
fetchDynamicModels(rule.then['x-models-url']);
}
}
});
};

const toggleCustomProvider = () => {
Expand Down Expand Up @@ -390,8 +437,15 @@ const configurationError = computed(() => {

<MoreInfo text="The name of the model." />
</FieldLabel>

<div v-if="isLoadingModels" class="py-2 text-sm opacity-50">
Loading available models...
</div>

<Alert warning v-if="modelFetchError">{{ modelFetchError }}</Alert>

<TextInput
v-if="modelOptions.length === 0"
v-if="modelOptions.length === 0 && !isLoadingModels"
id="model"
name="model"
v-model="newProviderData.model"
Expand All @@ -400,7 +454,7 @@ const configurationError = computed(() => {
placeholder="i.e. my-model"
/>
<SelectInput
v-if="modelOptions.length > 0"
v-if="modelOptions.length > 0 && !isLoadingModels"
id="plugin"
name="plugin"
v-model="newProviderData.model"
Expand Down