Skip to content

Commit

Permalink
Pre-release 0.31.105
Browse files Browse the repository at this point in the history
  • Loading branch information
actions-user committed Mar 6, 2025
1 parent 090e1e6 commit b9029ca
Show file tree
Hide file tree
Showing 49 changed files with 1,285 additions and 362 deletions.
3 changes: 2 additions & 1 deletion Core/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ let package = Package(
.product(name: "Cache", package: "Tool"),
.product(name: "MarkdownUI", package: "swift-markdown-ui"),
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "SwiftUIFlowLayout", package: "swiftui-flow-layout")
.product(name: "SwiftUIFlowLayout", package: "swiftui-flow-layout"),
.product(name: "Persist", package: "Tool")
]
),
.testTarget(
Expand Down
22 changes: 19 additions & 3 deletions Core/Sources/ChatService/ChatService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Status

public protocol ChatServiceType {
var memory: ContextAwareAutoManagedChatMemory { get set }
func send(_ id: String, content: String, skillSet: [ConversationSkill], references: [FileReference]) async throws
func send(_ id: String, content: String, skillSet: [ConversationSkill], references: [FileReference], model: String?) async throws
func stopReceivingMessage() async
func upvote(_ id: String, _ rating: ConversationRating) async
func downvote(_ id: String, _ rating: ConversationRating) async
Expand Down Expand Up @@ -82,7 +82,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
return ChatService(provider: provider)
}

public func send(_ id: String, content: String, skillSet: Array<ConversationSkill>, references: Array<FileReference>) async throws {
public func send(_ id: String, content: String, skillSet: Array<ConversationSkill>, references: Array<FileReference>, model: String? = nil) async throws {
guard activeRequestId == nil else { return }
let workDoneToken = UUID().uuidString
activeRequestId = workDoneToken
Expand Down Expand Up @@ -115,7 +115,8 @@ public final class ChatService: ChatServiceType, ObservableObject {
workspaceFolder: "",
skills: skillCapabilities,
ignoredSkills: ignoredSkills,
references: references)
references: references,
model: model)
self.skillSet = skillSet
try await send(request)
}
Expand Down Expand Up @@ -258,6 +259,11 @@ public final class ChatService: ChatServiceType, ObservableObject {
return nil
}

public func copilotModels() async -> [CopilotModel] {
guard let models = try? await conversationProvider?.models() else { return [] }
return models
}

public func handleSingleRoundDialogCommand(
systemPrompt: String?,
overwriteSystemPrompt: Bool,
Expand Down Expand Up @@ -334,6 +340,16 @@ public final class ChatService: ChatServiceType, ObservableObject {
await memory.removeMessage(progress.turnId)
await memory.appendMessage(errorMessage)
}
} else if CLSError.code == 400 && CLSError.message.contains("model is not supported") {
Task {
let errorMessage = ChatMessage(
id: progress.turnId,
role: .assistant,
content: "",
errorMessage: "Oops, the model is not supported. Please enable it first in [GitHub Copilot settings](https://github.com/settings/copilot)."
)
await memory.appendMessage(errorMessage)
}
} else {
Task {
let errorMessage = ChatMessage(
Expand Down
8 changes: 5 additions & 3 deletions Core/Sources/ConversationTab/Chat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ChatAPIService
import Preferences
import Terminal
import ConversationServiceProvider
import Persist

public struct DisplayedChatMessage: Equatable {
public enum Role: Equatable {
Expand Down Expand Up @@ -140,19 +141,20 @@ struct Chat {
state.typedMessage = ""

let selectedFiles = state.selectedFiles

let selectedModelFamily = AppState.shared.getSelectedModelFamily()
return .run { _ in
try await service.send(id, content: message, skillSet: skillSet, references: selectedFiles)
try await service.send(id, content: message, skillSet: skillSet, references: selectedFiles, model: selectedModelFamily)
}.cancellable(id: CancelID.sendMessage(self.id))

case let .followUpButtonClicked(id, message):
guard !message.isEmpty else { return .none }
let skillSet = state.buildSkillSet()

let selectedFiles = state.selectedFiles
let selectedModelFamily = AppState.shared.getSelectedModelFamily()

return .run { _ in
try await service.send(id, content: message, skillSet: skillSet, references: selectedFiles)
try await service.send(id, content: message, skillSet: skillSet, references: selectedFiles, model: selectedModelFamily)
}.cancellable(id: CancelID.sendMessage(self.id))

case .returnButtonTapped:
Expand Down
5 changes: 3 additions & 2 deletions Core/Sources/ConversationTab/ChatPanel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public struct ChatPanel: View {
} else {
ChatPanelMessages(chat: chat)
.accessibilityElement(children: .combine)
.accessibilityLabel("Chat Mesessages Group")
.accessibilityLabel("Chat Messages Group")

if chat.history.last?.role == .system {
ChatCLSError(chat: chat).padding(.trailing, 16)
Expand Down Expand Up @@ -567,6 +567,7 @@ struct ChatPanelInputArea: View {

Spacer()

ModelPicker()
Button(action: {
submitChatMessage()
}) {
Expand Down Expand Up @@ -676,7 +677,7 @@ struct ChatPanelInputArea: View {
id: "releaseNotes",
description: "What's New",
shortDescription: "What's New",
scopes: [ChatPromptTemplateScope.chatPanel]
scopes: [PromptTemplateScope.chatPanel]
)

guard !promptTemplates.isEmpty else {
Expand Down
131 changes: 67 additions & 64 deletions Core/Sources/ConversationTab/ChatTemplateDropdownView.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ConversationServiceProvider
import AppKit
import SwiftUI
import ComposableArchitecture

public struct ChatTemplateDropdownView: View {
@Binding var templates: [ChatTemplate]
Expand All @@ -10,76 +11,78 @@ public struct ChatTemplateDropdownView: View {
@State private var localMonitor: Any? = nil

public var body: some View {
VStack(alignment: .leading, spacing: 0) {
ForEach(Array(templates.enumerated()), id: \.element.id) { index, template in
HStack {
Text("/" + template.id)
.hoverPrimaryForeground(isHovered: selectedIndex == index)
Spacer()
Text(template.shortDescription)
.hoverSecondaryForeground(isHovered: selectedIndex == index)
}
.padding(.horizontal, 8)
.padding(.vertical, 6)
.contentShape(Rectangle())
.onTapGesture {
onSelect(template)
}
.hoverBackground(isHovered: selectedIndex == index)
.onHover { isHovered in
if isHovered {
selectedIndex = index
WithPerceptionTracking {
VStack(alignment: .leading, spacing: 0) {
ForEach(Array(templates.enumerated()), id: \.element.id) { index, template in
HStack {
Text("/" + template.id)
.hoverPrimaryForeground(isHovered: selectedIndex == index)
Spacer()
Text(template.shortDescription)
.hoverSecondaryForeground(isHovered: selectedIndex == index)
}
.padding(.horizontal, 8)
.padding(.vertical, 6)
.contentShape(Rectangle())
.onTapGesture {
onSelect(template)
}
.hoverBackground(isHovered: selectedIndex == index)
.onHover { isHovered in
if isHovered {
selectedIndex = index
}
}
}
}
}
.background(
GeometryReader { geometry in
Color.clear
.onAppear { frameHeight = geometry.size.height }
.onChange(of: geometry.size.height) { newHeight in
frameHeight = newHeight
}
.background(
GeometryReader { geometry in
Color.clear
.onAppear { frameHeight = geometry.size.height }
.onChange(of: geometry.size.height) { newHeight in
frameHeight = newHeight
}
}
)
.background(.ultraThickMaterial)
.cornerRadius(6)
.shadow(radius: 2)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(Color(nsColor: .separatorColor), lineWidth: 1)
)
.frame(maxWidth: .infinity)
.offset(y: -1 * frameHeight)
.onChange(of: templates) { _ in
selectedIndex = 0
}
)
.background(.ultraThickMaterial)
.cornerRadius(6)
.shadow(radius: 2)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(Color(nsColor: .separatorColor), lineWidth: 1)
)
.frame(maxWidth: .infinity)
.offset(y: -1 * frameHeight)
.onChange(of: templates) { _ in
selectedIndex = 0
}
.onAppear {
selectedIndex = 0
localMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
switch event.keyCode {
case 126: // Up arrow
moveSelection(up: true)
return nil
case 125: // Down arrow
moveSelection(up: false)
return nil
case 36: // Return key
handleEnter()
return nil
case 48: // Tab key
handleTab()
return nil // not forwarding the Tab Event which will replace the typed message to "\t"
default:
break
.onAppear {
selectedIndex = 0
localMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
switch event.keyCode {
case 126: // Up arrow
moveSelection(up: true)
return nil
case 125: // Down arrow
moveSelection(up: false)
return nil
case 36: // Return key
handleEnter()
return nil
case 48: // Tab key
handleTab()
return nil // not forwarding the Tab Event which will replace the typed message to "\t"
default:
break
}
return event
}
return event
}
}
.onDisappear {
if let monitor = localMonitor {
NSEvent.removeMonitor(monitor)
localMonitor = nil
.onDisappear {
if let monitor = localMonitor {
NSEvent.removeMonitor(monitor)
localMonitor = nil
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Core/Sources/ConversationTab/ContextUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import XcodeInspector
import Foundation
import Logger

public let supportedFileExtensions: Set<String> = ["swift", "m", "mm", "h", "cpp", "c", "js", "py", "rb", "java", "applescript", "scpt", "plist", "entitlements"]
public let supportedFileExtensions: Set<String> = ["swift", "m", "mm", "h", "cpp", "c", "js", "py", "rb", "java", "applescript", "scpt", "plist", "entitlements", "md", "json", "xml", "txt", "yaml", "yml"]
private let skipPatterns: [String] = [
".git",
".svn",
Expand Down
102 changes: 102 additions & 0 deletions Core/Sources/ConversationTab/ModelPicker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import SwiftUI
import ChatService
import Persist
import ComposableArchitecture

public let SELECTED_LLM_KEY = "selectedLLM"

extension AppState {
func getSelectedModelFamily() -> String? {
if let savedModel = get(key: SELECTED_LLM_KEY),
let modelFamily = savedModel["modelFamily"]?.stringValue {
return modelFamily
}
return nil
}

func getSelectedModelName() -> String? {
if let savedModel = get(key: SELECTED_LLM_KEY),
let modelName = savedModel["modelName"]?.stringValue {
return modelName
}
return nil
}

func setSelectedModel(_ model: LLMModel) {
update(key: SELECTED_LLM_KEY, value: model)
}
}

struct LLMModel: Codable, Hashable {
let modelName: String
let modelFamily: String
}

let defaultModel = LLMModel(modelName: "GPT-4o", modelFamily: "gpt-4o")
struct ModelPicker: View {
@State private var selectedModel = defaultModel.modelName
@State private var models: [LLMModel] = [ defaultModel ]
@State private var isHovered = false
@State private var isPressed = false

init() {
self.updateCurrentModel()
}

func updateCurrentModel() {
selectedModel = AppState.shared.getSelectedModelName() ?? defaultModel.modelName
}

var body: some View {
WithPerceptionTracking {
Menu(selectedModel) {
ForEach(models, id: \.self) { option in
Button {
selectedModel = option.modelName
AppState.shared.setSelectedModel(option)
} label: {
if selectedModel == option.modelName {
Text("\(option.modelName)")
} else {
Text(" \(option.modelName)")
}
}
}
}
.menuStyle(BorderlessButtonMenuStyle())
.frame(maxWidth: labelWidth())
.padding(4)
.background(
RoundedRectangle(cornerRadius: 5)
.fill(isHovered ? Color.gray.opacity(0.1) : Color.clear)
)
.onHover { hovering in
isHovered = hovering
}
.onAppear() {
Task {
updateCurrentModel()
self.models = await ChatService.shared.copilotModels().filter(
{ $0.scopes.contains(.chatPanel) }
).map {
LLMModel(modelName: $0.modelName, modelFamily: $0.modelFamily)
}
}
}
.help("Pick Model")
}
}

func labelWidth() -> CGFloat {
let font = NSFont.systemFont(ofSize: NSFont.systemFontSize)
let attributes = [NSAttributedString.Key.font: font]
let width = selectedModel.size(withAttributes: attributes).width
return CGFloat(width + 20)
}
}

struct ModelPicker_Previews: PreviewProvider {
static var previews: some View {
ModelPicker()
}
}
3 changes: 1 addition & 2 deletions Core/Sources/ConversationTab/Views/BotMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,7 @@ struct BotMessage: View {
if errorMessage != nil {
HStack(spacing: 4) {
Image(systemName: "info.circle")
Text(errorMessage!)
.font(.system(size: chatFontSize))
ThemedMarkdownText(text: errorMessage!, chat: chat)
}
}

Expand Down
Loading

0 comments on commit b9029ca

Please sign in to comment.