Skip to content

Commit f51663a

Browse files
committed
Pre-release 0.32.112
1 parent c32cc7c commit f51663a

File tree

16 files changed

+170
-83
lines changed

16 files changed

+170
-83
lines changed

Core/Sources/ChatService/ChatService.swift

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -88,20 +88,8 @@ public final class ChatService: ChatServiceType, ObservableObject {
8888

8989
private func subscribeToWatchedFilesHandler() {
9090
self.watchedFilesHandler.onWatchedFiles.sink(receiveValue: { [weak self] (request, completion) in
91-
guard let self,
92-
request.params!.workspaceUri != "/",
93-
!ProjectContextSkill.isWorkspaceResolved(self.chatTabInfo.workspacePath)
94-
else { return }
95-
96-
ProjectContextSkill.resolveSkill(
97-
request: request,
98-
workspacePath: self.chatTabInfo.workspacePath,
99-
completion: completion
100-
)
101-
102-
/// after sync complete files to CLS, start file watcher
91+
guard let self, request.params!.workspaceUri != "/" else { return }
10392
self.startFileChangeWatcher()
104-
10593
}).store(in: &cancellables)
10694
}
10795

@@ -463,20 +451,17 @@ public final class ChatService: ChatServiceType, ObservableObject {
463451

464452
Task {
465453
// mark running steps to cancelled
466-
if var message = await memory.history.last,
467-
message.role == .assistant {
468-
message.steps = message.steps.map { step in
469-
return .init(
470-
id: step.id,
471-
title: step.title,
472-
description: step.description,
473-
status: step.status == .running ? .cancelled : step.status,
474-
error: step.error
475-
)
476-
}
454+
await mutateHistory({ history in
455+
guard !history.isEmpty,
456+
let lastIndex = history.indices.last,
457+
history[lastIndex].role == .assistant else { return }
477458

478-
await memory.appendMessage(message)
479-
}
459+
for i in 0..<history[lastIndex].steps.count {
460+
if history[lastIndex].steps[i].status == .running {
461+
history[lastIndex].steps[i].status = .cancelled
462+
}
463+
}
464+
})
480465

481466
// The message of progress report could change rapidly
482467
// Directly upsert the last chat message of history here

Core/Sources/ConversationTab/ModelPicker.swift

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,11 @@ extension AppState {
3131
extension CopilotModelManager {
3232
static func getAvailableChatLLMs() -> [LLMModel] {
3333
let LLMs = CopilotModelManager.getAvailableLLMs()
34-
let availableModels = LLMs.filter(
34+
return LLMs.filter(
3535
{ $0.scopes.contains(.chatPanel) }
3636
).map {
3737
LLMModel(modelName: $0.modelName, modelFamily: $0.modelFamily)
3838
}
39-
return availableModels.isEmpty ? [defaultModel] : availableModels
4039
}
4140
}
4241

@@ -50,6 +49,7 @@ struct ModelPicker: View {
5049
@State private var selectedModel = defaultModel.modelName
5150
@State private var isHovered = false
5251
@State private var isPressed = false
52+
static var lastRefreshModelsTime: Date = .init(timeIntervalSince1970: 0)
5353

5454
init() {
5555
self.updateCurrentModel()
@@ -66,15 +66,23 @@ struct ModelPicker: View {
6666
var body: some View {
6767
WithPerceptionTracking {
6868
Menu(selectedModel) {
69-
ForEach(models, id: \.self) { option in
69+
if models.isEmpty {
7070
Button {
71-
selectedModel = option.modelName
72-
AppState.shared.setSelectedModel(option)
71+
// No action needed
7372
} label: {
74-
if selectedModel == option.modelName {
75-
Text("\(option.modelName)")
76-
} else {
77-
Text(" \(option.modelName)")
73+
Text("Loading...")
74+
}
75+
} else {
76+
ForEach(models, id: \.self) { option in
77+
Button {
78+
selectedModel = option.modelName
79+
AppState.shared.setSelectedModel(option)
80+
} label: {
81+
if selectedModel == option.modelName {
82+
Text("\(option.modelName)")
83+
} else {
84+
Text(" \(option.modelName)")
85+
}
7886
}
7987
}
8088
}
@@ -108,6 +116,12 @@ struct ModelPicker: View {
108116

109117
@MainActor
110118
func refreshModels() async {
119+
let now = Date()
120+
if now.timeIntervalSince(Self.lastRefreshModelsTime) < 60 {
121+
return
122+
}
123+
124+
Self.lastRefreshModelsTime = now
111125
let copilotModels = await SharedChatService.shared.copilotModels()
112126
if !copilotModels.isEmpty {
113127
CopilotModelManager.updateLLMs(copilotModels)

Core/Sources/ConversationTab/Views/ConversationProgressStepView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ struct StatusItemView: View {
2929
case .running:
3030
ProgressView()
3131
.controlSize(.small)
32+
.frame(width: 16, height: 16)
3233
.scaleEffect(0.7)
3334
case .completed:
3435
Image(systemName: "checkmark")

Core/Sources/GitHubCopilotViewModel/GitHubCopilotViewModel.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,12 @@ public class GitHubCopilotViewModel: ObservableObject {
155155
waitingForSignIn = false
156156
self.username = username
157157
self.status = status
158+
await Status.shared.updateAuthStatus(.loggedIn, username: username)
159+
broadcastStatusChange()
158160
let models = try? await service.models()
159161
if let models = models, !models.isEmpty {
160162
CopilotModelManager.updateLLMs(models)
161163
}
162-
await Status.shared.updateAuthStatus(.loggedIn, username: username)
163-
broadcastStatusChange()
164164
} catch let error as GitHubCopilotError {
165165
if case .languageServerError(.timeout) = error {
166166
// TODO figure out how to extend the default timeout on a Chime LSP request

Core/Sources/PersistMiddleware/Extensions/ChatMessage+Storage.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,29 @@ extension ChatMessage {
1414
var suggestedTitle: String?
1515
var errorMessage: String?
1616
var steps: [ConversationProgressStep]
17+
18+
// Custom decoder to provide default value for steps
19+
init(from decoder: Decoder) throws {
20+
let container = try decoder.container(keyedBy: CodingKeys.self)
21+
content = try container.decode(String.self, forKey: .content)
22+
rating = try container.decode(ConversationRating.self, forKey: .rating)
23+
references = try container.decode([ConversationReference].self, forKey: .references)
24+
followUp = try container.decodeIfPresent(ConversationFollowUp.self, forKey: .followUp)
25+
suggestedTitle = try container.decodeIfPresent(String.self, forKey: .suggestedTitle)
26+
errorMessage = try container.decodeIfPresent(String.self, forKey: .errorMessage)
27+
steps = try container.decodeIfPresent([ConversationProgressStep].self, forKey: .steps) ?? []
28+
}
29+
30+
// Default memberwise init for encoding
31+
init(content: String, rating: ConversationRating, references: [ConversationReference], followUp: ConversationFollowUp?, suggestedTitle: String?, errorMessage: String?, steps: [ConversationProgressStep]?) {
32+
self.content = content
33+
self.rating = rating
34+
self.references = references
35+
self.followUp = followUp
36+
self.suggestedTitle = suggestedTitle
37+
self.errorMessage = errorMessage
38+
self.steps = steps ?? []
39+
}
1740
}
1841

1942
func toTurnItem() -> TurnItem {

Core/Sources/Service/Service.swift

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -94,24 +94,32 @@ public final class Service {
9494
keyBindingManager.start()
9595

9696
Task {
97-
await XcodeInspector.shared.safe.$activeDocumentURL
98-
.removeDuplicates()
99-
.filter { $0 != .init(fileURLWithPath: "/") }
100-
.compactMap { $0 }
101-
.sink { [weak self] fileURL in
102-
Task {
103-
do {
104-
let _ = try await self?.workspacePool
105-
.fetchOrCreateWorkspaceAndFilespace(fileURL: fileURL)
106-
} catch let error as Workspace.WorkspaceFileError {
107-
Logger.workspacePool
108-
.info(error.localizedDescription)
109-
}
110-
catch {
111-
Logger.workspacePool.error(error)
112-
}
97+
await Publishers.CombineLatest(
98+
XcodeInspector.shared.safe.$activeDocumentURL
99+
.removeDuplicates(),
100+
XcodeInspector.shared.safe.$latestActiveXcode
101+
)
102+
.receive(on: DispatchQueue.main)
103+
.sink { [weak self] documentURL, latestXcode in
104+
Task {
105+
let fileURL = documentURL ?? latestXcode?.realtimeDocumentURL
106+
guard fileURL != nil, fileURL != .init(fileURLWithPath: "/") else {
107+
return
108+
}
109+
do {
110+
let _ = try await self?.workspacePool
111+
.fetchOrCreateWorkspaceAndFilespace(
112+
fileURL: fileURL!
113+
)
114+
} catch let error as Workspace.WorkspaceFileError {
115+
Logger.workspacePool
116+
.info(error.localizedDescription)
117+
}
118+
catch {
119+
Logger.workspacePool.error(error)
113120
}
114-
}.store(in: &cancellable)
121+
}
122+
}.store(in: &cancellable)
115123

116124
// Combine both workspace and auth status changes into a single stream
117125
await Publishers.CombineLatest3(
@@ -202,13 +210,11 @@ extension Service {
202210
let name = self.getDisplayNameOfXcodeWorkspace(url: workspaceURL)
203211
let path = workspaceURL.path
204212

205-
// switch workspace and username
206-
self.guiController.store.send(.switchWorkspace(path: path, name: name, username: username))
207-
213+
// switch workspace and username and wait for it to complete
214+
await self.guiController.store.send(.switchWorkspace(path: path, name: name, username: username)).finish()
208215
// restore if needed
209216
await self.guiController.restore(path: path, name: name, username: username)
210-
211-
// init chat tab if no history tab
212-
self.guiController.store.send(.initWorkspaceChatTabIfNeeded(path: path, username: username))
217+
// init chat tab if no history tab (only after workspace is fully switched and restored)
218+
await self.guiController.store.send(.initWorkspaceChatTabIfNeeded(path: path, username: username)).finish()
213219
}
214220
}

Core/Sources/SuggestionWidget/FeatureReducers/ChatPanelFeature.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,10 +513,13 @@ public struct ChatPanelFeature {
513513
if var existChatWorkspace = state.chatHistory.workspaces[id: chatWorkspace.id] {
514514

515515
if var selectedChatTabInfo = chatWorkspace.tabInfo.first(where: { $0.id == chatWorkspace.selectedTabId }) {
516-
// cancel selectedChatTabInfo in chat workspace
517-
selectedChatTabInfo.isSelected = false
516+
// Keep the selection state when restoring
517+
selectedChatTabInfo.isSelected = true
518518
chatWorkspace.tabInfo[id: selectedChatTabInfo.id] = selectedChatTabInfo
519519

520+
// Update the existing workspace's selected tab to match
521+
existChatWorkspace.selectedTabId = selectedChatTabInfo.id
522+
520523
// merge tab info
521524
existChatWorkspace.tabInfo.append(contentsOf: chatWorkspace.tabInfo)
522525
state.chatHistory.updateHistory(existChatWorkspace)

ExtensionService/AppDelegate.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
180180
.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication,
181181
app.isUserOfService
182182
else { continue }
183-
if NSWorkspace.shared.runningApplications.contains(where: \.isUserOfService) {
184-
continue
183+
184+
// Check if Xcode is running
185+
let isXcodeRunning = NSWorkspace.shared.runningApplications.contains {
186+
$0.bundleIdentifier == "com.apple.dt.Xcode"
187+
}
188+
189+
if !isXcodeRunning {
190+
Logger.client.info("No Xcode instances running, preparing to quit")
191+
quit()
185192
}
186-
quit()
187193
}
188194
}
189195
}

Tool/Sources/ConversationServiceProvider/ConversationServiceProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public struct ConversationProgressStep: Codable, Equatable, Identifiable {
170170
public let id: String
171171
public let title: String
172172
public let description: String?
173-
public let status: StepStatus
173+
public var status: StepStatus
174174
public let error: StepError?
175175

176176
public init(id: String, title: String, description: String?, status: StepStatus, error: StepError?) {
Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,55 @@
11
import JSONRPC
22
import Combine
3+
import Workspace
4+
import XcodeInspector
5+
import Foundation
36

47
public protocol WatchedFilesHandler {
58
var onWatchedFiles: PassthroughSubject<(WatchedFilesRequest, (AnyJSONRPCResponse) -> Void), Never> { get }
6-
func handleWatchedFiles(_ request: WatchedFilesRequest, completion: @escaping (AnyJSONRPCResponse) -> Void)
9+
func handleWatchedFiles(_ request: WatchedFilesRequest, workspaceURL: URL, completion: @escaping (AnyJSONRPCResponse) -> Void, service: GitHubCopilotService?)
710
}
811

912
public final class WatchedFilesHandlerImpl: WatchedFilesHandler {
1013
public static let shared = WatchedFilesHandlerImpl()
1114

1215
public let onWatchedFiles: PassthroughSubject<(WatchedFilesRequest, (AnyJSONRPCResponse) -> Void), Never> = .init()
16+
17+
public func handleWatchedFiles(_ request: WatchedFilesRequest, workspaceURL: URL, completion: @escaping (AnyJSONRPCResponse) -> Void, service: GitHubCopilotService?) {
18+
guard let params = request.params, params.workspaceUri != "/" else { return }
1319

14-
public func handleWatchedFiles(_ request: WatchedFilesRequest, completion: @escaping (AnyJSONRPCResponse) -> Void) {
20+
let projectURL = WorkspaceXcodeWindowInspector.extractProjectURL(workspaceURL: workspaceURL, documentURL: nil) ?? workspaceURL
21+
22+
let files = WorkspaceFile.getWatchedFiles(
23+
workspaceURL: workspaceURL,
24+
projectURL: projectURL,
25+
excludeGitIgnoredFiles: params.excludeGitignoredFiles,
26+
excludeIDEIgnoredFiles: params.excludeIDEIgnoredFiles
27+
)
28+
29+
let batchSize = BatchingFileChangeWatcher.maxEventPublishSize
30+
/// only `batchSize`(100) files to complete this event for setup watching workspace in CLS side
31+
let jsonResult: JSONValue = .array(files.prefix(batchSize).map { .string($0) })
32+
let jsonValue: JSONValue = .hash(["files": jsonResult])
33+
34+
completion(AnyJSONRPCResponse(id: request.id, result: jsonValue))
35+
36+
Task {
37+
if files.count > batchSize {
38+
for startIndex in stride(from: batchSize, to: files.count, by: batchSize) {
39+
let endIndex = min(startIndex + batchSize, files.count)
40+
let batch = Array(files[startIndex..<endIndex])
41+
try? await service?.notifyDidChangeWatchedFiles(.init(
42+
workspaceUri: params.workspaceUri,
43+
changes: batch.map { .init(uri: $0, type: .created)}
44+
))
45+
46+
try? await Task.sleep(nanoseconds: 100_000_000) // 100ms
47+
}
48+
}
49+
}
50+
51+
/// publish event for watching workspace file changes
1552
onWatchedFiles.send((request, completion))
1653
}
1754
}
55+

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy