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.
138 lines
4.8 KiB
Dart
138 lines
4.8 KiB
Dart
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;
|
|
// 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;
|
|
final DateTime updatedAt;
|
|
|
|
CopilotHistoryItem({
|
|
required this.threadId,
|
|
required this.firstHumanMessage,
|
|
required this.updatedAt,
|
|
});
|
|
|
|
factory CopilotHistoryItem.fromJson(Map<String, dynamic> json) => CopilotHistoryItem(
|
|
threadId: json['threadId'] ?? '',
|
|
firstHumanMessage: json['firstHumanMessage'] ?? '',
|
|
updatedAt: DateTime.tryParse(json['updatedAt'] ?? '') ?? DateTime.now(),
|
|
);
|
|
}
|
|
|