Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
23 changes: 23 additions & 0 deletions web-app/public/images/model-provider/azure.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions web-app/src/consts/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,40 @@ export const predefinedProviders = [
],
models: [],
},
{
active: true,
api_key: '',
base_url: 'https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1',
explore_models_url: 'https://oai.azure.com/deployments',
provider: 'azure',
settings: [
{
key: 'api-key',
title: 'API Key',
description:
'The Azure OpenAI API uses API keys for authentication. Visit your [Azure OpenAI Studio](https://oai.azure.com/) to retrieve the API key from your resource.',
controller_type: 'input',
controller_props: {
placeholder: 'Insert API Key',
value: '',
type: 'password',
input_actions: ['unobscure', 'copy'],
},
},
{
key: 'base-url',
title: 'Base URL',
description:
'Your Azure OpenAI resource endpoint. See the [Azure OpenAI documentation](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/latest) for more information.',
controller_type: 'input',
controller_props: {
placeholder: 'https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1',
value: 'https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1',
},
},
],
models: [],
},
{
active: true,
api_key: '',
Expand Down
8 changes: 4 additions & 4 deletions web-app/src/containers/dialogs/AddModel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {

Check warning on line 1 in web-app/src/containers/dialogs/AddModel.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

1 line is not covered with tests
Dialog,
DialogContent,
DialogDescription,
Expand All @@ -7,76 +7,76 @@
DialogTrigger,
DialogFooter,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { useModelProvider } from '@/hooks/useModelProvider'
import { useProviderModels } from '@/hooks/useProviderModels'
import { ModelCombobox } from '@/containers/ModelCombobox'
import { IconPlus } from '@tabler/icons-react'
import { useState } from 'react'
import { getProviderTitle } from '@/lib/utils'
import { useTranslation } from '@/i18n/react-i18next-compat'
import { ModelCapabilities } from '@/types/models'
import { models as providerModels } from 'token.js'

Check warning on line 19 in web-app/src/containers/dialogs/AddModel.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

10-19 lines are not covered with tests

type DialogAddModelProps = {
provider: ModelProvider
trigger?: React.ReactNode
}

export const DialogAddModel = ({ provider, trigger }: DialogAddModelProps) => {
const { t } = useTranslation()
const { updateProvider } = useModelProvider()
const [modelId, setModelId] = useState<string>('')
const [open, setOpen] = useState(false)
const [isComboboxOpen, setIsComboboxOpen] = useState(false)

Check warning on line 31 in web-app/src/containers/dialogs/AddModel.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

26-31 lines are not covered with tests

// Fetch models from provider API (API key is optional)
const { models, loading, error, refetch } = useProviderModels(
provider.base_url ? provider : undefined
)

Check warning on line 36 in web-app/src/containers/dialogs/AddModel.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

34-36 lines are not covered with tests

// Handle form submission
const handleSubmit = () => {
if (!modelId.trim()) {
return // Don't submit if model ID is empty
}

Check warning on line 42 in web-app/src/containers/dialogs/AddModel.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

39-42 lines are not covered with tests

// Create the new model
const newModel = {
id: modelId,
model: modelId,
name: modelId,
capabilities: [
ModelCapabilities.COMPLETION,

Check warning on line 50 in web-app/src/containers/dialogs/AddModel.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

45-50 lines are not covered with tests
(
providerModels[
provider.provider as unknown as keyof typeof providerModels
].supportsToolCalls as unknown as string[]
).includes(modelId)
]?.supportsToolCalls as unknown as string[]
)?.includes(modelId)
? ModelCapabilities.TOOLS
: undefined,

Check warning on line 57 in web-app/src/containers/dialogs/AddModel.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

52-57 lines are not covered with tests
(
providerModels[
provider.provider as unknown as keyof typeof providerModels
].supportsImages as unknown as string[]
).includes(modelId)
]?.supportsImages as unknown as string[]
)?.includes(modelId)
? ModelCapabilities.VISION
: undefined,
].filter(Boolean) as string[],
version: '1.0',
}

Check warning on line 67 in web-app/src/containers/dialogs/AddModel.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

59-67 lines are not covered with tests

// Update the provider with the new model
const updatedModels = [...provider.models, newModel]
updateProvider(provider.provider, {
...provider,
models: updatedModels,
})

Check warning on line 74 in web-app/src/containers/dialogs/AddModel.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

70-74 lines are not covered with tests

// Reset form and close dialog
setModelId('')
setOpen(false)
}

Check warning on line 79 in web-app/src/containers/dialogs/AddModel.tsx

View workflow job for this annotation

GitHub Actions / coverage-check

77-79 lines are not covered with tests

return (
<Dialog open={open} onOpenChange={setOpen}>
Expand Down
4 changes: 3 additions & 1 deletion web-app/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export function getProviderLogo(provider: string) {
return '/images/model-provider/gemini.svg'
case 'openai':
return '/images/model-provider/openai.svg'
case 'azure':
return '/images/model-provider/azure.svg'
default:
return undefined
}
Expand Down Expand Up @@ -161,5 +163,5 @@ export function formatDuration(startTime: number, endTime?: number): string {
}

export function sanitizeModelId(modelId: string): string {
return modelId.replace(/[^a-zA-Z0-9/_\-.]/g, '').replace(/\./g, "_")
return modelId.replace(/[^a-zA-Z0-9/_\-.]/g, '').replace(/\./g, '_')
}
8 changes: 4 additions & 4 deletions web-app/src/services/providers/tauri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ export class TauriProvidersService extends DefaultProvidersService {
(
providerModels[
provider.provider as unknown as keyof typeof providerModels
].supportsToolCalls as unknown as string[]
).includes(model)
]?.supportsToolCalls as unknown as string[]
)?.includes(model)
? ModelCapabilities.TOOLS
: undefined,
(
providerModels[
provider.provider as unknown as keyof typeof providerModels
].supportsImages as unknown as string[]
).includes(model)
]?.supportsImages as unknown as string[]
)?.includes(model)
? ModelCapabilities.VISION
: undefined,
].filter(Boolean) as string[]
Expand Down
25 changes: 17 additions & 8 deletions web-app/src/services/providers/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ export class WebProvidersService implements ProvidersService {
(
providerModels[
provider.provider as unknown as keyof typeof providerModels
].supportsToolCalls as unknown as string[]
).includes(model)
]?.supportsToolCalls as unknown as string[]
)?.includes(model)
? ModelCapabilities.TOOLS
: undefined,
].filter(Boolean) as string[]
Expand Down Expand Up @@ -163,7 +163,9 @@ export class WebProvidersService implements ProvidersService {
// Handle different response formats that providers might use
if (data.data && Array.isArray(data.data)) {
// OpenAI format: { data: [{ id: "model-id" }, ...] }
return data.data.map((model: { id: string }) => model.id).filter(Boolean)
return data.data
.map((model: { id: string }) => model.id)
.filter(Boolean)
} else if (Array.isArray(data)) {
// Direct array format: ["model-id1", "model-id2", ...]
return data
Expand All @@ -189,11 +191,15 @@ export class WebProvidersService implements ProvidersService {
'Authentication failed',
'Access forbidden',
'Models endpoint not found',
'Failed to fetch models from'
'Failed to fetch models from',
]

if (error instanceof Error &&
structuredErrorPrefixes.some(prefix => (error as Error).message.startsWith(prefix))) {
if (
error instanceof Error &&
structuredErrorPrefixes.some((prefix) =>
(error as Error).message.startsWith(prefix)
)
) {
throw new Error(error.message)
}

Expand All @@ -211,7 +217,10 @@ export class WebProvidersService implements ProvidersService {
}
}

async updateSettings(providerName: string, settings: ProviderSetting[]): Promise<void> {
async updateSettings(
providerName: string,
settings: ProviderSetting[]
): Promise<void> {
await ExtensionManager.getInstance()
.getEngine(providerName)
?.updateSettings(
Expand All @@ -233,4 +242,4 @@ export class WebProvidersService implements ProvidersService {
// Web implementation uses regular fetch
return fetch
}
}
}
Loading