Files
SwiftDBAI/Example/SwiftDBAIDemo/SwiftDBAIDemo/SwiftDBAIDemoApp.swift
Krishna Kumar fcd752466a SwiftDBAI: natural language queries for any SQLite database
Drop-in SwiftUI chat view, headless ChatEngine, LLM-agnostic via
AnyLanguageModel. Read-only by default with configurable allowlists.
Robust SQL parser with 63 tests. Includes demo app with GitHub stars dataset.
2026-04-05 17:11:12 -05:00

262 lines
9.2 KiB
Swift

// SwiftDBAIDemoApp.swift
// SwiftDBAIDemo
//
// Showcase app demonstrating all SwiftDBAI UI variants and presentation modes.
import SwiftUI
import SwiftDBAI
@main
struct SwiftDBAIDemoApp: App {
@State private var databasePath: String?
@State private var setupError: String?
private let context = """
This is a database of the top ~2000 most-starred GitHub \
repositories. Each repo has: full_name (owner/name), stars, \
forks, language (programming language), description, \
open_issues, created_at date, and topics. \
Star counts are real and current as of April 2026.
"""
var body: some Scene {
WindowGroup {
Group {
if let path = databasePath {
ShowcaseTabView(databasePath: path, context: context)
} else if let error = setupError {
ContentUnavailableView(
"Database Setup Failed",
systemImage: "exclamationmark.triangle",
description: Text(error)
)
} else {
ProgressView("Setting up database...")
}
}
.task {
do {
let path = try DatabaseSeeder.seedIfNeeded()
databasePath = path
} catch {
setupError = error.localizedDescription
}
}
}
}
}
struct ShowcaseTabView: View {
let databasePath: String
let context: String
@State private var showSheet = false
@State private var showFullScreen = false
var body: some View {
TabView {
// Tab 1: Default theme
DataChatView(
databasePath: databasePath,
model: DemoLanguageModel(),
allowlist: .readOnly,
additionalContext: context
)
.tabItem { Label("Default", systemImage: "bubble.left.and.text.bubble.right") }
// Tab 2: Dark theme
DataChatView(
databasePath: databasePath,
model: DemoLanguageModel(),
allowlist: .readOnly,
additionalContext: context
)
.chatViewConfiguration(.dark)
.tabItem { Label("Dark", systemImage: "moon.fill") }
// Tab 3: Compact theme
DataChatView(
databasePath: databasePath,
model: DemoLanguageModel(),
allowlist: .readOnly,
additionalContext: context
)
.chatViewConfiguration(.compact)
.tabItem { Label("Compact", systemImage: "rectangle.compress.vertical") }
// Tab 4: Custom styling
DataChatView(
databasePath: databasePath,
model: DemoLanguageModel(),
allowlist: .readOnly,
additionalContext: context
)
.chatViewConfiguration(customConfig)
.tabItem { Label("Custom", systemImage: "paintbrush") }
// Tab 5: Presentation modes
PresentationShowcase(databasePath: databasePath, context: context)
.tabItem { Label("Present", systemImage: "rectangle.portrait.and.arrow.forward") }
// Tab 6: Tool calling API
ToolDemoView(databasePath: databasePath)
.tabItem { Label("Tool", systemImage: "wrench") }
}
}
private var customConfig: ChatViewConfiguration {
var config = ChatViewConfiguration.default
config.userBubbleColor = .purple
config.userTextColor = .white
config.accentColor = .purple
config.inputPlaceholder = "Search GitHub repos..."
config.emptyStateTitle = "Explore GitHub Data"
config.emptyStateSubtitle = "Ask about stars, forks, languages, and trends"
config.emptyStateIcon = "star.circle"
config.assistantAvatarIcon = "sparkles"
config.assistantAvatarColor = .purple
return config
}
}
struct PresentationShowcase: View {
let databasePath: String
let context: String
@State private var showSheet = false
@State private var showFullScreen = false
var body: some View {
NavigationStack {
List {
Section("Sheet Presentations") {
Button("Show as Sheet") {
showSheet = true
}
Button("Show Full Screen") {
showFullScreen = true
}
}
Section("Navigation") {
NavigationLink("Push DataChatView") {
DataChatView(
databasePath: databasePath,
model: DemoLanguageModel(),
allowlist: .readOnly,
additionalContext: context
)
.navigationTitle("Chat")
}
}
Section("Info") {
LabeledContent("DataChatSheet", value: "Nav + Done button")
LabeledContent("DataChatViewController", value: "UIKit bridge")
LabeledContent(".dataChatSheet()", value: "View modifier")
LabeledContent(".dataChatFullScreen()", value: "View modifier")
}
}
.navigationTitle("Presentation Modes")
}
.sheet(isPresented: $showSheet) {
DataChatSheet(
databasePath: databasePath,
model: DemoLanguageModel(),
additionalContext: context,
title: "GitHub Stars"
)
}
.dataChatFullScreen(
isPresented: $showFullScreen,
databasePath: databasePath,
model: DemoLanguageModel(),
additionalContext: context
)
}
}
struct ToolDemoView: View {
let databasePath: String
@State private var tool: DatabaseTool?
@State private var sqlInput = "SELECT full_name, stars FROM repos ORDER BY stars DESC LIMIT 5"
@State private var result: ToolResult?
@State private var error: String?
@State private var showSchema = false
var body: some View {
NavigationStack {
List {
if let tool {
Section("Schema") {
Button(showSchema ? "Hide Schema" : "Show Schema") {
showSchema.toggle()
}
if showSchema {
Text(tool.schemaContext)
.font(.caption2.monospaced())
}
}
Section("SQL Query") {
TextField("Enter SQL", text: $sqlInput, axis: .vertical)
.font(.footnote.monospaced())
.lineLimit(3...6)
Button("Execute") {
do {
result = try tool.execute(sql: sqlInput)
error = nil
} catch {
self.error = error.localizedDescription
result = nil
}
}
.disabled(sqlInput.isEmpty)
}
if let error {
Section("Error") {
Text(error)
.foregroundStyle(.red)
.font(.footnote)
}
}
if let result {
Section("Result (\(result.rowCount) rows, \(String(format: "%.1fms", result.executionTime * 1000)))") {
Text(result.markdownTable)
.font(.caption2.monospaced())
}
Section("JSON Response") {
Text(result.jsonString)
.font(.caption2.monospaced())
.lineLimit(15)
}
}
Section("OpenAI Tool Definition") {
Text(toolDefinitionJSON(tool))
.font(.caption2.monospaced())
.lineLimit(10)
}
} else {
ProgressView("Loading database...")
}
}
.navigationTitle("DatabaseTool API")
}
.task {
do {
tool = try await DatabaseTool(databasePath: databasePath)
} catch {
self.error = error.localizedDescription
}
}
}
private func toolDefinitionJSON(_ tool: DatabaseTool) -> String {
let def = tool.openAIFunctionDefinition
if let data = try? JSONSerialization.data(withJSONObject: def, options: [.prettyPrinted, .sortedKeys]),
let str = String(data: data, encoding: .utf8) {
return str
}
return "{}"
}
}