Skip to content

add a chatbox example #205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

add a chatbox example #205

wants to merge 2 commits into from

Conversation

Haofei
Copy link

@Haofei Haofei commented Jun 29, 2025

Screenshot 2025-06-28 at 9 51 06 PM Screenshot 2025-06-29 at 2 11 04 PM

@stackotter
Copy link
Owner

This looks pretty awesome! Thanks for the PR 🙏

I’m away until the 11th of July. I’ll check out the example for myself (across a few different platforms) and review the code once I get back.

Just from a quick look through the code, I’d suggest using a NavigationSplitView to make the sidebar (so that it’s resizable and adapts to mobile). Also, you can use $viewModel.blah to get a binding to a property of a state variable (instead of manually constructing bindings). I haven’t looked too closely at the rest of the code though cause I’m just on my phone.

Copy link
Owner

@stackotter stackotter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm back from holidays now. I've left quite a few comments throughout the PR, mostly related to things that I believe could be significantly improved with suitable refactoring. Sorry if it's quite a bit; the SwiftCrossUI examples are the first serious SwiftCrossUI app codebases that many developers will see if they start looking into SwiftCrossUI, so I hold them to a pretty high standard. Let me know if any of the comments don't make sense or could do with elaboration!

The main overarching changes that I'd like to see are:

  • the ability to use the app without providing an OpenAI API key (by way of a simple demo mode), and
  • the ability to use the app on non-Apple platforms (which is currently prevented by use of UserDefaults)

Purely out of interest, did you use AI while producing this code?

Comment on lines +3 to +4
// MARK: - Error Types

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the MARKs (just a codebase style preference)

case 401:
return "API Error 401: Invalid API key. Please check your OpenAI API key and make sure it's valid and has sufficient credits."
case 429:
return "API Error 429: Rate limit exceeded. Please wait a moment and try again."
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the error code from the errors with nice human readable errors. E.g. the 401 error should read "Invalid API key...".

Keep default case and the server error case as they are because the error codes might carry more information.

// MARK: - Thread Models

struct ChatThread: Identifiable, Codable {
let id: String
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the id a UUID like you did for ChatMessage.

// MARK: - Thread Message

struct ThreadMessage: Identifiable, Codable {
let id: String
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the id a UUID like you did for ChatMessage.


// MARK: - Thread Message

struct ThreadMessage: Identifiable, Codable {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to a separate file


// MARK: - Chat Header View

struct ChatHeaderView: View {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can move the bodies of these small views into the MainChatView instead by creating computed properties, similar to var body, like so:

@ViewBuilder
var header: some View {
    HStack { ... }
}

Comment on lines +34 to +46
VStack(alignment: .leading, spacing: AppSpacing.xs) {
Text(message.content)
.font(AppFonts.body)
.foregroundColor(AppColors.text)
.padding(AppSpacing.md)
.background(AppColors.surface)
.cornerRadius(AppCornerRadius.large)
.frame(maxWidth: 600, alignment: .leading)

Text(formatTime(message.timestamp))
.font(AppFonts.caption2)
.foregroundColor(AppColors.textSecondary)
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is repeated from the user version above. Extract the logic for converting a message to a view into a seperate function like so:

func renderMessage(_ message: ChatMessage) -> some View {
    VStack(...) { ... }
}

ThreadRowView(
thread: thread,
isSelected: selectedThread?.id == thread.id,
onSelect: { onSelectThread(thread) },
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that you can just do onSelect: onSelectThread to make this a bit cleaner. Same for onDelete.

.fontWeight(.semibold)
.foregroundColor(AppColors.text)

Text("Start a new conversation to\nbegin chatting with AI!")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to avoid manually wrapping the string if possible. Let me know if this has to do with weird line wrapping behaviour or something though.

WindowGroup("ChatBot") {
#hotReloadable {
NavigationSplitView {
// Sidebar content
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove these section comments from this app body. The parts are all relatively self explanatory already imo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants