3 Commits

2 changed files with 16 additions and 111 deletions

View File

@@ -1,5 +1,8 @@
update_settings(max_parallel_updates=4) update_settings(max_parallel_updates=4)
load('ext://dotenv', 'dotenv')
dotenv() # defaults to .env in the current directory
local_resource( local_resource(
"server", "server",
cmd="npm ci --no-audit --no-fund", cmd="npm ci --no-audit --no-fund",

View File

@@ -1,6 +1,5 @@
import Observation import Observation
import SwiftUI import SwiftUI
import UIKit
struct SybilWorkspaceView: View { struct SybilWorkspaceView: View {
@Bindable var viewModel: SybilViewModel @Bindable var viewModel: SybilViewModel
@@ -75,11 +74,6 @@ struct SybilWorkspaceView: View {
} }
.background(SybilTheme.background) .background(SybilTheme.background)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.onChange(of: viewModel.isSending) { _, isSending in
if !isSending, viewModel.showsComposer {
composerFocused = true
}
}
} }
private var header: some View { private var header: some View {
@@ -217,23 +211,19 @@ struct SybilWorkspaceView: View {
private var composerBar: some View { private var composerBar: some View {
HStack(alignment: .bottom, spacing: 10) { HStack(alignment: .bottom, spacing: 10) {
ZStack(alignment: .topLeading) { TextField(
ComposerTextView( viewModel.isSearchMode ? "Search the web" : "Message Sybil",
text: $viewModel.composer, text: $viewModel.composer,
isFocused: $composerFocused, axis: .vertical
isDisabled: viewModel.isSending )
) { .focused($composerFocused)
Task { .textInputAutocapitalization(.sentences)
await viewModel.sendComposer() .autocorrectionDisabled(false)
} .lineLimit(1 ... 6)
} .submitLabel(.send)
.frame(minHeight: 24, maxHeight: 132) .onSubmit {
Task {
if viewModel.composer.isEmpty { await viewModel.sendComposer()
Text(viewModel.isSearchMode ? "Search the web" : "Message Sybil")
.font(.body)
.foregroundStyle(SybilTheme.textMuted.opacity(0.72))
.allowsHitTesting(false)
} }
} }
.padding(.horizontal, 12) .padding(.horizontal, 12)
@@ -283,91 +273,3 @@ struct SybilWorkspaceView: View {
) )
} }
} }
private struct ComposerTextView: UIViewRepresentable {
@Binding var text: String
var isFocused: FocusState<Bool>.Binding
var isDisabled: Bool
var onSubmit: () -> Void
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.backgroundColor = .clear
textView.font = .preferredFont(forTextStyle: .body)
textView.textColor = UIColor(SybilTheme.text)
textView.tintColor = UIColor(SybilTheme.primary)
textView.textContainerInset = .zero
textView.textContainer.lineFragmentPadding = 0
textView.isScrollEnabled = true
textView.alwaysBounceVertical = false
textView.keyboardDismissMode = .interactive
textView.returnKeyType = .send
textView.enablesReturnKeyAutomatically = true
textView.autocapitalizationType = .sentences
textView.autocorrectionType = .default
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return textView
}
func updateUIView(_ textView: UITextView, context: Context) {
context.coordinator.parent = self
if textView.text != text {
textView.text = text
}
textView.isEditable = !isDisabled
textView.alpha = isDisabled ? 0.74 : 1
if isFocused.wrappedValue, !textView.isFirstResponder {
textView.becomeFirstResponder()
} else if !isFocused.wrappedValue, textView.isFirstResponder {
textView.resignFirstResponder()
}
}
func sizeThatFits(_ proposal: ProposedViewSize, uiView: UITextView, context: Context) -> CGSize? {
let width = proposal.width ?? uiView.bounds.width
let fittingSize = uiView.sizeThatFits(
CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
)
return CGSize(width: width, height: min(max(fittingSize.height, 24), 132))
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
final class Coordinator: NSObject, UITextViewDelegate {
var parent: ComposerTextView
init(parent: ComposerTextView) {
self.parent = parent
}
func textViewDidChange(_ textView: UITextView) {
parent.text = textView.text
}
func textViewDidBeginEditing(_ textView: UITextView) {
parent.isFocused.wrappedValue = true
}
func textViewDidEndEditing(_ textView: UITextView) {
parent.isFocused.wrappedValue = false
}
func textView(
_ textView: UITextView,
shouldChangeTextIn range: NSRange,
replacementText replacement: String
) -> Bool {
if replacement == "\n" {
parent.onSubmit()
return false
}
return true
}
}
}