Skip to content
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

GitHub Copilot chat proof of concept #550

Merged
merged 12 commits into from
Jun 30, 2024
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ChatBasic
import Foundation
import LangChain
import OpenAIService
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import BingSearchService
import ChatBasic
import Foundation
import OpenAIService
import Preferences
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ChatBasic
import ChatContextCollector
import Foundation
import OpenAIService
Expand Down
43 changes: 25 additions & 18 deletions Core/Sources/ChatGPTChatTab/ChatContextMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,19 @@ struct ChatContextMenu: View {

@ViewBuilder
var chatModel: some View {
let allModels = chatModels + [.init(
id: "com.github.copilot",
name: "GitHub Copilot (poc)",
format: .openAI,
info: .init()
)]

Menu("Chat Model") {
Button(action: {
store.send(.chatModelIdOverrideSelected(nil))
}) {
HStack {
if let defaultModel = chatModels
if let defaultModel = allModels
.first(where: { $0.id == defaultChatModelId })
{
Text("Default (\(defaultModel.name))")
Expand All @@ -88,7 +95,7 @@ struct ChatContextMenu: View {
}
}

if let id = store.chatModelIdOverride, !chatModels.map(\.id).contains(id) {
if let id = store.chatModelIdOverride, !allModels.map(\.id).contains(id) {
Button(action: {
store.send(.chatModelIdOverrideSelected(nil))
}) {
Expand All @@ -101,7 +108,7 @@ struct ChatContextMenu: View {

Divider()

ForEach(chatModels, id: \.id) { model in
ForEach(allModels, id: \.id) { model in
Button(action: {
store.send(.chatModelIdOverrideSelected(model.id))
}) {
Expand Down Expand Up @@ -152,26 +159,26 @@ struct ChatContextMenu: View {
@ViewBuilder
var defaultScopes: some View {
Menu("Default Scopes") {
Button(action: {
store.send(.resetDefaultScopesButtonTapped)
}) {
Text("Reset Default Scopes")
}

Divider()

ForEach(ChatService.Scope.allCases, id: \.rawValue) { value in
Button(action: {
store.send(.resetDefaultScopesButtonTapped)
store.send(.toggleScope(value))
}) {
Text("Reset Default Scopes")
}

Divider()

ForEach(ChatService.Scope.allCases, id: \.rawValue) { value in
Button(action: {
store.send(.toggleScope(value))
}) {
HStack {
Text("@" + value.rawValue)
if store.defaultScopes.contains(value) {
Image(systemName: "checkmark")
}
HStack {
Text("@" + value.rawValue)
if store.defaultScopes.contains(value) {
Image(systemName: "checkmark")
}
}
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions Core/Sources/ChatService/ChatFunctionProvider.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ChatBasic
import Foundation
import OpenAIService

Expand Down
18 changes: 10 additions & 8 deletions Core/Sources/HostApp/AccountSettings/GitHubCopilotView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ struct GitHubCopilotView: View {
@AppStorage(\.gitHubCopilotProxyPassword) var gitHubCopilotProxyPassword
@AppStorage(\.gitHubCopilotUseStrictSSL) var gitHubCopilotUseStrictSSL
@AppStorage(\.gitHubCopilotEnterpriseURI) var gitHubCopilotEnterpriseURI
@AppStorage(\.gitHubCopilotPretendIDEToBeVSCode) var pretendIDEToBeVSCode
@AppStorage(\.disableGitHubCopilotSettingsAutoRefreshOnAppear)
var disableGitHubCopilotSettingsAutoRefreshOnAppear
@AppStorage(\.gitHubCopilotLoadKeyChainCertificates)
Expand Down Expand Up @@ -263,15 +264,16 @@ struct GitHubCopilotView: View {
.opacity(isRunningAction ? 0.8 : 1)
.disabled(isRunningAction)

Button("Refresh Configuration for Enterprise and Proxy") {
Button("Refresh configurations") {
refreshConfiguration()
}
}

SettingsDivider("Advanced")

Form {
Toggle("Verbose Log", isOn: $settings.gitHubCopilotVerboseLog)
Toggle("Verbose log", isOn: $settings.gitHubCopilotVerboseLog)
Toggle("Pretend IDE to be VSCode", isOn: $settings.pretendIDEToBeVSCode)
}

SettingsDivider("Enterprise")
Expand All @@ -281,7 +283,7 @@ struct GitHubCopilotView: View {
text: $settings.gitHubCopilotEnterpriseURI,
prompt: Text("Leave it blank if non is available.")
) {
Text("Auth Provider URL")
Text("Auth provider URL")
}
}

Expand All @@ -292,18 +294,18 @@ struct GitHubCopilotView: View {
text: $settings.gitHubCopilotProxyHost,
prompt: Text("xxx.xxx.xxx.xxx, leave it blank to disable proxy.")
) {
Text("Proxy Host")
Text("Proxy host")
}
TextField(text: $settings.gitHubCopilotProxyPort, prompt: Text("80")) {
Text("Proxy Port")
Text("Proxy port")
}
TextField(text: $settings.gitHubCopilotProxyUsername) {
Text("Proxy Username")
Text("Proxy username")
}
SecureField(text: $settings.gitHubCopilotProxyPassword) {
Text("Proxy Password")
Text("Proxy password")
}
Toggle("Proxy Strict SSL", isOn: $settings.gitHubCopilotUseStrictSSL)
Toggle("Proxy strict SSL", isOn: $settings.gitHubCopilotUseStrictSSL)
}
}
Spacer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,21 @@ struct ChatSettingsGeneralSectionView: View {
"Chat model",
selection: $settings.defaultChatFeatureChatModelId
) {
if !settings.chatModels
.contains(where: { $0.id == settings.defaultChatFeatureChatModelId })
{
let allModels = settings.chatModels + [.init(
id: "com.github.copilot",
name: "GitHub Copilot (poc)",
format: .openAI,
info: .init()
)]

if !allModels.contains(where: { $0.id == settings.defaultChatFeatureChatModelId }) {
Text(
(settings.chatModels.first?.name).map { "\($0) (Default)" }
?? "No model found"
(allModels.first?.name).map { "\($0) (Default)" } ?? "No model found"
)
.tag(settings.defaultChatFeatureChatModelId)
}

ForEach(settings.chatModels, id: \.id) { chatModel in
ForEach(allModels, id: \.id) { chatModel in
Text(chatModel.name).tag(chatModel.id)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Pro
Submodule Pro updated from 5e667a to f1d396
19 changes: 19 additions & 0 deletions Tool/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ let package = Package(
name: "SuggestionModelTests",
dependencies: ["SuggestionModel"]
),

.target(
name: "ChatBasic",
dependencies: [
"AIModel",
"Preferences",
"Keychain",
.product(name: "CodableWrappers", package: "CodableWrappers"),
]
),

.target(name: "AXExtension"),

Expand Down Expand Up @@ -205,8 +215,11 @@ let package = Package(
name: "BuiltinExtension",
dependencies: [
"SuggestionModel",
"SuggestionProvider",
"ChatBasic",
"Workspace",
"ChatTab",
"AIModel",
.product(name: "CopilotForXcodeKit", package: "CopilotForXcodeKit"),
]
),
Expand Down Expand Up @@ -288,6 +301,7 @@ let package = Package(
"OpenAIService",
"ObjectiveCExceptionHandling",
"USearchIndex",
"ChatBasic",
.product(name: "Parsing", package: "swift-parsing"),
.product(name: "SwiftSoup", package: "SwiftSoup"),
]
Expand All @@ -312,6 +326,7 @@ let package = Package(
dependencies: [
"LanguageClient",
"SuggestionModel",
"ChatBasic",
"Logger",
"Preferences",
"Terminal",
Expand Down Expand Up @@ -354,6 +369,8 @@ let package = Package(
"Preferences",
"TokenEncoder",
"Keychain",
"BuiltinExtension",
"ChatBasic",
.product(name: "JSONRPC", package: "JSONRPC"),
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
.product(name: "GoogleGenerativeAI", package: "generative-ai-swift"),
Expand All @@ -367,6 +384,7 @@ let package = Package(
name: "OpenAIServiceTests",
dependencies: [
"OpenAIService",
"ChatBasic",
.product(
name: "ComposableArchitecture",
package: "swift-composable-architecture"
Expand All @@ -390,6 +408,7 @@ let package = Package(
name: "ChatContextCollector",
dependencies: [
"SuggestionModel",
"ChatBasic",
"OpenAIService",
]
),
Expand Down
41 changes: 41 additions & 0 deletions Tool/Sources/BuiltinExtension/BuiltinExtension.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ChatBasic
import ChatTab
import CopilotForXcodeKit
import Foundation
Expand All @@ -6,6 +7,8 @@ import Preferences
public protocol BuiltinExtension: CopilotForXcodeExtensionCapability {
/// An id that let the extension manager determine whether the extension is in use.
var suggestionServiceId: BuiltInSuggestionFeatureProvider { get }
/// An identifier for the extension.
var extensionIdentifier: String { get }

/// All chat builders provided by this extension.
var chatTabTypes: [any ChatTab.Type] { get }
Expand All @@ -21,3 +24,41 @@ public extension BuiltinExtension {
var chatTabTypes: [any ChatTab.Type] { [] }
}

// MAKR: - ChatService

/// A temporary protocol for ChatServiceType. Migrate it to CopilotForXcodeKit when finished.
public protocol BuiltinExtensionChatServiceType: ChatServiceType {
typealias Message = ChatMessage

func sendMessage(
_ message: String,
history: [Message],
references: [RetrievedContent],
workspace: WorkspaceInfo
) async -> AsyncThrowingStream<String, Error>
}

public struct RetrievedContent {
public var document: ChatMessage.Reference
public var priority: Int

public init(document: ChatMessage.Reference, priority: Int) {
self.document = document
self.priority = priority
}
}

public enum ChatServiceMemoryMutation: Codable {
public typealias Message = ChatMessage

/// Add a new message to the end of memory.
/// If an id is not provided, a new id will be generated.
/// If an id is provided, and a message with the same id exists the message with the same
/// id will be updated.
case appendMessage(id: String?, role: Message.Role, text: String)
/// Update the message with the given id.
case updateMessage(id: String, role: Message.Role, text: String)
/// Stream the content into a message with the given id.
case streamIntoMessage(id: String, role: Message.Role?, text: String?)
}

Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,12 @@ public extension ChatGPTArgumentsCollectingFunction {
}
}

struct ChatGPTFunctionSchema: Codable, Equatable {
var name: String
var description: String
var parameters: JSONSchemaValue
public struct ChatGPTFunctionSchema: Codable, Equatable {
public var name: String
public var description: String
public var parameters: JSONSchemaValue

init(name: String, description: String, parameters: JSONSchemaValue) {
public init(name: String, description: String, parameters: JSONSchemaValue) {
self.name = name
self.description = description
self.parameters = parameters
Expand Down
Loading