Overview
Add a ProviderBadge component to the Feedback view to show users which AI provider generated the categorization result. This provides transparency about the AI system.
Dependencies
Note: Can be developed in parallel with most other PRs.
Files to Modify
| File |
Action |
Description |
Sources/SortAI/App/FeedbackView.swift |
Modify |
Add ProviderBadge |
Sources/SortAI/App/Components/ProviderBadge.swift |
Create |
Badge component |
Implementation Details
1. ProviderBadge Component
import SwiftUI
/// A badge showing which AI provider generated a result
struct ProviderBadge: View {
let provider: String
var showLabel: Bool = true
var size: BadgeSize = .medium
enum BadgeSize {
case small, medium, large
var fontSize: Font {
switch self {
case .small: return .caption2
case .medium: return .caption
case .large: return .footnote
}
}
var iconSize: CGFloat {
switch self {
case .small: return 10
case .medium: return 12
case .large: return 14
}
}
var padding: EdgeInsets {
switch self {
case .small: return EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4)
case .medium: return EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)
case .large: return EdgeInsets(top: 6, leading: 10, bottom: 6, trailing: 10)
}
}
}
var body: some View {
HStack(spacing: 4) {
Image(systemName: iconName)
.font(.system(size: size.iconSize))
if showLabel {
Text(displayName)
}
}
.font(size.fontSize)
.padding(size.padding)
.background(backgroundColor.opacity(0.15))
.foregroundColor(foregroundColor)
.cornerRadius(size == .small ? 4 : 8)
.overlay(
RoundedRectangle(cornerRadius: size == .small ? 4 : 8)
.stroke(backgroundColor.opacity(0.3), lineWidth: 1)
)
.help(tooltip)
}
// MARK: - Provider Styling
private var displayName: String {
switch provider.lowercased() {
case "apple-intelligence": return "Apple Intelligence"
case "ollama": return "Ollama"
case "openai": return "OpenAI"
case "anthropic": return "Anthropic"
case "local-ml": return "Local ML"
default: return provider.capitalized
}
}
private var iconName: String {
switch provider.lowercased() {
case "apple-intelligence": return "apple.logo"
case "ollama": return "server.rack"
case "openai": return "cloud.fill"
case "anthropic": return "cloud.fill"
case "local-ml": return "cpu"
default: return "questionmark.circle"
}
}
private var backgroundColor: Color {
switch provider.lowercased() {
case "apple-intelligence": return .blue
case "ollama": return .green
case "openai": return .teal
case "anthropic": return .orange
case "local-ml": return .purple
default: return .gray
}
}
private var foregroundColor: Color {
backgroundColor
}
private var tooltip: String {
switch provider.lowercased() {
case "apple-intelligence":
return "Categorized using Apple Intelligence (on-device)"
case "ollama":
return "Categorized using Ollama (local server)"
case "openai":
return "Categorized using OpenAI API (cloud)"
case "anthropic":
return "Categorized using Anthropic API (cloud)"
case "local-ml":
return "Categorized using local machine learning heuristics"
default:
return "Categorized using \(displayName)"
}
}
}
// MARK: - Preview
#Preview {
VStack(spacing: 20) {
HStack(spacing: 16) {
ProviderBadge(provider: "apple-intelligence")
ProviderBadge(provider: "ollama")
ProviderBadge(provider: "openai")
}
HStack(spacing: 16) {
ProviderBadge(provider: "anthropic")
ProviderBadge(provider: "local-ml")
}
// Size variations
HStack(spacing: 16) {
ProviderBadge(provider: "apple-intelligence", size: .small)
ProviderBadge(provider: "apple-intelligence", size: .medium)
ProviderBadge(provider: "apple-intelligence", size: .large)
}
// Icon only
HStack(spacing: 16) {
ProviderBadge(provider: "apple-intelligence", showLabel: false, size: .small)
ProviderBadge(provider: "ollama", showLabel: false, size: .small)
}
}
.padding()
}
2. Integration in FeedbackView
// In FeedbackView.swift
struct FeedbackView: View {
let result: BrainResult
// ... existing properties
var body: some View {
VStack(alignment: .leading, spacing: 16) {
// Header with provider badge
HStack {
Text(result.suggestedCategory)
.font(.headline)
Spacer()
// Confidence and provider
HStack(spacing: 12) {
ConfidenceBadge(confidence: result.confidence)
if let provider = result.provider {
ProviderBadge(provider: provider, size: .small)
}
}
}
// ... rest of the view
}
}
}
// Alternative: Provider info row
struct CategorizationInfoRow: View {
let result: BrainResult
var body: some View {
HStack {
Label {
Text("Categorized by")
} icon: {
Image(systemName: "sparkles")
}
.foregroundColor(.secondary)
Spacer()
if let provider = result.provider {
ProviderBadge(provider: provider)
} else {
Text("Unknown")
.foregroundColor(.secondary)
}
}
.font(.caption)
}
}
3. Usage in ProcessingItem List
// In ContentView.swift or ProcessingItemRow.swift
struct ProcessingItemRow: View {
let item: ProcessingItem
var body: some View {
HStack {
// File icon and name
FileIconView(item: item)
VStack(alignment: .leading) {
Text(item.filename)
.font(.body)
if let category = item.suggestedCategory {
Text(category)
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
// Status badges
HStack(spacing: 8) {
if let confidence = item.confidence {
ConfidenceBadge(confidence: confidence, compact: true)
}
if let provider = item.provider {
ProviderBadge(provider: provider, showLabel: false, size: .small)
}
}
}
}
}
Visual Design
┌────────────────────────────────────────────────────────────────┐
│ Processing Results │
├────────────────────────────────────────────────────────────────┤
│ │
│ 📄 quarterly_report.pdf │
│ Documents / Financial / Reports │
│ Confidence: 0.92 ██████████░ │ 🍎 Apple Intel │ │
│ │
│ 📄 meeting_notes.docx │
│ Documents / Work / Meetings │
│ Confidence: 0.78 ████████░░░ │ 🖥 Ollama │ │
│ │
│ 📄 random_file.dat │
│ Documents / Misc │
│ Confidence: 0.45 █████░░░░░░ │ 🟣 Local ML │ │
│ │
└────────────────────────────────────────────────────────────────┘
Badge Styles:
┌──────────────────┐ ┌─────────┐ ┌───────────┐ ┌──────────┐
│ 🍎 Apple Intel │ │ 🖥 Ollama│ │ ☁ OpenAI│ │ 🟣 Local │
└──────────────────┘ └─────────┘ └───────────┘ └──────────┘
(blue) (green) (teal) (purple)
Acceptance Criteria
Testing
func testProviderBadgeDisplay() {
let providers = ["apple-intelligence", "ollama", "openai", "anthropic", "local-ml"]
for provider in providers {
let badge = ProviderBadge(provider: provider)
// Each provider should have a valid display name and icon
XCTAssertFalse(badge.displayName.isEmpty)
XCTAssertFalse(badge.iconName.isEmpty)
}
}
func testProviderBadgeSizes() {
let sizes: [ProviderBadge.BadgeSize] = [.small, .medium, .large]
for size in sizes {
let badge = ProviderBadge(provider: "apple-intelligence", size: size)
// Badge should render without errors
XCTAssertNotNil(badge)
}
}
func testUnknownProvider() {
let badge = ProviderBadge(provider: "unknown-provider")
XCTAssertEqual(badge.displayName, "Unknown-Provider")
XCTAssertEqual(badge.iconName, "questionmark.circle")
}
Estimated Size
~50 lines of code
Risk Assessment
Low - Simple SwiftUI component with no external dependencies.
Overview
Add a
ProviderBadgecomponent to the Feedback view to show users which AI provider generated the categorization result. This provides transparency about the AI system.Dependencies
Note: Can be developed in parallel with most other PRs.
Files to Modify
Sources/SortAI/App/FeedbackView.swiftSources/SortAI/App/Components/ProviderBadge.swiftImplementation Details
1. ProviderBadge Component
2. Integration in FeedbackView
3. Usage in ProcessingItem List
Visual Design
Acceptance Criteria
Testing
Estimated Size
~50 lines of code
Risk Assessment
Low - Simple SwiftUI component with no external dependencies.