Skip to content

Commit bd805e4

Browse files
committed
Add ChatPluginController
1 parent caf816b commit bd805e4

File tree

2 files changed

+134
-100
lines changed

2 files changed

+134
-100
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import ChatPlugins
2+
import Combine
3+
import Foundation
4+
import OpenAIService
5+
6+
final class ChatPluginController {
7+
let chatGPTService: any ChatGPTServiceType
8+
let plugins: [String: ChatPlugin.Type]
9+
var runningPlugin: ChatPlugin?
10+
11+
init(chatGPTService: any ChatGPTServiceType, plugins: ChatPlugin.Type...) {
12+
self.chatGPTService = chatGPTService
13+
var all = [String: ChatPlugin.Type]()
14+
for plugin in plugins {
15+
all[plugin.command] = plugin
16+
}
17+
self.plugins = all
18+
}
19+
20+
func handleContent(_ content: String) async throws -> Bool {
21+
// look for the prefix of content, see if there is something like /command.
22+
// If there is, then we need to find the plugin that can handle this command.
23+
// If there is no such plugin, then we just send the message to the GPT service.
24+
let regex = try NSRegularExpression(pattern: #"^\/([a-zA-Z0-9]+)"#)
25+
let matches = regex.matches(in: content, range: NSRange(content.startIndex..., in: content))
26+
if let match = matches.first {
27+
let command = String(content[Range(match.range(at: 1), in: content)!])
28+
// handle exit plugin
29+
if command == "exit" {
30+
if let plugin = runningPlugin {
31+
runningPlugin = nil
32+
_ = await chatGPTService.mutateHistory { history in
33+
history.append(.init(
34+
role: .user,
35+
content: "",
36+
summary: "Exit plugin \(plugin.name)."
37+
))
38+
history.append(.init(
39+
role: .system,
40+
content: "",
41+
summary: "Exited plugin \(plugin.name)."
42+
))
43+
}
44+
} else {
45+
_ = await chatGPTService.mutateHistory { history in
46+
history.append(.init(
47+
role: .system,
48+
content: "",
49+
summary: "No plugin running."
50+
))
51+
}
52+
}
53+
return true
54+
}
55+
56+
// pass message to running plugin
57+
if let runningPlugin {
58+
await runningPlugin.send(content: content, originalMessage: content)
59+
return true
60+
}
61+
62+
// pass message to new plugin
63+
if let pluginType = plugins[command] {
64+
let plugin = pluginType.init(inside: chatGPTService, delegate: self)
65+
if #available(macOS 13.0, *) {
66+
await plugin.send(
67+
content: String(
68+
content.dropFirst(command.count + 1)
69+
.trimmingPrefix(while: { $0 == " " })
70+
),
71+
originalMessage: content
72+
)
73+
} else {
74+
await plugin.send(
75+
content: String(content.dropFirst(command.count + 1)),
76+
originalMessage: content
77+
)
78+
}
79+
return true
80+
}
81+
82+
return false
83+
} else if let runningPlugin {
84+
// pass message to running plugin
85+
await runningPlugin.send(content: content, originalMessage: content)
86+
return true
87+
} else {
88+
return false
89+
}
90+
}
91+
}
92+
93+
extension ChatPluginController: ChatPluginDelegate {
94+
public func pluginDidStartResponding(_: ChatPlugins.ChatPlugin) {
95+
Task {
96+
await chatGPTService.markReceivingMessage(true)
97+
}
98+
}
99+
100+
public func pluginDidEndResponding(_: ChatPlugins.ChatPlugin) {
101+
Task {
102+
await chatGPTService.markReceivingMessage(false)
103+
}
104+
}
105+
106+
public func pluginDidStart(_ plugin: ChatPlugin) {
107+
runningPlugin = plugin
108+
}
109+
110+
public func pluginDidEnd(_ plugin: ChatPlugin) {
111+
if runningPlugin === plugin {
112+
runningPlugin = nil
113+
}
114+
}
115+
116+
public func shouldStartAnotherPlugin(_ type: ChatPlugin.Type, withContent content: String) {
117+
let plugin = type.init(inside: chatGPTService, delegate: self)
118+
Task {
119+
await plugin.send(content: content, originalMessage: content)
120+
}
121+
}
122+
}
123+

Core/Sources/ChatService/ChatService.swift

Lines changed: 11 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -5,79 +5,29 @@ import OpenAIService
55

66
public final class ChatService: ObservableObject {
77
public let chatGPTService: any ChatGPTServiceType
8-
let plugins = registerPlugins(
9-
TerminalChatPlugin.self,
10-
AITerminalChatPlugin.self
11-
)
8+
let pluginController: ChatPluginController
129
var runningPlugin: ChatPlugin?
1310
var cancellable = Set<AnyCancellable>()
1411

1512
public init<T: ChatGPTServiceType>(chatGPTService: T) {
1613
self.chatGPTService = chatGPTService
14+
pluginController = ChatPluginController(
15+
chatGPTService: chatGPTService,
16+
plugins:
17+
TerminalChatPlugin.self,
18+
AITerminalChatPlugin.self
19+
)
1720

1821
chatGPTService.objectWillChange.sink { [weak self] _ in
1922
self?.objectWillChange.send()
2023
}.store(in: &cancellable)
2124
}
2225

2326
public func send(content: String) async throws {
24-
// look for the prefix of content, see if there is something like /command.
25-
// If there is, then we need to find the plugin that can handle this command.
26-
// If there is no such plugin, then we just send the message to the GPT service.
27-
let regex = try NSRegularExpression(pattern: #"^\/([a-zA-Z0-9]+)"#)
28-
let matches = regex.matches(in: content, range: NSRange(content.startIndex..., in: content))
29-
if let match = matches.first {
30-
let command = String(content[Range(match.range(at: 1), in: content)!])
31-
if command == "exit" {
32-
if let plugin = runningPlugin {
33-
runningPlugin = nil
34-
_ = await chatGPTService.mutateHistory { history in
35-
history.append(.init(
36-
role: .user,
37-
content: "",
38-
summary: "Exit plugin \(plugin.name)."
39-
))
40-
history.append(.init(
41-
role: .system,
42-
content: "",
43-
summary: "Exited plugin \(plugin.name)."
44-
))
45-
}
46-
} else {
47-
_ = await chatGPTService.mutateHistory { history in
48-
history.append(.init(
49-
role: .system,
50-
content: "",
51-
summary: "No plugin running."
52-
))
53-
}
54-
}
55-
} else if let runningPlugin {
56-
await runningPlugin.send(content: content, originalMessage: content)
57-
} else if let pluginType = plugins[command] {
58-
let plugin = pluginType.init(inside: chatGPTService, delegate: self)
59-
if #available(macOS 13.0, *) {
60-
await plugin.send(
61-
content: String(
62-
content.dropFirst(command.count + 1)
63-
.trimmingPrefix(while: { $0 == " " })
64-
),
65-
originalMessage: content
66-
)
67-
} else {
68-
await plugin.send(
69-
content: String(content.dropFirst(command.count + 1)),
70-
originalMessage: content
71-
)
72-
}
73-
} else {
74-
_ = try await chatGPTService.send(content: content, summary: nil)
75-
}
76-
} else if let runningPlugin {
77-
await runningPlugin.send(content: content, originalMessage: content)
78-
} else {
79-
_ = try await chatGPTService.send(content: content, summary: nil)
80-
}
27+
let handledInPlugin = try await pluginController.handleContent(content)
28+
if handledInPlugin { return }
29+
30+
_ = try await chatGPTService.send(content: content, summary: nil)
8131
}
8232

8333
public func stopReceivingMessage() async {
@@ -110,42 +60,3 @@ public final class ChatService: ObservableObject {
11060
await chatGPTService.mutateSystemPrompt(newPrompt)
11161
}
11262
}
113-
114-
extension ChatService: ChatPluginDelegate {
115-
public func pluginDidStartResponding(_: ChatPlugins.ChatPlugin) {
116-
Task {
117-
await chatGPTService.markReceivingMessage(true)
118-
}
119-
}
120-
121-
public func pluginDidEndResponding(_: ChatPlugins.ChatPlugin) {
122-
Task {
123-
await chatGPTService.markReceivingMessage(false)
124-
}
125-
}
126-
127-
public func pluginDidStart(_ plugin: ChatPlugin) {
128-
runningPlugin = plugin
129-
}
130-
131-
public func pluginDidEnd(_ plugin: ChatPlugin) {
132-
if runningPlugin === plugin {
133-
runningPlugin = nil
134-
}
135-
}
136-
137-
public func shouldStartAnotherPlugin(_ type: ChatPlugin.Type, withContent content: String) {
138-
let plugin = type.init(inside: chatGPTService, delegate: self)
139-
Task {
140-
await plugin.send(content: content, originalMessage: content)
141-
}
142-
}
143-
}
144-
145-
func registerPlugins(_ plugins: ChatPlugin.Type...) -> [String: ChatPlugin.Type] {
146-
var all = [String: ChatPlugin.Type]()
147-
for plugin in plugins {
148-
all[plugin.command] = plugin
149-
}
150-
return all
151-
}

0 commit comments

Comments
 (0)