You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cloudsolutions-atoms/lib/modules/copilot/model/copilot_message_model.dart

138 lines
4.7 KiB
Dart

3 weeks ago
import 'package:flutter/foundation.dart';
/// Role of a message inside a Copilot conversation.
enum CopilotRole { user, assistant }
/// A single reference image returned alongside an assistant answer
/// (e.g. a scanned page from a referenced document).
class CopilotImage {
final String base64;
final String source;
final int page;
final String? fileLink;
final double? similarityScore;
CopilotImage({
required this.base64,
required this.source,
required this.page,
this.fileLink,
this.similarityScore,
});
factory CopilotImage.fromJson(Map<String, dynamic> json) => CopilotImage(
base64: json['image_base64'] ?? '',
source: json['source'] ?? '',
page: json['page'] is int ? json['page'] : int.tryParse('${json['page']}') ?? 0,
fileLink: json['file_link'],
similarityScore: (json['similarity_score'] as num?)?.toDouble(),
);
}
/// A single message (human query or assistant answer) inside a Copilot conversation.
///
/// This is intentionally a NEW, small model (not a reuse of chat's SingleUserChatModel),
/// since the Copilot Automation API response shape (answer/document_answer/website_answer/images)
/// is completely different from the multi-user chat history model.
class CopilotMessage {
final String id;
final CopilotRole role;
String content; // assistant answer text OR the human query text
String? documentAnswer;
String? websiteAnswer;
List<CopilotImage> images;
DateTime? timestamp;
bool isStreaming;
bool isError;
3 weeks ago
3 weeks ago
// Local path of a recorded audio file for a user's voice query (never a remote
// download URL - the backend never sends audio back). Used to let the user
// replay their own sent voice message, same as Chat's audio playback.
String? localAudioPath;
CopilotMessage({
required this.id,
required this.role,
this.content = '',
this.documentAnswer,
this.websiteAnswer,
this.images = const [],
this.timestamp,
this.isStreaming = false,
this.isError = false,
this.localAudioPath,
});
/// Builds a message from GetConversationByThreadId's flat message shape:
/// { "role": "human"|"ai", "content": "...", "document_answer": "...", "website_answer": "...", "images": [...] }
/// NOTE: the API currently always returns `timestamp: null`, so we fall back to
/// "now" purely so the UI has a value to render (matches Chat's bubble layout,
/// which always shows a time under each message).
factory CopilotMessage.fromHistoryJson(Map<String, dynamic> json) => CopilotMessage(
id: UniqueKey().toString(),
role: json['role'] == 'ai' ? CopilotRole.assistant : CopilotRole.user,
content: json['content'] ?? '',
documentAnswer: json['document_answer'],
websiteAnswer: json['website_answer'],
images: (json['images'] as List? ?? []).map((e) => CopilotImage.fromJson(e)).toList(),
timestamp: json['timestamp'] != null ? (DateTime.tryParse(json['timestamp'].toString()) ?? DateTime.now()) : DateTime.now(),
);
}
/// A single event decoded from a QueryStream SSE `data:` line
/// (payload shape: {"type":"response","content":{...}}, or the literal string "[DONE]").
class CopilotStreamEvent {
final String type;
final CopilotStreamContent? content;
CopilotStreamEvent({required this.type, this.content});
factory CopilotStreamEvent.fromJson(Map<String, dynamic> json) => CopilotStreamEvent(
type: json['type'] ?? '',
content: json['content'] is Map<String, dynamic> ? CopilotStreamContent.fromJson(json['content']) : null,
);
}
class CopilotStreamContent {
final String? answer;
final String? documentAnswer;
final String? websiteAnswer;
final int? chunksUsed;
final List<CopilotImage> images;
CopilotStreamContent({
this.answer,
this.documentAnswer,
this.websiteAnswer,
this.chunksUsed,
this.images = const [],
});
factory CopilotStreamContent.fromJson(Map<String, dynamic> json) => CopilotStreamContent(
answer: json['answer'],
documentAnswer: json['document_answer'],
websiteAnswer: json['website_answer'],
chunksUsed: json['chunks_used'],
images: (json['images'] as List? ?? []).map((e) => CopilotImage.fromJson(e)).toList(),
);
}
/// A single row in the Copilot query history list (GetQueryHistory).
class CopilotHistoryItem {
final String threadId;
final String firstHumanMessage;
3 weeks ago
final String updatedAt;
3 weeks ago
CopilotHistoryItem({
required this.threadId,
required this.firstHumanMessage,
required this.updatedAt,
});
factory CopilotHistoryItem.fromJson(Map<String, dynamic> json) => CopilotHistoryItem(
threadId: json['threadId'] ?? '',
firstHumanMessage: json['firstHumanMessage'] ?? '',
3 weeks ago
updatedAt: json['updatedAt'] ?? '',
3 weeks ago
);
}