@@ -10,7 +10,8 @@ import SwiftUI
10
10
11
11
@MainActor
12
12
class DraftPatchViewModel : ObservableObject {
13
- private var context : ModelContext
13
+ private var repository : ChatThreadRepository
14
+ private var llmManager : LLMManager
14
15
15
16
@Published var chatThreads : [ ChatThread ] = [ ]
16
17
@Published var selectedThread : ChatThread ? {
@@ -38,8 +39,9 @@ class DraftPatchViewModel: ObservableObject {
38
39
@Published var settings : Settings ? = nil
39
40
@Published var errorMessage : String ? = nil
40
41
41
- init ( context: ModelContext ) {
42
- self . context = context
42
+ init ( repository: ChatThreadRepository , llmManager: LLMManager = . shared) {
43
+ self . repository = repository
44
+ self . llmManager = llmManager
43
45
44
46
loadSettings ( )
45
47
loadThreads ( )
@@ -49,6 +51,46 @@ class DraftPatchViewModel: ObservableObject {
49
51
}
50
52
}
51
53
54
+ func loadThreads( ) {
55
+ do {
56
+ chatThreads = try repository. fetchThreads ( )
57
+ selectedThread = chatThreads. first
58
+ } catch {
59
+ print ( " Error loading threads: \( error) " )
60
+ chatThreads = [ ]
61
+ selectedThread = nil
62
+ }
63
+ }
64
+
65
+ func loadSettings( ) {
66
+ do {
67
+ settings = try repository. fetchSettings ( )
68
+
69
+ if settings != nil , let ollamaEndpontURL = settings? . ollamaConfig? . endpointURL {
70
+ OllamaService . shared. endpointURL = ollamaEndpontURL
71
+ }
72
+ } catch {
73
+ print ( " Error loading settings: \( error) " )
74
+ }
75
+ }
76
+
77
+ func deleteThread( _ thread: ChatThread ) {
78
+ do {
79
+ try repository. deleteThread ( thread)
80
+ try repository. save ( )
81
+
82
+ if let index = chatThreads. firstIndex ( where: { $0. id == thread. id } ) {
83
+ chatThreads. remove ( at: index)
84
+ }
85
+
86
+ if selectedThread == thread {
87
+ selectedThread = chatThreads. first
88
+ }
89
+ } catch {
90
+ print ( " Error deleting thread: \( error) " )
91
+ }
92
+ }
93
+
52
94
func toggleDrafting( ) {
53
95
isDraftingEnabled. toggle ( )
54
96
@@ -122,35 +164,6 @@ class DraftPatchViewModel: ObservableObject {
122
164
}
123
165
}
124
166
125
- private func loadThreads( ) {
126
- let descriptor = FetchDescriptor < ChatThread > (
127
- sortBy: [ SortDescriptor ( \. updatedAt, order: . reverse) ]
128
- )
129
-
130
- do {
131
- chatThreads = try context. fetch ( descriptor)
132
- selectedThread = chatThreads. first
133
- } catch {
134
- print ( " Error loading threads: \( error) " )
135
- chatThreads = [ ]
136
- selectedThread = nil
137
- }
138
- }
139
-
140
- private func loadSettings( ) {
141
- let descriptor = FetchDescriptor < Settings > ( )
142
-
143
- do {
144
- settings = try context. fetch ( descriptor) . first
145
-
146
- if settings != nil , let ollamaEndpontURL = settings? . ollamaConfig? . endpointURL {
147
- OllamaService . shared. endpointURL = ollamaEndpontURL
148
- }
149
- } catch {
150
- print ( " Error loading settings: \( error) " )
151
- }
152
- }
153
-
154
167
func toggleDraftWithLastApp( ) {
155
168
if let lastAppDraftedWith = settings? . lastAppDraftedWith {
156
169
if isDraftingEnabled {
@@ -217,10 +230,11 @@ class DraftPatchViewModel: ObservableObject {
217
230
return
218
231
}
219
232
233
+ // If we're working with a draft thread, persist it
220
234
if let draftThread, draftThread == thread {
221
- context. insert ( thread)
222
235
do {
223
- try context. save ( )
236
+ try repository. insertThread ( thread)
237
+ try repository. save ( )
224
238
chatThreads. insert ( thread, at: 0 )
225
239
} catch {
226
240
print ( " Error saving new thread: \( error) " )
@@ -238,7 +252,11 @@ class DraftPatchViewModel: ObservableObject {
238
252
239
253
if let tokenStream = getTokenStream ( for: thread, with: messagesPayload) {
240
254
thinking = true
241
- saveContext ( )
255
+ do {
256
+ try repository. save ( )
257
+ } catch {
258
+ print ( " Error saving context: \( error) " )
259
+ }
242
260
243
261
let assistantMsg = ChatMessage ( text: " " , role: . assistant, streaming: true )
244
262
thread. messages. append ( assistantMsg)
@@ -257,13 +275,21 @@ class DraftPatchViewModel: ObservableObject {
257
275
}
258
276
259
277
assistantMsg. streaming = false
260
- saveContext ( )
278
+ do {
279
+ try repository. save ( )
280
+ } catch {
281
+ print ( " Error saving context: \( error) " )
282
+ }
261
283
262
284
if thread. title == " New Conversation " {
263
285
do {
264
286
let title = try await generateTitle ( for: messageText, using: thread. model)
265
287
thread. title = title
266
- saveContext ( )
288
+ do {
289
+ try repository. save ( )
290
+ } catch {
291
+ print ( " Error saving context: \( error) " )
292
+ }
267
293
} catch {
268
294
print ( " Error generating thread title: \( error) " )
269
295
}
@@ -277,72 +303,17 @@ class DraftPatchViewModel: ObservableObject {
277
303
}
278
304
}
279
305
280
- private func saveContext( ) {
281
- do {
282
- try context. save ( )
283
- objectWillChange. send ( )
284
- } catch {
285
- print ( " Error saving context: \( error) " )
286
- }
287
- }
288
-
289
- func deleteThread( _ thread: ChatThread ) {
290
- context. delete ( thread)
291
-
292
- do {
293
- try context. save ( )
294
-
295
- if let index = chatThreads. firstIndex ( where: { $0. id == thread. id } ) {
296
- chatThreads. remove ( at: index)
297
- }
298
-
299
- if selectedThread == thread {
300
- selectedThread = chatThreads. first
301
- }
302
- } catch {
303
- print ( " Error deleting thread: \( error) " )
304
- }
305
- }
306
-
307
306
/// Determines the correct service and returns a token stream.
308
307
private func getTokenStream( for thread: ChatThread , with messages: [ ChatMessagePayload ] )
309
308
-> AsyncThrowingStream < String , Error > ?
310
309
{
311
- switch thread. model. provider {
312
- case . ollama:
313
- return OllamaService . shared. streamChat (
314
- messages: messages,
315
- modelName: thread. model. name
316
- )
317
- case . openai:
318
- return OpenAIService . shared. streamChat (
319
- messages: messages,
320
- modelName: thread. model. name
321
- )
322
- case . gemini:
323
- return GeminiService . shared. streamChat (
324
- messages: messages,
325
- modelName: thread. model. name
326
- )
327
- case . anthropic:
328
- return ClaudeService . shared. streamChat (
329
- messages: messages,
330
- modelName: thread. model. name
331
- )
332
- }
310
+ return llmManager. getService ( for: thread. model. provider)
311
+ . streamChat ( messages: messages, modelName: thread. model. name)
333
312
}
334
313
335
314
/// Calls the appropriate service to generate a title based on the provider.
336
315
private func generateTitle( for text: String , using model: ChatModel ) async throws -> String {
337
- switch model. provider {
338
- case . ollama:
339
- return try await OllamaService . shared. generateTitle ( for: text, modelName: model. name)
340
- case . openai:
341
- return try await OpenAIService . shared. generateTitle ( for: text, modelName: model. name)
342
- case . gemini:
343
- return try await GeminiService . shared. generateTitle ( for: text, modelName: model. name)
344
- case . anthropic:
345
- return try await ClaudeService . shared. generateTitle ( for: text, modelName: model. name)
346
- }
316
+ return try await llmManager. getService ( for: model. provider)
317
+ . generateTitle ( for: text, modelName: model. name)
347
318
}
348
319
}
0 commit comments