@@ -18,7 +18,7 @@ import SystemUtils
1818
1919public protocol ChatServiceType {
2020 var memory : ContextAwareAutoManagedChatMemory { get set }
21- func send( _ id: String , content: String , skillSet: [ ConversationSkill ] , references: [ FileReference ] , model: String ? , agentMode: Bool , userLanguage: String ? , turnId: String ? ) async throws
21+ func send( _ id: String , content: String , contentImages : [ ChatCompletionContentPartImage ] , contentImageReferences : [ ImageReference ] , skillSet: [ ConversationSkill ] , references: [ FileReference ] , model: String ? , agentMode: Bool , userLanguage: String ? , turnId: String ? ) async throws
2222 func stopReceivingMessage( ) async
2323 func upvote( _ id: String , _ rating: ConversationRating ) async
2424 func downvote( _ id: String , _ rating: ConversationRating ) async
@@ -316,10 +316,23 @@ public final class ChatService: ChatServiceType, ObservableObject {
316316 }
317317 }
318318 }
319+
320+ public enum ChatServiceError : Error , LocalizedError {
321+ case conflictingImageFormats( String )
322+
323+ public var errorDescription : String ? {
324+ switch self {
325+ case . conflictingImageFormats( let message) :
326+ return message
327+ }
328+ }
329+ }
319330
320331 public func send(
321332 _ id: String ,
322333 content: String ,
334+ contentImages: Array < ChatCompletionContentPartImage > = [ ] ,
335+ contentImageReferences: Array < ImageReference > = [ ] ,
323336 skillSet: Array < ConversationSkill > ,
324337 references: Array < FileReference > ,
325338 model: String ? = nil ,
@@ -331,11 +344,31 @@ public final class ChatService: ChatServiceType, ObservableObject {
331344 let workDoneToken = UUID ( ) . uuidString
332345 activeRequestId = workDoneToken
333346
347+ let finalImageReferences : [ ImageReference ]
348+ let finalContentImages : [ ChatCompletionContentPartImage ]
349+
350+ if !contentImageReferences. isEmpty {
351+ // User attached images are all parsed as ImageReference
352+ finalImageReferences = contentImageReferences
353+ finalContentImages = contentImageReferences
354+ . map {
355+ ChatCompletionContentPartImage (
356+ url: $0. dataURL ( imageType: $0. source == . screenshot ? " png " : " " )
357+ )
358+ }
359+ } else {
360+ // In current implementation, only resend message will have contentImageReferences
361+ // No need to convert ChatCompletionContentPartImage to ImageReference for persistence
362+ finalImageReferences = [ ]
363+ finalContentImages = contentImages
364+ }
365+
334366 var chatMessage = ChatMessage (
335367 id: id,
336368 chatTabID: self . chatTabInfo. id,
337369 role: . user,
338370 content: content,
371+ contentImageReferences: finalImageReferences,
339372 references: references. toConversationReferences ( )
340373 )
341374
@@ -406,6 +439,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
406439 let request = createConversationRequest (
407440 workDoneToken: workDoneToken,
408441 content: content,
442+ contentImages: finalContentImages,
409443 activeDoc: activeDoc,
410444 references: references,
411445 model: model,
@@ -417,12 +451,13 @@ public final class ChatService: ChatServiceType, ObservableObject {
417451
418452 self . lastUserRequest = request
419453 self . skillSet = validSkillSet
420- try await send ( request)
454+ try await sendConversationRequest ( request)
421455 }
422456
423457 private func createConversationRequest(
424458 workDoneToken: String ,
425459 content: String ,
460+ contentImages: [ ChatCompletionContentPartImage ] = [ ] ,
426461 activeDoc: Doc ? ,
427462 references: [ FileReference ] ,
428463 model: String ? = nil ,
@@ -443,6 +478,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
443478 return ConversationRequest (
444479 workDoneToken: workDoneToken,
445480 content: newContent,
481+ contentImages: contentImages,
446482 workspaceFolder: " " ,
447483 activeDoc: activeDoc,
448484 skills: skillCapabilities,
@@ -504,6 +540,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
504540 try await send (
505541 id,
506542 content: lastUserRequest. content,
543+ contentImages: lastUserRequest. contentImages,
507544 skillSet: skillSet,
508545 references: lastUserRequest. references ?? [ ] ,
509546 model: model != nil ? model : lastUserRequest. model,
@@ -720,12 +757,14 @@ public final class ChatService: ChatServiceType, ObservableObject {
720757 await Status . shared
721758 . updateCLSStatus ( . warning, busy: false , message: CLSError . message)
722759 let errorMessage = buildErrorMessage (
723- turnId: progress. turnId,
760+ turnId: progress. turnId,
724761 panelMessages: [ . init( type: . error, title: String ( CLSError . code ?? 0 ) , message: CLSError . message, location: . Panel) ] )
725762 // will persist in resetongoingRequest()
726763 await memory. appendMessage ( errorMessage)
727764
728- if let lastUserRequest {
765+ if let lastUserRequest,
766+ let currentUserPlan = await Status . shared. currentUserPlan ( ) ,
767+ currentUserPlan != " free " {
729768 guard let fallbackModel = CopilotModelManager . getFallbackLLM (
730769 scope: lastUserRequest. agentMode ? . agentPanel : . chatPanel
731770 ) else {
@@ -852,7 +891,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
852891 }
853892 }
854893
855- private func send ( _ request: ConversationRequest ) async throws {
894+ private func sendConversationRequest ( _ request: ConversationRequest ) async throws {
856895 guard !isReceivingMessage else { throw CancellationError ( ) }
857896 isReceivingMessage = true
858897
@@ -892,7 +931,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
892931
893932 switch fileEdit. toolName {
894933 case . insertEditIntoFile:
895- try InsertEditIntoFileTool . applyEdit ( for: fileURL, content: fileEdit. originalContent, contextProvider: self )
934+ InsertEditIntoFileTool . applyEdit ( for: fileURL, content: fileEdit. originalContent, contextProvider: self )
896935 case . createFile:
897936 try CreateFileTool . undo ( for: fileURL)
898937 default :
0 commit comments