Skip to content

Commit f5ac71e

Browse files
author
CSL
committed
Release v1.1.3: Fix entitlements, improve cancellation, update version
1 parent 18794f9 commit f5ac71e

4 files changed

Lines changed: 65 additions & 23 deletions

File tree

SeaCrab.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@
262262
CODE_SIGN_ENTITLEMENTS = SeaCrab/SeaCrab.entitlements;
263263
CODE_SIGN_STYLE = Automatic;
264264
COMBINE_HIDPI_IMAGES = YES;
265-
CURRENT_PROJECT_VERSION = 2;
265+
CURRENT_PROJECT_VERSION = 3;
266266
DEVELOPMENT_ASSET_PATHS = "\"SeaCrab/Preview Content\"";
267267
DEVELOPMENT_TEAM = 2795FFTPWT;
268268
ENABLE_HARDENED_RUNTIME = YES;
@@ -273,7 +273,7 @@
273273
"$(inherited)",
274274
"@executable_path/../Frameworks",
275275
);
276-
MARKETING_VERSION = 1.1.2;
276+
MARKETING_VERSION = 1.1.3;
277277
PRODUCT_BUNDLE_IDENTIFIER = com.csl.cool.SeaCrab;
278278
PRODUCT_NAME = "$(TARGET_NAME)";
279279
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -289,7 +289,7 @@
289289
CODE_SIGN_ENTITLEMENTS = SeaCrab/SeaCrab.entitlements;
290290
CODE_SIGN_STYLE = Automatic;
291291
COMBINE_HIDPI_IMAGES = YES;
292-
CURRENT_PROJECT_VERSION = 2;
292+
CURRENT_PROJECT_VERSION = 3;
293293
DEVELOPMENT_ASSET_PATHS = "\"SeaCrab/Preview Content\"";
294294
DEVELOPMENT_TEAM = 2795FFTPWT;
295295
ENABLE_HARDENED_RUNTIME = YES;
@@ -300,7 +300,7 @@
300300
"$(inherited)",
301301
"@executable_path/../Frameworks",
302302
);
303-
MARKETING_VERSION = 1.1.2;
303+
MARKETING_VERSION = 1.1.3;
304304
PRODUCT_BUNDLE_IDENTIFIER = com.csl.cool.SeaCrab;
305305
PRODUCT_NAME = "$(TARGET_NAME)";
306306
SWIFT_EMIT_LOC_STRINGS = YES;

SeaCrab/SeaCrab.entitlements

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<plist version="1.0">
44
<dict>
55
<key>com.apple.security.app-sandbox</key>
6-
<true/>
6+
<false/>
77
<key>com.apple.security.network.client</key>
88
<true/>
99
<key>com.apple.security.network.server</key>

SeaCrab/Services/LLMService.swift

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,20 @@ actor LLMService {
5555
}
5656

5757
// Extract host (domain) from the URL
58-
// The MacPaw/OpenAI SDK expects just the hostname like "api.openai.com"
59-
// not the full URL with scheme
6058
guard let host = baseURL.host else {
6159
throw LLMError.invalidURL(baseURLString)
6260
}
6361

64-
// Construct the host string with optional path
65-
var hostString = host
66-
if baseURL.path != "/" && !baseURL.path.isEmpty {
67-
hostString += baseURL.path
68-
}
62+
let scheme = baseURL.scheme ?? "https"
63+
let port = baseURL.port ?? (scheme == "https" ? 443 : 80)
6964

70-
// Construct the configuration with just the host
65+
// Construct the configuration
7166
let configuration = OpenAI.Configuration(
7267
token: settings.apiKey,
73-
host: hostString
68+
host: host,
69+
port: port,
70+
scheme: scheme,
71+
timeoutInterval: 60.0
7472
)
7573

7674
return OpenAI(configuration: configuration)
@@ -100,6 +98,9 @@ actor LLMService {
10098
}
10199

102100
func refineText(_ text: String, prompt: String) async throws -> String {
101+
// Check for cancellation before starting
102+
try Task.checkCancellation()
103+
103104
let client = try createClient()
104105

105106
guard let systemMessage = ChatQuery.ChatCompletionMessageParam(role: .system, content: prompt),
@@ -115,15 +116,25 @@ actor LLMService {
115116
)
116117

117118
do {
119+
// The library uses URLSession which handles Task cancellation automatically
118120
let result = try await client.chats(query: query)
119121

122+
// Check again after response
123+
try Task.checkCancellation()
124+
120125
guard let content = result.choices.first?.message.content else {
121126
throw LLMError.invalidResponse
122127
}
123128

124129
// Only trim spaces and tabs, but preserve line breaks to maintain text structure
125130
return content.trimmingCharacters(in: .whitespaces)
131+
} catch is CancellationError {
132+
throw CancellationError()
126133
} catch {
134+
// Check if error is actually a cancellation
135+
if (error as NSError).code == NSURLErrorCancelled {
136+
throw CancellationError()
137+
}
127138
throw LLMError.networkError(error)
128139
}
129140
}

SeaCrab/Services/TextRefinementService.swift

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,52 @@ class TextRefinementService: ObservableObject {
1515
private let loadingIndicator = LoadingIndicatorWindow()
1616
@Published var isProcessing = false
1717
@Published var lastError: String?
18+
private var currentTask: Task<Void, Never>?
1819

1920
func refineSelectedText(using card: RefinementCard) {
20-
guard !isProcessing else { return }
21+
// If processing, cancel the current task and STOP
22+
if isProcessing {
23+
currentTask?.cancel()
24+
currentTask = nil
25+
isProcessing = false
26+
loadingIndicator.hide()
27+
print("Refinement task cancelled by user")
28+
return // Do NOT start a new task
29+
}
2130

22-
Task {
31+
currentTask = Task {
2332
isProcessing = true
2433
lastError = nil
2534

2635
// Show loading indicator near cursor
2736
loadingIndicator.show()
2837

2938
do {
39+
// Check for cancellation
40+
try Task.checkCancellation()
41+
3042
// Step 1: Get selected text (prefer Accessibility API, fallback to clipboard)
3143
let textResult = await getSelectedText()
44+
45+
try Task.checkCancellation()
46+
3247
guard let (selectedText, element, hasSelection) = textResult else {
33-
lastError = "No text selected"
34-
loadingIndicator.hide()
35-
isProcessing = false
48+
if !Task.isCancelled {
49+
lastError = "No text selected"
50+
}
51+
// Only hide if we are still the current task (not cancelled by a new one)
52+
if !Task.isCancelled {
53+
loadingIndicator.hide()
54+
isProcessing = false
55+
}
3656
return
3757
}
3858

3959
// Step 2: Send to LLM for refinement using the card's prompt
4060
let refinedText = try await llmService.refineText(selectedText, prompt: card.prompt)
4161

62+
try Task.checkCancellation()
63+
4264
// Step 3: Check if text has actually changed
4365
if refinedText == selectedText {
4466
// Text unchanged - cancel any selection to avoid leaving highlight
@@ -62,13 +84,22 @@ class TextRefinementService: ObservableObject {
6284
}
6385
}
6486

87+
} catch is CancellationError {
88+
// Task was cancelled - clean up silently
89+
print("Refinement task cancelled")
6590
} catch {
66-
lastError = error.localizedDescription
91+
// Only show error if not cancelled
92+
if !Task.isCancelled {
93+
lastError = error.localizedDescription
94+
}
6795
}
6896

69-
// Hide loading indicator
70-
loadingIndicator.hide()
71-
isProcessing = false
97+
// Hide loading indicator only if we are not cancelled (if cancelled, the new task handles UI)
98+
if !Task.isCancelled {
99+
loadingIndicator.hide()
100+
isProcessing = false
101+
currentTask = nil
102+
}
72103
}
73104
}
74105

0 commit comments

Comments
 (0)