fix(web): correct Add Provider form DTO field mapping (#623)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful

Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #623.
This commit is contained in:
2026-03-01 19:19:04 +00:00
committed by jason.woltje
parent 99a4567e32
commit 861eff4686
3 changed files with 50 additions and 37 deletions

View File

@@ -85,6 +85,14 @@ const INITIAL_FORM: ProviderFormState = {
isActive: true, isActive: true,
}; };
function mapProviderTypeToApi(type: string): "ollama" | "openai" | "claude" {
if (type === "ollama" || type === "claude") {
return type;
}
return "openai";
}
function getErrorMessage(error: unknown, fallback: string): string { function getErrorMessage(error: unknown, fallback: string): string {
if (error instanceof Error && error.message.trim().length > 0) { if (error instanceof Error && error.message.trim().length > 0) {
return error.message; return error.message;
@@ -93,18 +101,6 @@ function getErrorMessage(error: unknown, fallback: string): string {
return fallback; return fallback;
} }
function buildProviderName(displayName: string, type: string): string {
const slug = displayName
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+/, "")
.replace(/-+$/, "");
const candidate = `${type}-${slug.length > 0 ? slug : "provider"}`;
return candidate.slice(0, 100);
}
function normalizeProviderModels(models: unknown): FleetProviderModel[] { function normalizeProviderModels(models: unknown): FleetProviderModel[] {
if (!Array.isArray(models)) { if (!Array.isArray(models)) {
return []; return [];
@@ -153,11 +149,11 @@ function modelsToEditorText(models: unknown): string {
.join("\n"); .join("\n");
} }
function parseModelsText(value: string): FleetProviderModel[] { function parseModelsText(value: string): string[] {
const seen = new Set<string>(); const seen = new Set<string>();
return value return value
.split(/\n|,/g) .split(/\r?\n/g)
.map((segment) => segment.trim()) .map((segment) => segment.trim())
.filter((segment) => segment.length > 0) .filter((segment) => segment.length > 0)
.filter((segment) => { .filter((segment) => {
@@ -166,8 +162,7 @@ function parseModelsText(value: string): FleetProviderModel[] {
} }
seen.add(segment); seen.add(segment);
return true; return true;
}) });
.map((id) => ({ id, name: id }));
} }
function maskApiKey(value: string): string { function maskApiKey(value: string): string {
@@ -279,6 +274,7 @@ export default function ProvidersSettingsPage(): ReactElement {
} }
const models = parseModelsText(form.modelsText); const models = parseModelsText(form.modelsText);
const providerModels = models.map((id) => ({ id, name: id }));
const baseUrl = form.baseUrl.trim(); const baseUrl = form.baseUrl.trim();
const apiKey = form.apiKey.trim(); const apiKey = form.apiKey.trim();
@@ -289,7 +285,7 @@ export default function ProvidersSettingsPage(): ReactElement {
const updatePayload: UpdateFleetProviderRequest = { const updatePayload: UpdateFleetProviderRequest = {
displayName, displayName,
isActive: form.isActive, isActive: form.isActive,
models, models: providerModels,
}; };
if (baseUrl.length > 0) { if (baseUrl.length > 0) {
@@ -303,21 +299,27 @@ export default function ProvidersSettingsPage(): ReactElement {
await updateFleetProvider(editingProvider.id, updatePayload); await updateFleetProvider(editingProvider.id, updatePayload);
setSuccessMessage(`Updated provider "${displayName}".`); setSuccessMessage(`Updated provider "${displayName}".`);
} else { } else {
const createPayload: CreateFleetProviderRequest = { const config: CreateFleetProviderRequest["config"] = {};
name: buildProviderName(displayName, form.type),
displayName,
type: form.type,
models,
};
if (baseUrl.length > 0) { if (baseUrl.length > 0) {
createPayload.baseUrl = baseUrl; config.endpoint = baseUrl;
} }
if (apiKey.length > 0) { if (apiKey.length > 0) {
createPayload.apiKey = apiKey; config.apiKey = apiKey;
} }
if (models.length > 0) {
config.models = models;
}
const createPayload: CreateFleetProviderRequest = {
displayName,
providerType: mapProviderTypeToApi(form.type),
config,
isEnabled: form.isActive,
};
await createFleetProvider(createPayload); await createFleetProvider(createPayload);
setSuccessMessage(`Added provider "${displayName}".`); setSuccessMessage(`Added provider "${displayName}".`);
} }

View File

@@ -34,17 +34,25 @@ describe("createFleetProvider", (): void => {
vi.mocked(client.apiPost).mockResolvedValueOnce({ id: "provider-1" } as never); vi.mocked(client.apiPost).mockResolvedValueOnce({ id: "provider-1" } as never);
await createFleetProvider({ await createFleetProvider({
name: "openai-main", providerType: "openai",
displayName: "OpenAI Main", displayName: "OpenAI Main",
type: "openai", config: {
endpoint: "https://api.openai.com/v1",
apiKey: "sk-test", apiKey: "sk-test",
models: ["gpt-4.1-mini", "gpt-4o-mini"],
},
isEnabled: true,
}); });
expect(client.apiPost).toHaveBeenCalledWith("/api/fleet-settings/providers", { expect(client.apiPost).toHaveBeenCalledWith("/api/fleet-settings/providers", {
name: "openai-main", providerType: "openai",
displayName: "OpenAI Main", displayName: "OpenAI Main",
type: "openai", config: {
endpoint: "https://api.openai.com/v1",
apiKey: "sk-test", apiKey: "sk-test",
models: ["gpt-4.1-mini", "gpt-4o-mini"],
},
isEnabled: true,
}); });
}); });
}); });

View File

@@ -16,13 +16,16 @@ export interface FleetProvider {
} }
export interface CreateFleetProviderRequest { export interface CreateFleetProviderRequest {
name: string; providerType: "ollama" | "openai" | "claude";
displayName: string; displayName: string;
type: string; config: {
baseUrl?: string; endpoint?: string;
apiKey?: string; apiKey?: string;
apiType?: string; models?: string[];
models?: FleetProviderModel[]; timeout?: number;
};
isDefault?: boolean;
isEnabled?: boolean;
} }
export interface UpdateFleetProviderRequest { export interface UpdateFleetProviderRequest {