A production-quality iOS AI chat app with branching sub-thread conversations.
Powered by OpenAI · Your keys · Beautiful SwiftUI.
The defining feature. Fork any message into a sub-thread — the parent conversation context is carried forward automatically. Sub-threads appear as inline cards inside the parent chat. Nesting is unlimited and context builds recursively.
Responses stream token-by-token with a live blinking cursor. Stop generation at any time — the partial response is preserved.
AI responses render with complete markdown support, including syntax-highlighted code blocks.
Save any AI message with a long-press. Each bookmark gets a short auto-generated title for easy identification.
Keep your conversations organised with search, pinning, and inline sub-thread previews.
Your API keys are stored in the iOS Keychain and never leave your device except to reach the AI provider directly.
Switch model mid-conversation from the chat toolbar. Adding a new provider requires only a single file that conforms to the AIProvider protocol — nothing else changes.
Strict one-way dependency flow · Domain has zero framework imports · Providers are plug-and-play
| Use Case | What it does |
|---|---|
| SendMessageUseCase | Sends a user message and handles the streaming AI response. Saves both messages to CoreData via the repository. |
| BuildContextChainUseCase | Recursively walks the thread ancestry to assemble the full message context for the AI request. Handles arbitrary nesting depth and excludes messages after the fork point in parent threads. |
| CreateSubThreadUseCase | Forks a sub-thread from a specific message, linking it to the parent conversation with the fork message ID. Creates the new CDConversation record. |
| BookmarkMessageUseCase | Toggles a bookmark on a message. Auto-generates a short title from the message content. Persists via MessageRepository. |
! operators in production code. All optionals are handled explicitly.
print() in production. Structured logging via Apple's Logger API.
try? swallowing.
LocalizedStringKey.
git clone git@github.com:dsngeu/ThreadAI.git
open ThreadAI.xcodeproj
Select a simulator or device in Xcode and press Run. No package manager, no pod install, no swift package resolve — zero third-party dependencies.
Open the Settings tab in the app. Tap OpenAI API Key, paste your key, and tap Validate & Save. The app makes a live 1-token test before storing the key in Keychain.
Tap the compose button, choose a model, and start chatting. Long-press any AI message to fork a sub-thread or bookmark it.
| Path | What lives here |
|---|---|
| App/ | Entry point (ThreadAIApp.swift) and dependency wiring (AppDependencies.swift) |
| Core/AIHarness/ | AIProvider protocol, OpenAIProvider, AIHarnessService, all AI models |
| Core/Domain/ | Pure Swift entities (Conversation, Message, Bookmark), UseCases, Repository protocols |
| Core/Data/ | CoreData stack, repository implementations, CoreData ↔ Domain mappings, Keychain service |
| Features/Chat/ | Chat UI: ChatView, MessageBubbleView, StreamingBubbleView, ChatInputBar, CodeBlockView, threading sheets |
| Features/ConversationList/ | Conversation list with search, pin, sub-thread preview cards, create sheet |
| Features/Bookmarks/ | Bookmarks tab: list of saved messages with navigation back to source |
| Features/Settings/ | API key entry and validation, app version info |
| Shared/UI/Theme/ | AppColors, AppTypography, AppSpacing — the design token system |
| Shared/UI/Components/ | MarkdownContentView (full markdown parser), CodeBlockView |