Introduce message kinds, and handle them appropriately in user and agent chat
This commit is contained in:
1 parent
cba41f1e7e
commit
396b06b05c
4 files changed
+89
-18
No files matched your search
@@ -86,14 +86,15 @@ class ChatMixin:
|
|||||||
print(f"Added agent #{agent_id} to chat #{chat_id}")
|
print(f"Added agent #{agent_id} to chat #{chat_id}")
|
||||||
|
|
||||||
# If an integer is passed in sender, it is interpreted as an agent id
|
# If an integer is passed in sender, it is interpreted as an agent id
|
||||||
def send_chat_message(self, chat_id, content, sender='user', thread_id=None):
|
def send_chat_message(self, chat_id, content, sender='user', thread_id=None, kind=None, subkind=None, format=None):
|
||||||
with self.sql_session() as session:
|
with self.sql_session() as session:
|
||||||
message = db.Message()
|
message = db.Message()
|
||||||
message.chat_id = chat_id
|
message.chat_id = chat_id
|
||||||
message.time = int(datetime.now().timestamp())
|
message.time = int(datetime.now().timestamp())
|
||||||
message.last_edit = message.time
|
message.last_edit = message.time
|
||||||
message.sender = sender
|
message.sender = sender
|
||||||
message.kind = 'text' # default kind
|
message.kind = kind or 'message'
|
||||||
|
message.subkind = subkind
|
||||||
message.content = content
|
message.content = content
|
||||||
message.thread_id = thread_id
|
message.thread_id = thread_id
|
||||||
|
|
||||||
|
|||||||
+16
-3
@@ -7,11 +7,13 @@ import database as db
|
|||||||
MESSAGE_PAGE_SIZE = 256
|
MESSAGE_PAGE_SIZE = 256
|
||||||
|
|
||||||
class Message:
|
class Message:
|
||||||
def __init__(self, id, content, sender, time=None):
|
def __init__(self, id, content, sender, time=None, kind='message', subkind=None):
|
||||||
self.id = id
|
self.id = id
|
||||||
self.content = content
|
self.content = content
|
||||||
self.sender = sender
|
self.sender = sender
|
||||||
self.time = time
|
self.time = time
|
||||||
|
self.kind = kind
|
||||||
|
self.subkind = subkind
|
||||||
|
|
||||||
def from_db_record(record):
|
def from_db_record(record):
|
||||||
return Message(record.id, record.content, record.sender, record.time)
|
return Message(record.id, record.content, record.sender, record.time)
|
||||||
@@ -27,6 +29,8 @@ class Thread:
|
|||||||
self.description = description
|
self.description = description
|
||||||
|
|
||||||
self.messages = []
|
self.messages = []
|
||||||
|
self.notes = []
|
||||||
|
self.memories = []
|
||||||
|
|
||||||
class ChatView:
|
class ChatView:
|
||||||
def __init__(self, app, chat_id):
|
def __init__(self, app, chat_id):
|
||||||
@@ -44,6 +48,9 @@ class ChatView:
|
|||||||
self.thread_map = {}
|
self.thread_map = {}
|
||||||
# Long term summary of this chat
|
# Long term summary of this chat
|
||||||
self.summary = None
|
self.summary = None
|
||||||
|
# Separate list for note messages from any thread
|
||||||
|
# Is it better to exclude them or include them from threads? atm they're included
|
||||||
|
self.notes = []
|
||||||
|
|
||||||
# Id of the thread currently in focus, by default the last thread appearing in the conversation
|
# Id of the thread currently in focus, by default the last thread appearing in the conversation
|
||||||
self.current_thread_id = None
|
self.current_thread_id = None
|
||||||
@@ -92,8 +99,14 @@ class ChatView:
|
|||||||
self.thread_map[thread.id] = thread
|
self.thread_map[thread.id] = thread
|
||||||
self.threads.insert(0, thread)
|
self.threads.insert(0, thread)
|
||||||
# This is the oldest message so far, add it to the start of the chat
|
# This is the oldest message so far, add it to the start of the chat
|
||||||
message = Message(record.id, record.content, record.sender, record.time)
|
message = Message(record.id, record.content, record.sender, record.time, record.kind, record.subkind)
|
||||||
thread.messages.insert(0, message)
|
|
||||||
|
if message.kind == 'note':
|
||||||
|
thread.notes.insert(0, message)
|
||||||
|
elif message.kind == 'memory':
|
||||||
|
thread.memories.insert(0, message)
|
||||||
|
else:
|
||||||
|
thread.messages.insert(0, message)
|
||||||
|
|
||||||
def get_thread(self, thread_id):
|
def get_thread(self, thread_id):
|
||||||
if thread_id == None:
|
if thread_id == None:
|
||||||
|
|||||||
+4
-1
@@ -185,8 +185,11 @@ def start_server(app):
|
|||||||
elif action == "send-chat-message":
|
elif action == "send-chat-message":
|
||||||
message = app.send_chat_message(
|
message = app.send_chat_message(
|
||||||
chat_id=body['chatId'],
|
chat_id=body['chatId'],
|
||||||
content=body['content'],
|
|
||||||
thread_id=body.get('threadId'),
|
thread_id=body.get('threadId'),
|
||||||
|
content=body['content'],
|
||||||
|
kind=body.get('kind'),
|
||||||
|
subkind=body.get('subkind'),
|
||||||
|
format=body.get('format'),
|
||||||
)
|
)
|
||||||
await send({'message': "message sent", 'messageId': message['id'], 'nonce': nonce})
|
await send({'message': "message sent", 'messageId': message['id'], 'nonce': nonce})
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,34 @@ class Message extends ChatEntry {
|
|||||||
|
|
||||||
class ThreadHeader extends ChatEntry {
|
class ThreadHeader extends ChatEntry {
|
||||||
final int threadId;
|
final int threadId;
|
||||||
final int messageCount;
|
int messageCount;
|
||||||
|
|
||||||
ThreadHeader({required this.threadId, required this.messageCount})
|
ThreadHeader({required this.threadId, required this.messageCount})
|
||||||
: super(content: "Thread ($messageCount messages)", isFromUser: false);
|
: super(content: "Thread ($messageCount messages)", isFromUser: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NoteMessage extends Message {
|
||||||
|
NoteMessage({sender, content, id, threadId}) :
|
||||||
|
super(sender: sender, content: content, id: id, threadId: threadId);
|
||||||
|
}
|
||||||
|
class HiddenMessage extends Message {
|
||||||
|
HiddenMessage({sender, content, id, threadId}) :
|
||||||
|
super(sender: sender, content: content, id: id, threadId: threadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Message createMessageFromData (Map<String, dynamic> data) {
|
||||||
|
switch (data['kind']) {
|
||||||
|
case 'message':
|
||||||
|
return Message(sender: data['sender'], content: data['content'], threadId: data['threadId']);
|
||||||
|
break;
|
||||||
|
case 'note':
|
||||||
|
return NoteMessage(sender: data['sender'], content: data['content'], threadId: data['threadId']);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return HiddenMessage(sender: data['sender'], content: data['content'], threadId: data['threadId']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ChatScreen extends StatefulWidget {
|
class ChatScreen extends StatefulWidget {
|
||||||
final int chatId;
|
final int chatId;
|
||||||
|
|
||||||
@@ -39,9 +61,7 @@ class ChatScreen extends StatefulWidget {
|
|||||||
static Future<List<Message>> getChatMessages(int chatId) async {
|
static Future<List<Message>> getChatMessages(int chatId) async {
|
||||||
final response = await Backend.sendAction("get-chat-messages", {'chatId': chatId});
|
final response = await Backend.sendAction("get-chat-messages", {'chatId': chatId});
|
||||||
List<dynamic> messageObjects = response['messages'];
|
List<dynamic> messageObjects = response['messages'];
|
||||||
List<Message> messages = messageObjects.map((msg) =>
|
List<Message> messages = messageObjects.map((msg) => createMessageFromData(msg)).toList();
|
||||||
Message(sender: msg['sender'], content: msg['content'], id: msg['id'], threadId: msg['threadId'])
|
|
||||||
).toList();
|
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +78,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
// UI State
|
// UI State
|
||||||
Set<int> expandedThreads = {};
|
Set<int> expandedThreads = {};
|
||||||
int? editingMessageId = null;
|
int? editingMessageId = null;
|
||||||
|
bool noteMode = false;
|
||||||
|
|
||||||
// Internal Funcionality
|
// Internal Funcionality
|
||||||
Offset? _longPressOffset;
|
Offset? _longPressOffset;
|
||||||
@@ -70,7 +91,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
_loadInitialData();
|
_loadInitialData();
|
||||||
_chatSubscription = Backend.chatMessages(widget.chatId).listen((data) {
|
_chatSubscription = Backend.chatMessages(widget.chatId).listen((data) {
|
||||||
final message = Message(sender: data['sender'], content: data['content'], threadId: data['threadId']);
|
final message = createMessageFromData(data);
|
||||||
if (!message.isFromUser) {
|
if (!message.isFromUser) {
|
||||||
addMessage(message);
|
addMessage(message);
|
||||||
}
|
}
|
||||||
@@ -117,8 +138,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
|
|
||||||
// Second pass: Build the display list with correct ordering
|
// Second pass: Build the display list with correct ordering
|
||||||
for (var message in _messages) {
|
for (var message in _messages) {
|
||||||
if (message.threadId == null) {
|
if (message is NoteMessage || message.threadId == null) {
|
||||||
// Non-threaded messages go directly to the result
|
// Non-threaded messages and notes go directly to the result
|
||||||
result.add(message);
|
result.add(message);
|
||||||
} else {
|
} else {
|
||||||
final threadId = message.threadId!;
|
final threadId = message.threadId!;
|
||||||
@@ -164,29 +185,40 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
'chatId': widget.chatId,
|
'chatId': widget.chatId,
|
||||||
'content': content,
|
'content': content,
|
||||||
'threadId': currentThreadId,
|
'threadId': currentThreadId,
|
||||||
|
'kind': noteMode ? 'note' : null,
|
||||||
}).then((response) {
|
}).then((response) {
|
||||||
if (response['error'] != null) {
|
if (response['error'] != null) {
|
||||||
print("Error sending chat message: ${response['error']}");
|
print("Error sending chat message: ${response['error']}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final newMessage = Message(sender: "user", content: content, threadId: currentThreadId);
|
final newMessage = noteMode ?
|
||||||
|
NoteMessage(sender: "user", content: content, threadId: currentThreadId) :
|
||||||
|
Message(sender: "user", content: content, threadId: currentThreadId);
|
||||||
addMessage(newMessage);
|
addMessage(newMessage);
|
||||||
} else {
|
} else {
|
||||||
|
Message message = _messages.firstWhere((element) => element.id == editingMessageId!);
|
||||||
|
|
||||||
Backend.sendAction("update-chat-message", {
|
Backend.sendAction("update-chat-message", {
|
||||||
'messageId': editingMessageId,
|
'messageId': editingMessageId,
|
||||||
'content': content,
|
'content': content,
|
||||||
|
'kind': noteMode ? 'note' : null,
|
||||||
}).then((response) {
|
}).then((response) {
|
||||||
if (response['error'] != null) {
|
if (response['error'] != null) {
|
||||||
print("Error updating message: ${response['error']}");
|
print("Error updating message: ${response['error']}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Message message = _messages.firstWhere((element) => element.id == editingMessageId!);
|
|
||||||
setState(() {
|
setState(() {
|
||||||
message.content = content;
|
message.content = content;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (noteMode) {
|
||||||
|
setState(() {
|
||||||
|
noteMode = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void createThread () async {
|
void createThread () async {
|
||||||
@@ -276,6 +308,18 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isWideScreen = MediaQuery.of(context).size.width > 600;
|
final isWideScreen = MediaQuery.of(context).size.width > 600;
|
||||||
|
final String? editInfo;
|
||||||
|
if (editingMessageId != null) {
|
||||||
|
if (noteMode) {
|
||||||
|
editInfo = 'Editing Note ${editingMessageId}';
|
||||||
|
} else {
|
||||||
|
editInfo = 'Editing ${editingMessageId}';
|
||||||
|
}
|
||||||
|
} else if (noteMode) {
|
||||||
|
editInfo = 'Editing Note';
|
||||||
|
} else {
|
||||||
|
editInfo = null;
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -297,6 +341,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
if (entry is ThreadHeader) {
|
if (entry is ThreadHeader) {
|
||||||
return _buildThreadHeader(context, entry);
|
return _buildThreadHeader(context, entry);
|
||||||
} else if (entry is Message) {
|
} else if (entry is Message) {
|
||||||
|
final isNote = entry is NoteMessage;
|
||||||
final message = entry as Message;
|
final message = entry as Message;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onLongPressStart: (details) {
|
onLongPressStart: (details) {
|
||||||
@@ -318,7 +363,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
padding: EdgeInsets.all(10),
|
padding: EdgeInsets.all(10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: getMessageColor(context),
|
color: getMessageColor(context),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(isNote ? 0 : 12),
|
||||||
|
border: isNote ? Border.all(color: Colors.white) : null,
|
||||||
),
|
),
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
message.content,
|
message.content,
|
||||||
@@ -343,8 +389,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (editingMessageId!= null) ...[
|
if (editInfo != null) ...[
|
||||||
Text('Editing ${editingMessageId}'),
|
Text(editInfo!),
|
||||||
],
|
],
|
||||||
Expanded(
|
Expanded(
|
||||||
child: KeyboardListener(
|
child: KeyboardListener(
|
||||||
@@ -380,6 +426,14 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
},
|
},
|
||||||
child: Text('Start New Topic'),
|
child: Text('Start New Topic'),
|
||||||
),
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
noteMode = !noteMode;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Text(noteMode ? 'Switch to Message' : 'Make Note'),
|
||||||
|
),
|
||||||
if (editingMessageId!= null) ...[
|
if (editingMessageId!= null) ...[
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|||||||
Reference in new issue
Block a user