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.
This commit is contained in:
38
Example/SwiftDBAIDemo/SwiftDBAIDemo/DatabaseSeeder.swift
Normal file
38
Example/SwiftDBAIDemo/SwiftDBAIDemo/DatabaseSeeder.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
// DatabaseSeeder.swift
|
||||
// SwiftDBAIDemo
|
||||
//
|
||||
// Copies the bundled GitHub stars database to the Documents directory.
|
||||
// The database contains real star counts for ~2000 top GitHub repos,
|
||||
// fetched live from the GitHub API.
|
||||
|
||||
import Foundation
|
||||
|
||||
enum DatabaseSeeder {
|
||||
|
||||
/// Returns the path to the GitHub stars database, copying from bundle if needed.
|
||||
static func seedIfNeeded() throws -> String {
|
||||
let url = URL.documentsDirectory.appending(path: "github_stars.sqlite")
|
||||
let path = url.path(percentEncoded: false)
|
||||
|
||||
// If the database already exists, just return the path.
|
||||
if FileManager.default.fileExists(atPath: path) {
|
||||
return path
|
||||
}
|
||||
|
||||
// Copy bundled database to Documents
|
||||
guard let bundledURL = Bundle.main.url(forResource: "github_stars", withExtension: "sqlite") else {
|
||||
throw SeederError.bundledDatabaseNotFound
|
||||
}
|
||||
|
||||
try FileManager.default.copyItem(at: bundledURL, to: url)
|
||||
return path
|
||||
}
|
||||
|
||||
enum SeederError: LocalizedError {
|
||||
case bundledDatabaseNotFound
|
||||
|
||||
var errorDescription: String? {
|
||||
"Could not find github_stars.sqlite in app bundle."
|
||||
}
|
||||
}
|
||||
}
|
||||
253
Example/SwiftDBAIDemo/SwiftDBAIDemo/DemoLanguageModel.swift
Normal file
253
Example/SwiftDBAIDemo/SwiftDBAIDemo/DemoLanguageModel.swift
Normal file
@@ -0,0 +1,253 @@
|
||||
// DemoLanguageModel.swift
|
||||
// SwiftDBAIDemo
|
||||
//
|
||||
// A mock LanguageModel that returns canned SQL for common GitHub repo queries.
|
||||
// Pattern-matches natural language questions about GitHub stars, languages,
|
||||
// and repository metadata.
|
||||
|
||||
import AnyLanguageModel
|
||||
import Foundation
|
||||
|
||||
struct DemoLanguageModel: LanguageModel {
|
||||
typealias UnavailableReason = Never
|
||||
|
||||
func respond<Content>(
|
||||
within session: LanguageModelSession,
|
||||
to prompt: Prompt,
|
||||
generating type: Content.Type,
|
||||
includeSchemaInPrompt: Bool,
|
||||
options: GenerationOptions
|
||||
) async throws -> LanguageModelSession.Response<Content> where Content: Generable {
|
||||
let promptText = prompt.description.lowercased()
|
||||
let responseText: String
|
||||
|
||||
if promptText.contains("row") && (promptText.contains("column") || promptText.contains("|")) {
|
||||
responseText = deriveSummary(from: prompt.description)
|
||||
} else {
|
||||
responseText = deriveSQL(from: promptText)
|
||||
}
|
||||
|
||||
let rawContent = GeneratedContent(kind: .string(responseText))
|
||||
let content = try Content(rawContent)
|
||||
return LanguageModelSession.Response(
|
||||
content: content,
|
||||
rawContent: rawContent,
|
||||
transcriptEntries: [][...]
|
||||
)
|
||||
}
|
||||
|
||||
func streamResponse<Content>(
|
||||
within session: LanguageModelSession,
|
||||
to prompt: Prompt,
|
||||
generating type: Content.Type,
|
||||
includeSchemaInPrompt: Bool,
|
||||
options: GenerationOptions
|
||||
) -> sending LanguageModelSession.ResponseStream<Content> where Content: Generable {
|
||||
let rawContent = GeneratedContent(kind: .string("SELECT full_name, stars FROM repos ORDER BY stars DESC LIMIT 10"))
|
||||
let content = try! Content(rawContent)
|
||||
return LanguageModelSession.ResponseStream(content: content, rawContent: rawContent)
|
||||
}
|
||||
|
||||
// MARK: - SQL Pattern Matching
|
||||
|
||||
private func deriveSQL(from prompt: String) -> String {
|
||||
let q = extractLastQuestion(from: prompt)
|
||||
|
||||
// Specific repo lookups
|
||||
if q.contains("react") && !q.contains("react-native") && !q.contains("react native") {
|
||||
return "SELECT full_name, stars, forks, language, description FROM repos WHERE name = 'react' OR full_name LIKE '%/react' ORDER BY stars DESC LIMIT 5"
|
||||
}
|
||||
|
||||
// How many stars does X have
|
||||
if q.contains("how many stars") || q.contains("stars does") || q.contains("stars for") {
|
||||
return "SELECT full_name, stars, forks, language FROM repos ORDER BY stars DESC LIMIT 10"
|
||||
}
|
||||
|
||||
// Language breakdown MUST come before "most popular" to avoid collision
|
||||
if q.contains("language") && (q.contains("breakdown") || q.contains("distribution") || q.contains("popular") || q.contains("most")) {
|
||||
return """
|
||||
SELECT language, COUNT(*) AS repo_count,
|
||||
SUM(stars) AS total_stars,
|
||||
ROUND(AVG(stars)) AS avg_stars
|
||||
FROM repos WHERE language IS NOT NULL AND language != ''
|
||||
GROUP BY language
|
||||
ORDER BY total_stars DESC
|
||||
LIMIT 15
|
||||
"""
|
||||
}
|
||||
|
||||
// Most starred / top repos
|
||||
if q.contains("most starred") || q.contains("most popular") || q.contains("top repo") || q.contains("top 10") || q.contains("most stars") {
|
||||
return """
|
||||
SELECT full_name, stars, forks, language
|
||||
FROM repos ORDER BY stars DESC LIMIT 10
|
||||
"""
|
||||
}
|
||||
|
||||
// Language-specific queries
|
||||
if q.contains("python") && (q.contains("repo") || q.contains("project")) {
|
||||
return """
|
||||
SELECT full_name, stars, forks, description
|
||||
FROM repos WHERE language = 'Python'
|
||||
ORDER BY stars DESC LIMIT 10
|
||||
"""
|
||||
}
|
||||
if q.contains("swift") && (q.contains("repo") || q.contains("project")) {
|
||||
return """
|
||||
SELECT full_name, stars, forks, description
|
||||
FROM repos WHERE language = 'Swift'
|
||||
ORDER BY stars DESC LIMIT 10
|
||||
"""
|
||||
}
|
||||
if q.contains("rust") && (q.contains("repo") || q.contains("project")) {
|
||||
return """
|
||||
SELECT full_name, stars, forks, description
|
||||
FROM repos WHERE language = 'Rust'
|
||||
ORDER BY stars DESC LIMIT 10
|
||||
"""
|
||||
}
|
||||
if q.contains("typescript") && (q.contains("repo") || q.contains("project")) {
|
||||
return """
|
||||
SELECT full_name, stars, forks, description
|
||||
FROM repos WHERE language = 'TypeScript'
|
||||
ORDER BY stars DESC LIMIT 10
|
||||
"""
|
||||
}
|
||||
|
||||
// Count queries
|
||||
if q.contains("how many repo") || q.contains("how many project") || q.contains("total repo") {
|
||||
return "SELECT COUNT(*) AS total_repos FROM repos"
|
||||
}
|
||||
if q.contains("how many language") {
|
||||
return "SELECT COUNT(DISTINCT language) AS total_languages FROM repos WHERE language IS NOT NULL AND language != ''"
|
||||
}
|
||||
|
||||
// Stars threshold queries
|
||||
if q.contains("100k") || q.contains("100,000") || q.contains("100000") {
|
||||
return """
|
||||
SELECT full_name, stars, language
|
||||
FROM repos WHERE stars > 100000
|
||||
ORDER BY stars DESC
|
||||
"""
|
||||
}
|
||||
|
||||
// Forks
|
||||
if q.contains("most forked") || q.contains("most forks") {
|
||||
return """
|
||||
SELECT full_name, forks, stars, language
|
||||
FROM repos ORDER BY forks DESC LIMIT 10
|
||||
"""
|
||||
}
|
||||
|
||||
// Created / oldest / newest
|
||||
if q.contains("oldest") || q.contains("first") {
|
||||
return """
|
||||
SELECT full_name, created_at, stars, language
|
||||
FROM repos ORDER BY created_at ASC LIMIT 10
|
||||
"""
|
||||
}
|
||||
if q.contains("newest") || q.contains("recent") || q.contains("latest") {
|
||||
return """
|
||||
SELECT full_name, created_at, stars, language
|
||||
FROM repos ORDER BY created_at DESC LIMIT 10
|
||||
"""
|
||||
}
|
||||
|
||||
// Microsoft / Google / Meta specific
|
||||
if q.contains("microsoft") {
|
||||
return """
|
||||
SELECT full_name, stars, forks, language
|
||||
FROM repos WHERE owner = 'microsoft'
|
||||
ORDER BY stars DESC
|
||||
"""
|
||||
}
|
||||
if q.contains("google") {
|
||||
return """
|
||||
SELECT full_name, stars, forks, language
|
||||
FROM repos WHERE owner = 'google'
|
||||
ORDER BY stars DESC
|
||||
"""
|
||||
}
|
||||
if q.contains("facebook") || q.contains("meta") {
|
||||
return """
|
||||
SELECT full_name, stars, forks, language
|
||||
FROM repos WHERE owner = 'facebook'
|
||||
ORDER BY stars DESC
|
||||
"""
|
||||
}
|
||||
|
||||
// Compare
|
||||
if q.contains("vs") || q.contains("versus") || q.contains("compare") {
|
||||
return """
|
||||
SELECT full_name, stars, forks, language
|
||||
FROM repos ORDER BY stars DESC LIMIT 20
|
||||
"""
|
||||
}
|
||||
|
||||
// Default
|
||||
return """
|
||||
SELECT full_name, stars, language
|
||||
FROM repos ORDER BY stars DESC LIMIT 10
|
||||
"""
|
||||
}
|
||||
|
||||
private func extractLastQuestion(from prompt: String) -> String {
|
||||
let lines = prompt.components(separatedBy: "\n")
|
||||
// First pass: find lines ending with "?" (most likely user questions)
|
||||
// Take the LAST one (most recent question)
|
||||
var lastQuestion: String?
|
||||
for line in lines {
|
||||
let trimmed = line.trimmingCharacters(in: .whitespaces)
|
||||
if trimmed.hasSuffix("?") && trimmed.count < 200 && trimmed.count > 5 {
|
||||
lastQuestion = trimmed.lowercased()
|
||||
}
|
||||
}
|
||||
if let q = lastQuestion { return q }
|
||||
|
||||
// Fallback: walk backwards looking for short non-SQL lines
|
||||
for line in lines.reversed() {
|
||||
let trimmed = line.trimmingCharacters(in: .whitespaces)
|
||||
guard !trimmed.isEmpty, trimmed.count > 3, trimmed.count < 100 else { continue }
|
||||
let lower = trimmed.lowercased()
|
||||
if lower.hasPrefix("select ") || lower.hasPrefix("create ") { continue }
|
||||
if lower.contains("integer") || lower.contains("text not") { continue }
|
||||
if lower.contains("respond with only") { continue }
|
||||
return lower
|
||||
}
|
||||
return prompt.lowercased()
|
||||
}
|
||||
|
||||
// MARK: - Summary Generation
|
||||
|
||||
private func deriveSummary(from rawPrompt: String) -> String {
|
||||
let lines = rawPrompt.components(separatedBy: "\n")
|
||||
let dataLines = lines.filter { $0.contains("|") || $0.contains(",") }
|
||||
let rowCount = max(dataLines.count - 1, 0)
|
||||
let lower = rawPrompt.lowercased()
|
||||
|
||||
if lower.contains("total_repos") || lower.contains("total_languages") || lower.contains("count(") {
|
||||
if let countLine = dataLines.last {
|
||||
let num = countLine.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.components(separatedBy: "|").last?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines) ?? "\(rowCount)"
|
||||
return "The count is \(num)."
|
||||
}
|
||||
}
|
||||
if lower.contains("avg_stars") || lower.contains("group by") || lower.contains("total_stars") {
|
||||
return "Here's the breakdown across programming languages."
|
||||
}
|
||||
if lower.contains("forks") && lower.contains("order by forks") {
|
||||
return "These are the most forked repositories on GitHub."
|
||||
}
|
||||
if rowCount == 0 {
|
||||
return "No repositories matched your query."
|
||||
}
|
||||
if rowCount == 1 {
|
||||
return "Here's what I found."
|
||||
}
|
||||
if rowCount <= 5 {
|
||||
return "Found \(rowCount) repositories."
|
||||
}
|
||||
return "Here are the top \(rowCount) repositories."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// OllamaWithSystemPrompt.swift
|
||||
// SwiftDBAIDemo
|
||||
//
|
||||
// Wraps OllamaLanguageModel to prepend session instructions into the user
|
||||
// prompt, working around AnyLanguageModel's Ollama adapter not forwarding
|
||||
// system messages.
|
||||
|
||||
import AnyLanguageModel
|
||||
import Foundation
|
||||
|
||||
/// Wrapper that injects session instructions into every Ollama request.
|
||||
struct OllamaWithSystemPrompt: LanguageModel {
|
||||
typealias UnavailableReason = Never
|
||||
|
||||
private let inner: OllamaLanguageModel
|
||||
|
||||
init(baseURL: URL = OllamaLanguageModel.defaultBaseURL, model: String) {
|
||||
self.inner = OllamaLanguageModel(baseURL: baseURL, model: model)
|
||||
}
|
||||
|
||||
func respond<Content>(
|
||||
within session: LanguageModelSession,
|
||||
to prompt: Prompt,
|
||||
generating type: Content.Type,
|
||||
includeSchemaInPrompt: Bool,
|
||||
options: GenerationOptions
|
||||
) async throws -> LanguageModelSession.Response<Content> where Content: Generable {
|
||||
let userText = prompt.description
|
||||
let instructionText = session.instructions?.description ?? ""
|
||||
|
||||
let combinedText: String
|
||||
if instructionText.isEmpty {
|
||||
combinedText = userText
|
||||
} else {
|
||||
combinedText = """
|
||||
[System Instructions]
|
||||
\(instructionText)
|
||||
|
||||
[User Message]
|
||||
\(userText)
|
||||
"""
|
||||
}
|
||||
|
||||
let plainSession = LanguageModelSession(model: inner)
|
||||
let combinedPrompt = Prompt(combinedText)
|
||||
return try await inner.respond(
|
||||
within: plainSession,
|
||||
to: combinedPrompt,
|
||||
generating: type,
|
||||
includeSchemaInPrompt: includeSchemaInPrompt,
|
||||
options: options
|
||||
)
|
||||
}
|
||||
|
||||
func streamResponse<Content>(
|
||||
within session: LanguageModelSession,
|
||||
to prompt: Prompt,
|
||||
generating type: Content.Type,
|
||||
includeSchemaInPrompt: Bool,
|
||||
options: GenerationOptions
|
||||
) -> sending LanguageModelSession.ResponseStream<Content> where Content: Generable {
|
||||
inner.streamResponse(
|
||||
within: session,
|
||||
to: prompt,
|
||||
generating: type,
|
||||
includeSchemaInPrompt: includeSchemaInPrompt,
|
||||
options: options
|
||||
)
|
||||
}
|
||||
}
|
||||
261
Example/SwiftDBAIDemo/SwiftDBAIDemo/SwiftDBAIDemoApp.swift
Normal file
261
Example/SwiftDBAIDemo/SwiftDBAIDemo/SwiftDBAIDemoApp.swift
Normal file
@@ -0,0 +1,261 @@
|
||||
// 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 "{}"
|
||||
}
|
||||
}
|
||||
BIN
Example/SwiftDBAIDemo/SwiftDBAIDemo/github_stars.sqlite
Normal file
BIN
Example/SwiftDBAIDemo/SwiftDBAIDemo/github_stars.sqlite
Normal file
Binary file not shown.
26
Example/SwiftDBAIDemo/project.yml
Normal file
26
Example/SwiftDBAIDemo/project.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
name: SwiftDBAIDemo
|
||||
options:
|
||||
bundleIdPrefix: com.swiftdbai.demo
|
||||
deploymentTarget:
|
||||
iOS: "17.0"
|
||||
xcodeVersion: "16.0"
|
||||
createIntermediateGroups: true
|
||||
packages:
|
||||
SwiftDBAI:
|
||||
path: ../..
|
||||
targets:
|
||||
SwiftDBAIDemo:
|
||||
type: application
|
||||
platform: iOS
|
||||
sources:
|
||||
- SwiftDBAIDemo
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.swiftdbai.demo
|
||||
MARKETING_VERSION: "1.0"
|
||||
CURRENT_PROJECT_VERSION: "1"
|
||||
SWIFT_VERSION: "6.0"
|
||||
INFOPLIST_GENERATION: true
|
||||
GENERATE_INFOPLIST_FILE: true
|
||||
dependencies:
|
||||
- package: SwiftDBAI
|
||||
Reference in New Issue
Block a user