Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
18c6109
Merge tag 'change-license' into develop
intitni May 25, 2023
7e6e07b
Implement cancellation in SuggestionService
intitni May 24, 2023
1b751fe
Make request cancellation simpler
intitni May 24, 2023
c439bc7
Discard suggestion when cursor moved to another line
intitni May 24, 2023
5287a50
Fix tests
intitni May 24, 2023
5f80ae9
Make OpenedDocumentPool an actor
intitni May 24, 2023
0e4f7f8
Reuse suggestion if user is typing according to it
intitni May 24, 2023
256032a
Disable request cancellation on mouse clicks
intitni May 24, 2023
d684e32
Update README.md
intitni May 24, 2023
d7d7e3e
Fix typos
intitni May 25, 2023
cd70355
Merge branch 'feature/real-time-suggestion-tweak' into develop
intitni May 25, 2023
f60b595
Tweak workspace cleanup
intitni May 25, 2023
0cfe177
Fix Codeium language server setup
intitni May 25, 2023
281002b
Make workspace expire sooner
intitni May 25, 2023
68627c1
Replace unowned with weak
intitni May 25, 2023
ebd94eb
Support terminating suggestion services on normal exits
intitni May 25, 2023
f28d836
Fix racing
intitni May 25, 2023
7a8b2f5
Support dependency update
intitni May 25, 2023
bd3103c
Bump Codeium to 1.2.25
intitni May 25, 2023
f21722c
Implement CancelRequest for Codeium
intitni May 25, 2023
ee2bbef
Merge branch 'feature/codeium-1.2.25' into develop
intitni May 25, 2023
30204ff
Remove exit button in widget
intitni May 25, 2023
11f9f8d
Update the way we use keychain
intitni May 25, 2023
90bacd9
Update to use SecureField
intitni May 25, 2023
a27f5c7
Allow setting max token that is larger than the size defined in the m…
intitni May 25, 2023
182d426
Change ChatGPTEndpoint to OpenAIBaseURL
intitni May 25, 2023
a707645
Support Azure OpenAI as chat provider
intitni May 25, 2023
16d9cf7
Move settings to chat settings view
intitni May 25, 2023
69d6682
Remove keychain instruction
intitni May 25, 2023
080372b
Make each word from ChatGPT wait for a few milliseconds
intitni May 25, 2023
f691f1a
Merge branch 'feature/azure-openai' into develop
intitni May 25, 2023
42d82cf
Lower CPU usage when updating window position
intitni May 25, 2023
51d5739
Fix dependency
intitni May 25, 2023
c9b3f07
Support disabling selected code to be visible by the chat by default
intitni May 26, 2023
ee9ab39
Update README.md
intitni May 26, 2023
c409699
Merge branch 'feature/chat-scope-selection' into develop
intitni May 26, 2023
e3df057
Set as system prompt
intitni May 26, 2023
076d40d
Make stream faster
intitni May 26, 2023
cbfdc1a
Bump version to 0.17.0
intitni May 26, 2023
04d9b1a
Make fetchFocusedElementURI return fake URL when Xcode not launched
intitni May 26, 2023
bb8cf1f
Adjust steam speed
intitni May 26, 2023
f5ed510
Improve Codeium request cancellation
intitni May 27, 2023
3d0c043
Fix that selection range change could cancel real-time suggestions
intitni May 27, 2023
83289ba
Fix line number font size in code block
intitni May 27, 2023
035b742
Fix tests
intitni May 27, 2023
7237dad
Fix codeium auth key migration
intitni May 27, 2023
ef538ec
Update README.md
intitni May 27, 2023
ab95449
Update appcast.xml
intitni May 27, 2023
2b1a27b
Merge branch 'release/0.17.0'
intitni May 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Support Azure OpenAI as chat provider
  • Loading branch information
intitni committed May 25, 2023
commit a707645d86e91ae6e5b51f14d4695c9ba77be8c6
70 changes: 70 additions & 0 deletions Core/Sources/HostApp/AccountSettings/AzureView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import AppKit
import Client
import OpenAIService
import Preferences
import SuggestionModel
import SwiftUI

final class AzureViewSettings: ObservableObject {
@AppStorage(\.azureOpenAIAPIKey) var azureOpenAIAPIKey: String
@AppStorage(\.azureOpenAIBaseURL) var azureOpenAIBaseURL: String
@AppStorage(\.azureChatGPTDeployment) var azureChatGPTDeployment: String
init() {}
}

struct AzureView: View {
@Environment(\.toast) var toast
@State var isTesting = false
@StateObject var settings = AzureViewSettings()

var body: some View {
Form {
SecureField(text: $settings.azureOpenAIAPIKey, prompt: Text("")) {
Text("OpenAI Service API Key")
}
.textFieldStyle(.roundedBorder)

TextField(
text: $settings.azureOpenAIBaseURL,
prompt: Text("https://XXXXXX.openai.azure.com")
) {
Text("OpenAI Service Base URL")
}.textFieldStyle(.roundedBorder)

HStack {
TextField(
text: $settings.azureChatGPTDeployment,
prompt: Text("")
) {
Text("Chat Model Deployment Name")
}.textFieldStyle(.roundedBorder)

Button("Test") {
Task { @MainActor in
isTesting = true
defer { isTesting = false }
do {
let reply = try await ChatGPTService(designatedProvider: .azureOpenAI)
.sendAndWait(content: "Hello", summary: nil)
toast(Text("ChatGPT replied: \(reply ?? "N/A")"), .info)
} catch {
toast(Text(error.localizedDescription), .error)
}
}
}
.disabled(isTesting)
}
}
}
}

struct AzureView_Previews: PreviewProvider {
static var previews: some View {
VStack(alignment: .leading, spacing: 8) {
AzureView()
}
.frame(height: 800)
.padding(.all, 8)
}
}

2 changes: 1 addition & 1 deletion Core/Sources/HostApp/AccountSettings/OpenAIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ struct OpenAIView: View {
Button("Test") {
Task {
do {
let reply = try await ChatGPTService()
let reply = try await ChatGPTService(designatedProvider: .openAI)
.sendAndWait(content: "Hello", summary: nil)
toast(Text("ChatGPT replied: \(reply ?? "N/A")"), .info)
} catch {
Expand Down
9 changes: 9 additions & 0 deletions Core/Sources/HostApp/ServiceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ struct ServiceView: View {
subtitle: "Chat, Prompt to Code",
image: "globe"
)

ScrollView {
AzureView().padding()
}.sidebarItem(
tag: 3,
title: "Azure",
subtitle: "Chat, Prompt to Code",
image: "globe"
)
}
}
}
Expand Down
48 changes: 40 additions & 8 deletions Core/Sources/OpenAIService/ChatGPTService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ public protocol ChatGPTServiceType: ObservableObject {
public enum ChatGPTServiceError: Error, LocalizedError {
case endpointIncorrect
case responseInvalid
case otherError(String)

public var errorDescription: String? {
switch self {
case .endpointIncorrect:
return "ChatGPT endpoint is incorrect"
case .responseInvalid:
return "Response is invalid"
case let .otherError(content):
return content
}
}
}
Expand Down Expand Up @@ -59,7 +62,7 @@ public actor ChatGPTService: ChatGPTServiceType {
public var defaultTemperature: Double {
min(max(0, UserDefaults.shared.value(for: \.chatGPTTemperature)), 2)
}

var temperature: Double?

public var model: String {
Expand All @@ -68,14 +71,30 @@ public actor ChatGPTService: ChatGPTServiceType {
return value
}

var designatedProvider: ChatFeatureProvider?

public var endpoint: String {
var baseURL = UserDefaults.shared.value(for: \.openAIBaseURL)
if baseURL.isEmpty { return "https://api.openai.com/v1/chat/completions" }
return "\(baseURL)/v1/chat/completions"
switch designatedProvider ?? UserDefaults.shared.value(for: \.chatFeatureProvider) {
case .openAI:
let baseURL = UserDefaults.shared.value(for: \.openAIBaseURL)
if baseURL.isEmpty { return "https://api.openai.com/v1/chat/completions" }
return "\(baseURL)/v1/chat/completions"
case .azureOpenAI:
let baseURL = UserDefaults.shared.value(for: \.azureOpenAIBaseURL)
let deployment = UserDefaults.shared.value(for: \.azureChatGPTDeployment)
let version = "2023-05-15"
if baseURL.isEmpty { return "" }
return "\(baseURL)/openai/deployments/\(deployment)/chat/completions?api-version=\(version)"
}
}

public var apiKey: String {
UserDefaults.shared.value(for: \.openAIAPIKey)
switch designatedProvider ?? UserDefaults.shared.value(for: \.chatFeatureProvider) {
case .openAI:
return UserDefaults.shared.value(for: \.openAIAPIKey)
case .azureOpenAI:
return UserDefaults.shared.value(for: \.azureOpenAIAPIKey)
}
}

public var maxToken: Int {
Expand All @@ -97,10 +116,12 @@ public actor ChatGPTService: ChatGPTServiceType {

public init(
systemPrompt: String = "",
temperature: Double? = nil
temperature: Double? = nil,
designatedProvider: ChatFeatureProvider? = nil
) {
self.systemPrompt = systemPrompt
self.temperature = temperature
self.designatedProvider = designatedProvider
}

public func send(
Expand Down Expand Up @@ -129,7 +150,12 @@ public actor ChatGPTService: ChatGPTServiceType {

isReceivingMessage = true

let api = buildCompletionStreamAPI(apiKey, url, requestBody)
let api = buildCompletionStreamAPI(
apiKey,
designatedProvider ?? UserDefaults.shared.value(for: \.chatFeatureProvider),
url,
requestBody
)

return AsyncThrowingStream<String, Error> { continuation in
Task {
Expand Down Expand Up @@ -210,7 +236,12 @@ public actor ChatGPTService: ChatGPTServiceType {
isReceivingMessage = true
defer { isReceivingMessage = false }

let api = buildCompletionAPI(apiKey, url, requestBody)
let api = buildCompletionAPI(
apiKey,
designatedProvider ?? UserDefaults.shared.value(for: \.chatFeatureProvider),
url,
requestBody
)
let response = try await api()

if let choice = response.choices.first {
Expand Down Expand Up @@ -296,3 +327,4 @@ func maxTokenForReply(model: String, remainingTokens: Int) -> Int {
guard let model = ChatGPTModel(rawValue: model) else { return remainingTokens }
return min(model.maxToken / 2, remainingTokens)
}

32 changes: 24 additions & 8 deletions Core/Sources/OpenAIService/CompletionAPI.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Foundation
import Preferences

typealias CompletionAPIBuilder = (String, URL, CompletionRequestBody) -> CompletionAPI
typealias CompletionAPIBuilder = (String, ChatFeatureProvider, URL, CompletionRequestBody)
-> CompletionAPI

protocol CompletionAPI {
func callAsFunction() async throws -> CompletionResponseBody
Expand All @@ -12,13 +14,13 @@ struct CompletionResponseBody: Codable, Equatable {
var role: ChatMessage.Role
var content: String
}

struct Choice: Codable, Equatable {
var message: Message
var index: Int
var finish_reason: String
}

struct Usage: Codable, Equatable {
var prompt_tokens: Int
var completion_tokens: Int
Expand All @@ -40,21 +42,29 @@ struct CompletionAPIError: Error, Codable, LocalizedError {
var param: String
var code: String
}

var error: E

var errorDescription: String? { error.message }
}

struct OpenAICompletionAPI: CompletionAPI {
var apiKey: String
var endpoint: URL
var requestBody: CompletionRequestBody
var provider: ChatFeatureProvider

init(apiKey: String, endpoint: URL, requestBody: CompletionRequestBody) {
init(
apiKey: String,
provider: ChatFeatureProvider,
endpoint: URL,
requestBody: CompletionRequestBody
) {
self.apiKey = apiKey
self.endpoint = endpoint
self.requestBody = requestBody
self.requestBody.stream = false
self.provider = provider
}

func callAsFunction() async throws -> CompletionResponseBody {
Expand All @@ -64,7 +74,11 @@ struct OpenAICompletionAPI: CompletionAPI {
request.httpBody = try encoder.encode(requestBody)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
if !apiKey.isEmpty {
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
if provider == .openAI {
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
} else {
request.setValue(apiKey, forHTTPHeaderField: "api-key")
}
}

let (result, response) = try await URLSession.shared.data(for: request)
Expand All @@ -74,9 +88,11 @@ struct OpenAICompletionAPI: CompletionAPI {

guard response.statusCode == 200 else {
let error = try? JSONDecoder().decode(CompletionAPIError.self, from: result)
throw error ?? ChatGPTServiceError.responseInvalid
throw error ?? ChatGPTServiceError
.otherError(String(data: result, encoding: .utf8) ?? "Unknown Error")
}

return try JSONDecoder().decode(CompletionResponseBody.self, from: result)
}
}

23 changes: 18 additions & 5 deletions Core/Sources/OpenAIService/CompletionStreamAPI.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import AsyncAlgorithms
import Foundation
import Preferences

typealias CompletionStreamAPIBuilder = (String, URL, CompletionRequestBody) -> CompletionStreamAPI
typealias CompletionStreamAPIBuilder = (String, ChatFeatureProvider, URL, CompletionRequestBody) -> CompletionStreamAPI

protocol CompletionStreamAPI {
func callAsFunction() async throws -> (
Expand Down Expand Up @@ -54,12 +55,19 @@ struct OpenAICompletionStreamAPI: CompletionStreamAPI {
var apiKey: String
var endpoint: URL
var requestBody: CompletionRequestBody
var provider: ChatFeatureProvider

init(apiKey: String, endpoint: URL, requestBody: CompletionRequestBody) {
init(
apiKey: String,
provider: ChatFeatureProvider,
endpoint: URL,
requestBody: CompletionRequestBody
) {
self.apiKey = apiKey
self.endpoint = endpoint
self.requestBody = requestBody
self.requestBody.stream = true
self.provider = provider
}

func callAsFunction() async throws -> (
Expand All @@ -72,7 +80,11 @@ struct OpenAICompletionStreamAPI: CompletionStreamAPI {
request.httpBody = try encoder.encode(requestBody)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
if !apiKey.isEmpty {
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
if provider == .openAI {
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
} else {
request.setValue(apiKey, forHTTPHeaderField: "api-key")
}
}

let (result, response) = try await URLSession.shared.bytes(for: request)
Expand All @@ -90,9 +102,9 @@ struct OpenAICompletionStreamAPI: CompletionStreamAPI {
let error = try? decoder.decode(ChatGPTError.self, from: data)
throw error ?? ChatGPTServiceError.responseInvalid
}

var receivingDataTask: Task<Void, Error>?

let stream = AsyncThrowingStream<CompletionStreamDataTrunk, Error> { continuation in
receivingDataTask = Task {
do {
Expand Down Expand Up @@ -122,3 +134,4 @@ struct OpenAICompletionStreamAPI: CompletionStreamAPI {
)
}
}

4 changes: 4 additions & 0 deletions Core/Sources/Preferences/ChatFeatureProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
public enum ChatFeatureProvider: String, CaseIterable {
case openAI
case azureOpenAI
}
20 changes: 20 additions & 0 deletions Core/Sources/Preferences/Keys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,22 @@ public extension UserDefaultPreferenceKeys {
}
}

// MARK: - Azure OpenAI Settings

public extension UserDefaultPreferenceKeys {
var azureOpenAIAPIKey: PreferenceKey<String> {
.init(defaultValue: "", key: "AzureOpenAIAPIKey")
}

var azureOpenAIBaseURL: PreferenceKey<String> {
.init(defaultValue: "", key: "AzureOpenAIBaseURL")
}

var azureChatGPTDeployment: PreferenceKey<String> {
.init(defaultValue: "", key: "AzureChatGPTDeployment")
}
}

// MARK: - GitHub Copilot Settings

public extension UserDefaultPreferenceKeys {
Expand Down Expand Up @@ -186,6 +202,10 @@ public extension UserDefaultPreferenceKeys {
// MARK: - Chat

public extension UserDefaultPreferenceKeys {
var chatFeatureProvider: PreferenceKey<ChatFeatureProvider> {
.init(defaultValue: .openAI, key: "ChatFeatureProvider")
}

var chatFontSize: PreferenceKey<Double> {
.init(defaultValue: 12, key: "ChatFontSize")
}
Expand Down