|
| 1 | +import CopilotModel |
| 2 | +import CopilotService |
| 3 | +import Foundation |
| 4 | +import OpenAIService |
| 5 | + |
| 6 | +final class OpenAIPromptToCodeAPI: PromptToCodeAPI { |
| 7 | + var service: (any ChatGPTServiceType)? |
| 8 | + |
| 9 | + func stopResponding() { |
| 10 | + Task { |
| 11 | + await service?.stopReceivingMessage() |
| 12 | + } |
| 13 | + } |
| 14 | + |
| 15 | + func modifyCode( |
| 16 | + code: String, |
| 17 | + language: CopilotLanguage, |
| 18 | + indentSize: Int, |
| 19 | + usesTabsForIndentation: Bool, |
| 20 | + requirement: String |
| 21 | + ) async throws -> AsyncThrowingStream<(code: String, description: String), Error> { |
| 22 | + let userPreferredLanguage = UserDefaults.shared.value(for: \.chatGPTLanguage) |
| 23 | + let textLanguage = userPreferredLanguage.isEmpty ? "" : "in \(userPreferredLanguage)" |
| 24 | + |
| 25 | + let prompt = { |
| 26 | + let indentRule = usesTabsForIndentation ? "\(indentSize) tabs" : "\(indentSize) spaces" |
| 27 | + if code.isEmpty { |
| 28 | + return """ |
| 29 | + You are a senior programer in writing code in \(language.rawValue). |
| 30 | +
|
| 31 | + Please write a piece of code that meets my requirements. The indentation should be \( |
| 32 | + indentRule |
| 33 | + ). |
| 34 | +
|
| 35 | + Please reply to me start with the code block, followed by a clear and concise description in 1-3 sentences about what you did \( |
| 36 | + textLanguage |
| 37 | + ). |
| 38 | + """ |
| 39 | + } else { |
| 40 | + return """ |
| 41 | + You are a senior programer in writing code in \(language.rawValue). |
| 42 | +
|
| 43 | + Please mutate the following code fragment with my requirements. Keep the original indentation. Do not add comments unless told to. |
| 44 | +
|
| 45 | + Please reply to me start with the code block followed by a clear and concise description about what you did in 1-3 sentences \( |
| 46 | + textLanguage |
| 47 | + ). |
| 48 | +
|
| 49 | + ``` |
| 50 | + \(code) |
| 51 | + ``` |
| 52 | + """ |
| 53 | + } |
| 54 | + }() |
| 55 | + |
| 56 | + let chatGPTService = ChatGPTService(systemPrompt: prompt, temperature: 0.5) |
| 57 | + service = chatGPTService |
| 58 | + let stream = try await chatGPTService.send(content: requirement) |
| 59 | + return .init { continuation in |
| 60 | + Task { |
| 61 | + var content = "" |
| 62 | + do { |
| 63 | + for try await fragment in stream { |
| 64 | + content.append(fragment) |
| 65 | + continuation.yield(extractCodeAndDescription(from: content)) |
| 66 | + } |
| 67 | + continuation.finish() |
| 68 | + } catch { |
| 69 | + continuation.finish(throwing: error) |
| 70 | + } |
| 71 | + } |
| 72 | + } |
| 73 | + } |
| 74 | + |
| 75 | + func extractCodeAndDescription(from content: String) -> (code: String, description: String) { |
| 76 | + func extractCodeFromMarkdown(_ markdown: String) -> (code: String, endIndex: Int)? { |
| 77 | + let codeBlockRegex = try! NSRegularExpression( |
| 78 | + pattern: #"```(?:\w+)?[\n]([\s\S]+?)[\n]```"#, |
| 79 | + options: .dotMatchesLineSeparators |
| 80 | + ) |
| 81 | + let range = NSRange(markdown.startIndex..<markdown.endIndex, in: markdown) |
| 82 | + if let match = codeBlockRegex.firstMatch(in: markdown, options: [], range: range) { |
| 83 | + let codeBlockRange = Range(match.range(at: 1), in: markdown)! |
| 84 | + return (String(markdown[codeBlockRange]), match.range(at: 0).upperBound) |
| 85 | + } |
| 86 | + |
| 87 | + let incompleteCodeBlockRegex = try! NSRegularExpression( |
| 88 | + pattern: #"```(?:\w+)?[\n]([\s\S]+?)$"#, |
| 89 | + options: .dotMatchesLineSeparators |
| 90 | + ) |
| 91 | + let range2 = NSRange(markdown.startIndex..<markdown.endIndex, in: markdown) |
| 92 | + if let match = incompleteCodeBlockRegex.firstMatch( |
| 93 | + in: markdown, |
| 94 | + options: [], |
| 95 | + range: range2 |
| 96 | + ) { |
| 97 | + let codeBlockRange = Range(match.range(at: 1), in: markdown)! |
| 98 | + return (String(markdown[codeBlockRange]), match.range(at: 0).upperBound) |
| 99 | + } |
| 100 | + return nil |
| 101 | + } |
| 102 | + |
| 103 | + guard let (code, endIndex) = extractCodeFromMarkdown(content) else { |
| 104 | + return ("", "") |
| 105 | + } |
| 106 | + |
| 107 | + func extractDescriptionFromMarkdown(_ markdown: String, startIndex: Int) -> String { |
| 108 | + let startIndex = markdown.index(markdown.startIndex, offsetBy: startIndex) |
| 109 | + guard startIndex < markdown.endIndex else { return "" } |
| 110 | + let range = startIndex..<markdown.endIndex |
| 111 | + let description = String(markdown[range]) |
| 112 | + .trimmingCharacters(in: .whitespacesAndNewlines) |
| 113 | + return description |
| 114 | + } |
| 115 | + |
| 116 | + let description = extractDescriptionFromMarkdown(content, startIndex: endIndex) |
| 117 | + |
| 118 | + return (code, description) |
| 119 | + } |
| 120 | +} |
0 commit comments