mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 14:31:25 +00:00
* Chat improvements * Delete/reset account via API for Flutter app * Fix tests. * Add "contact us" to settings * Update mobile/lib/screens/chat_conversation_screen.dart Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Juan José Mata <jjmata@jjmata.com> * Improve LLM special token detection * Deactivated user shouldn't have API working * Fix tests * API-Key usage * Flutter app launch failure on no network * Handle deletion/reset delays * Local cached data may become stale * Use X-Api-Key correctly! --------- Signed-off-by: Juan José Mata <jjmata@jjmata.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
86 lines
2.4 KiB
Dart
86 lines
2.4 KiB
Dart
import 'tool_call.dart';
|
||
|
||
class Message {
|
||
/// Known LLM special tokens that may leak into responses (strip from display).
|
||
/// Includes ASCII ChatML (<|...|>) and DeepSeek full-width variants (<|...|>).
|
||
static const _llmTokenPatterns = [
|
||
'<|start_of_sentence|>',
|
||
'<|im_start|>',
|
||
'<|im_end|>',
|
||
'<|endoftext|>',
|
||
'</s>',
|
||
// DeepSeek full-width pipe variants (U+FF5C |)
|
||
'<\uFF5Cstart_of_sentence\uFF5C>',
|
||
'<\uFF5Cim_start\uFF5C>',
|
||
'<\uFF5Cim_end\uFF5C>',
|
||
'<\uFF5Cendoftext\uFF5C>',
|
||
];
|
||
|
||
/// Removes LLM tokens and trims trailing whitespace from assistant content.
|
||
static String sanitizeContent(String content) {
|
||
var out = content;
|
||
for (final token in _llmTokenPatterns) {
|
||
out = out.replaceAll(token, '');
|
||
}
|
||
out = out.replaceAll(RegExp(r'<\|[^|]*\|>'), '');
|
||
out = out.replaceAll(RegExp('<\u{FF5C}[^\u{FF5C}]*\u{FF5C}>'), '');
|
||
return out.trim();
|
||
}
|
||
|
||
final String id;
|
||
final String type;
|
||
final String role;
|
||
final String content;
|
||
final String? model;
|
||
final DateTime createdAt;
|
||
final DateTime updatedAt;
|
||
final List<ToolCall>? toolCalls;
|
||
|
||
Message({
|
||
required this.id,
|
||
required this.type,
|
||
required this.role,
|
||
required this.content,
|
||
this.model,
|
||
required this.createdAt,
|
||
required this.updatedAt,
|
||
this.toolCalls,
|
||
});
|
||
|
||
factory Message.fromJson(Map<String, dynamic> json) {
|
||
final rawContent = json['content'] as String;
|
||
final role = json['role'] as String;
|
||
final content = role == 'assistant' ? sanitizeContent(rawContent) : rawContent;
|
||
return Message(
|
||
id: json['id'].toString(),
|
||
type: json['type'] as String,
|
||
role: role,
|
||
content: content,
|
||
model: json['model'] as String?,
|
||
createdAt: DateTime.parse(json['created_at'] as String),
|
||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||
toolCalls: json['tool_calls'] != null
|
||
? (json['tool_calls'] as List)
|
||
.map((tc) => ToolCall.fromJson(tc as Map<String, dynamic>))
|
||
.toList()
|
||
: null,
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> toJson() {
|
||
return {
|
||
'id': id,
|
||
'type': type,
|
||
'role': role,
|
||
'content': content,
|
||
'model': model,
|
||
'created_at': createdAt.toIso8601String(),
|
||
'updated_at': updatedAt.toIso8601String(),
|
||
'tool_calls': toolCalls?.map((tc) => tc.toJson()).toList(),
|
||
};
|
||
}
|
||
|
||
bool get isUser => role == 'user';
|
||
bool get isAssistant => role == 'assistant';
|
||
}
|