Explain HF's per-provider BYOK requirement in the Custom Model ID sheet and chat errors
Some Hugging Face backends (e.g. Featherless AI) only route requests if the user has added their own API key for that provider at HF's settings — surfaced by Confab as a bare "not supported by any provider you have enabled" error with no indication why. Enriches that specific error with guidance, and adds the same note to the Custom Model ID entry point. Also converted the alert-based Custom Model ID dialog to a real sheet since SwiftUI alerts can't be resized and it read as cramped.
This commit is contained in:
@@ -1255,7 +1255,7 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
||||
messages.remove(at: index)
|
||||
}
|
||||
Log.api.error("Generation failed: \(error.localizedDescription)")
|
||||
showSystemMessage("❌ \(friendlyErrorMessage(from: error))")
|
||||
showSystemMessage("❌ \(Self.friendlyErrorMessage(from: error))")
|
||||
}
|
||||
|
||||
isGenerating = false
|
||||
@@ -1935,7 +1935,7 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
||||
messages.append(assistantMessage)
|
||||
} else {
|
||||
Log.api.error("Tool generation failed: \(error.localizedDescription)")
|
||||
showSystemMessage("❌ \(friendlyErrorMessage(from: error))")
|
||||
showSystemMessage("❌ \(Self.friendlyErrorMessage(from: error))")
|
||||
}
|
||||
|
||||
isGenerating = false
|
||||
@@ -1976,7 +1976,7 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
||||
|
||||
// MARK: - Error Helpers
|
||||
|
||||
private func friendlyErrorMessage(from error: Error) -> String {
|
||||
nonisolated static func friendlyErrorMessage(from error: Error) -> String {
|
||||
let desc = error.localizedDescription
|
||||
|
||||
// Network connectivity
|
||||
@@ -1997,6 +1997,14 @@ Don't narrate future actions ("Let me...") - just use the tools.
|
||||
if desc.contains("401") || desc.contains("403") || desc.lowercased().contains("unauthorized") || desc.lowercased().contains("invalid.*key") {
|
||||
return "Invalid API key. Update it in Settings (\u{2318},)."
|
||||
}
|
||||
|
||||
// Hugging Face: model exists but no *enabled* provider can serve it — usually means the
|
||||
// provider that hosts it (e.g. Featherless AI) needs the user's own API key on HF's side,
|
||||
// not a Confab-side problem.
|
||||
if desc.lowercased().contains("not supported by any provider you have enabled") {
|
||||
let base = desc.hasPrefix("Unknown error: ") ? String(desc.dropFirst("Unknown error: ".count)) : desc
|
||||
return base + " Some Hugging Face providers (e.g. Featherless AI) require your own API key — add one at huggingface.co/settings/inference-providers, or pick a model hosted by a provider Hugging Face bills directly (Together, Fireworks, Groq, Cerebras, etc.)."
|
||||
}
|
||||
if desc.contains("429") || desc.lowercased().contains("rate limit") {
|
||||
return "Rate limited. Wait a moment and try again."
|
||||
}
|
||||
|
||||
@@ -277,19 +277,60 @@ struct ModelSelectorView: View {
|
||||
.sheet(item: $selectedInfoModel) { model in
|
||||
ModelInfoView(model: model)
|
||||
}
|
||||
.alert("Custom Model ID", isPresented: $showCustomModelAlert) {
|
||||
TextField("org/model-name", text: $customModelIDText)
|
||||
Button("Add") {
|
||||
onCustomModelID?(customModelIDText)
|
||||
}
|
||||
Button("Cancel", role: .cancel) {}
|
||||
} message: {
|
||||
Text("Enter a Hugging Face model ID, e.g. meta-llama/Llama-3.1-8B-Instruct")
|
||||
.sheet(isPresented: $showCustomModelAlert) {
|
||||
CustomModelIDSheet(
|
||||
modelID: $customModelIDText,
|
||||
onAdd: {
|
||||
onCustomModelID?(customModelIDText)
|
||||
showCustomModelAlert = false
|
||||
},
|
||||
onCancel: { showCustomModelAlert = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Custom Model ID Sheet
|
||||
|
||||
struct CustomModelIDSheet: View {
|
||||
@Binding var modelID: String
|
||||
let onAdd: () -> Void
|
||||
let onCancel: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Custom Model ID")
|
||||
.font(.system(size: 17, weight: .semibold))
|
||||
|
||||
Text("Enter a Hugging Face model ID, e.g. meta-llama/Llama-3.1-8B-Instruct")
|
||||
.font(.system(size: 13))
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
TextField("org/model-name", text: $modelID)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.onSubmit(onAdd)
|
||||
|
||||
Text("Some models are only served by providers that require your own API key, set at huggingface.co/settings/inference-providers. If adding a model fails, check there for that provider, or pick one from Confab's model list instead.")
|
||||
.font(.system(size: 12))
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Cancel", role: .cancel, action: onCancel)
|
||||
Button("Add", action: onAdd)
|
||||
.buttonStyle(.borderedProminent)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
.disabled(modelID.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
|
||||
}
|
||||
}
|
||||
.padding(24)
|
||||
.frame(minWidth: 440)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sort Order
|
||||
|
||||
enum ModelSortOrder: String, CaseIterable {
|
||||
|
||||
@@ -197,4 +197,27 @@ struct ChatViewModelPureLogicTests {
|
||||
let (_, body) = ChatViewModel.extractNotesUpdate(from: content)
|
||||
#expect(body == "")
|
||||
}
|
||||
|
||||
// MARK: - friendlyErrorMessage
|
||||
|
||||
@Test("A Hugging Face 'no enabled provider' error keeps the original text and appends BYOK guidance")
|
||||
func friendlyErrorMessageEnrichesHuggingFaceProviderGap() {
|
||||
let error = ProviderError.unknown("The requested model 'org/model' is not supported by any provider you have enabled.")
|
||||
let message = ChatViewModel.friendlyErrorMessage(from: error)
|
||||
#expect(message.contains("The requested model 'org/model' is not supported by any provider you have enabled."))
|
||||
#expect(!message.hasPrefix("Unknown error:"))
|
||||
#expect(message.contains("huggingface.co/settings/inference-providers"))
|
||||
}
|
||||
|
||||
@Test("A 401 error maps to the invalid API key message")
|
||||
func friendlyErrorMessageMapsUnauthorized() {
|
||||
let error = ProviderError.unknown("HTTP 401")
|
||||
#expect(ChatViewModel.friendlyErrorMessage(from: error) == "Invalid API key. Update it in Settings (\u{2318},).")
|
||||
}
|
||||
|
||||
@Test("An unrecognized error falls back to its raw description")
|
||||
func friendlyErrorMessageFallsBackToRawDescription() {
|
||||
let error = ProviderError.unknown("Something entirely unexpected happened")
|
||||
#expect(ChatViewModel.friendlyErrorMessage(from: error) == "Unknown error: Something entirely unexpected happened")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user