message edit/update functionality

This commit is contained in:
Shivering Ocelot committed 2025-07-05 10:44:14 -05:00
1 parent cdba31e9f7
commit cba41f1e7e
6 files changed
+122 -32

No files matched your search

+13 -8
View File
@@ -208,10 +208,12 @@ class ToolUsingAgent:
output += f"[{action.toolset_name}.{action.tool_name}]:{action_result}\n" output += f"[{action.toolset_name}.{action.tool_name}]:{action_result}\n"
return output return output
class Llama8bAgent(Agent): class LlamaAgent(Agent):
identifier = "llama-8b" identifier = "llama-8b"
description = "Uses the llama 3 instruct models. Model sizes: '8b', '30b'"
schema_fields = [ schema_fields = [
AgentField('system_prompt', 'string', default="You are a helpful AI assistant."), AgentField('system_prompt', 'string', default="You are a helpful AI assistant."),
AgentField('model_size', 'string', default="8b"),
] ]
tools = [] tools = []
@@ -228,13 +230,14 @@ class Llama8bAgent(Agent):
async def received_message(self, message): async def received_message(self, message):
def sender_name(message): def sender_name(message):
sender = message['sender'] sender = message.sender
is_digit = isinstance(sender, (int, str)) and str(sender).isdigit() is_digit = isinstance(sender, (int, str)) and str(sender).isdigit()
is_self = is_digit and str(sender) == str(self.id) is_self = is_digit and str(sender) == str(self.id)
return self.name if is_self else sender return self.name if is_self else sender
chat_context = self.app.get_chat_context(message.chat_id) chat_view = self.app.get_chat_view(message.chat_id)
chat_summary = "\n".join(f"[{sender_name(message)}]: {message['content']}" for message in chat_context.messages) thread_messages = chat_view.get_thread(message.thread_id).messages
chat_summary = "\n".join(f"[{sender_name(message)}]: {message.content}" for message in thread_messages)
messages = [ messages = [
LlamaMessage("system", f"{self.config['system_prompt']}\nYou are given the context of the conversation, respond with your next message in the conversation."), LlamaMessage("system", f"{self.config['system_prompt']}\nYou are given the context of the conversation, respond with your next message in the conversation."),
LlamaMessage("user", f"Recent Messages:\n{chat_summary}") LlamaMessage("user", f"Recent Messages:\n{chat_summary}")
@@ -248,15 +251,17 @@ class Llama8bAgent(Agent):
thread_id=message.thread_id, thread_id=message.thread_id,
) )
class BasicAgent(Llama8bAgent): class BasicAgent(LlamaAgent):
identifier = "basic" identifier = "basic"
schema_fields = Llama8bAgent.schema_fields schema_fields = LlamaAgent.schema_fields
tools = Llama8bAgent.tools tools = LlamaAgent.tools
class CoderV1Agent(Llama8bAgent): class CoderV1Agent(LlamaAgent):
identifier = "coder-v1" identifier = "coder-v1"
description = "Optimized for coding tasks. Based on the llama agent:\n" + LlamaAgent.description
schema_fields = [ schema_fields = [
AgentField('system_prompt', 'string', default="You are an expert Software Engineer profficient in multiple programming languages."), AgentField('system_prompt', 'string', default="You are an expert Software Engineer profficient in multiple programming languages."),
AgentField('model_size', 'string', default="30b"),
] ]
# TODO: add gral functions to the tool list # TODO: add gral functions to the tool list
tools = [] tools = []
+1 -1
View File
@@ -38,7 +38,7 @@ class App(ChatMixin):
self.agent_classes = [ self.agent_classes = [
agent_mod.BasicAgent, agent_mod.BasicAgent,
ConversationAgent, ConversationAgent,
agent_mod.Llama8bAgent, agent_mod.LlamaAgent,
agent_mod.CoderV1Agent, agent_mod.CoderV1Agent,
agent_mod.ToolUsingAgent, agent_mod.ToolUsingAgent,
agent_mod.DebugAgent, agent_mod.DebugAgent,
+31
View File
@@ -91,6 +91,7 @@ class ChatMixin:
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.sender = sender message.sender = sender
message.kind = 'text' # default kind message.kind = 'text' # default kind
message.content = content message.content = content
@@ -120,6 +121,36 @@ class ChatMixin:
'content': message.content, 'content': message.content,
} }
# Updates a single chat message.
# Most fields are optional. If they're not passed a value, they wont be updated.
# When kind is set, subkind is always set too.
def update_chat_message(self, message_id, content=None, kind=None, subkind=None, format=None):
with self.sql_session() as session:
message = session.execute(
select(db.Message).where(db.Message.id == message_id)
).scalars().first()
if not message:
raise Exception(f"Message not found with id {message_id}")
summary = []
if content != None:
message.content = content
summary.append('content')
if kind != None:
message.kind = kind
message.subkind = subkind
summary.append('kind')
summary.append('subkind')
if format != None:
message.format = format
summary.append('format')
#message.last_edit = int(datetime.now().timestamp())
session.commit()
print(f"Updated message #{message.id} fields: {summary}")
def get_chat_view(self, chat_id): def get_chat_view(self, chat_id):
return ChatView(self, chat_id) return ChatView(self, chat_id)
+4 -2
View File
@@ -80,10 +80,12 @@ class Message(Base):
chat_id: Mapped[int] = mapped_column(ForeignKey("chats.id")) chat_id: Mapped[int] = mapped_column(ForeignKey("chats.id"))
thread_id: Mapped[Optional[int]] = mapped_column(ForeignKey("threads.id")) thread_id: Mapped[Optional[int]] = mapped_column(ForeignKey("threads.id"))
# Timestamp of this message
time: Mapped[int]
# Identifier of the sender: user / system / user-$ID / agent-$ID # Identifier of the sender: user / system / user-$ID / agent-$ID
sender: Mapped[str] sender: Mapped[str]
# Timestamp of this message
time: Mapped[int]
# Timestamp of this message's last edit
#last_edit: Mapped[int]
# Predefined set of message types: # Predefined set of message types:
# message, log, memory, file, note # message, log, memory, file, note
+10
View File
@@ -190,6 +190,16 @@ def start_server(app):
) )
await send({'message': "message sent", 'messageId': message['id'], 'nonce': nonce}) await send({'message': "message sent", 'messageId': message['id'], 'nonce': nonce})
elif action == "update-chat-message":
message = app.update_chat_message(
message_id=body['messageId'],
content=body['content'],
kind=body.get('kind'),
subkind=body.get('subkind'),
format=body.get('format'),
)
await send({'message': "message sent", 'messageId': body['messageId'], 'nonce': nonce})
elif action == "create-chat-thread": elif action == "create-chat-thread":
thread_id = app.create_chat_thread( thread_id = app.create_chat_thread(
chat_id=body['chatId'], chat_id=body['chatId'],
+63 -21
View File
@@ -57,6 +57,7 @@ class _ChatScreenState extends State<ChatScreen> {
// UI State // UI State
Set<int> expandedThreads = {}; Set<int> expandedThreads = {};
int? editingMessageId = null;
// Internal Funcionality // Internal Funcionality
Offset? _longPressOffset; Offset? _longPressOffset;
@@ -153,21 +154,39 @@ class _ChatScreenState extends State<ChatScreen> {
}); });
} }
void sendMessage(String content) async { void sendMessage() async {
if (content.trim().isEmpty) return; // Don't send empty messages final content = _textController.text;
_textController.clear(); _textController.clear();
if (content.trim().isEmpty) return; // Don't send empty messages
final newMessage = Message(sender: "user", content: content, threadId: currentThreadId); if (editingMessageId == null) {
Backend.sendAction("send-chat-message", { Backend.sendAction("send-chat-message", {
'chatId': widget.chatId, 'chatId': widget.chatId,
'content': content, 'content': content,
'threadId': currentThreadId, 'threadId': currentThreadId,
}).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']}");
} }
}); });
addMessage(newMessage);
final newMessage = Message(sender: "user", content: content, threadId: currentThreadId);
addMessage(newMessage);
} else {
Backend.sendAction("update-chat-message", {
'messageId': editingMessageId,
'content': content,
}).then((response) {
if (response['error'] != null) {
print("Error updating message: ${response['error']}");
}
});
Message message = _messages.firstWhere((element) => element.id == editingMessageId!);
setState(() {
message.content = content;
});
}
} }
void createThread () async { void createThread () async {
@@ -186,7 +205,7 @@ class _ChatScreenState extends State<ChatScreen> {
return messageColor; return messageColor;
} }
void _showMessageContextMenu(BuildContext context, Offset position) { void _showMessageContextMenu(BuildContext context, Message message, Offset position) {
showMenu( showMenu(
context: context, context: context,
position: RelativeRect.fromLTRB(position.dx, position.dy, position.dx + 1, position.dy + 1), position: RelativeRect.fromLTRB(position.dx, position.dy, position.dx + 1, position.dy + 1),
@@ -201,8 +220,10 @@ class _ChatScreenState extends State<ChatScreen> {
PopupMenuItem( PopupMenuItem(
child: Text('Edit'), child: Text('Edit'),
onTap: () { onTap: () {
// TODO: Implement Edit functionality _textController.text = message.content;
print('Edit clicked'); setState(() {
editingMessageId = message.id;
});
}, },
), ),
PopupMenuItem( PopupMenuItem(
@@ -282,11 +303,11 @@ class _ChatScreenState extends State<ChatScreen> {
_longPressOffset = details.globalPosition; _longPressOffset = details.globalPosition;
}, },
onLongPress: () { onLongPress: () {
_showMessageContextMenu(context, _longPressOffset!); _showMessageContextMenu(context, message, _longPressOffset!);
_longPressOffset = null; _longPressOffset = null;
}, },
onSecondaryTapUp: (details) { onSecondaryTapUp: (details) {
_showMessageContextMenu(context, details.globalPosition); _showMessageContextMenu(context, message, details.globalPosition);
}, },
child: Padding( child: Padding(
padding: isWideScreen ? message.padding : EdgeInsets.zero, padding: isWideScreen ? message.padding : EdgeInsets.zero,
@@ -299,7 +320,14 @@ class _ChatScreenState extends State<ChatScreen> {
color: getMessageColor(context), color: getMessageColor(context),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Text(message.content), child: SelectableText(
message.content,
toolbarOptions: ToolbarOptions(
copy: true,
selectAll: false,
),
onTap: () {},
),
), ),
), ),
), ),
@@ -315,13 +343,16 @@ 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) ...[
Text('Editing ${editingMessageId}'),
],
Expanded( Expanded(
child: KeyboardListener( child: KeyboardListener(
focusNode: _inputFocusNode, focusNode: _inputFocusNode,
onKeyEvent: (event) { onKeyEvent: (event) {
// TODO: Probably doing this in the input focus node would be cleaner // TODO: Probably doing this in the input focus node would be cleaner
if (HardwareKeyboard.instance.isControlPressed && HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.enter)) { if (HardwareKeyboard.instance.isControlPressed && HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.enter)) {
sendMessage(_textController.text); sendMessage();
//return KeyEventResult.handled; //return KeyEventResult.handled;
} }
//return KeyEventResult.ignored; //return KeyEventResult.ignored;
@@ -338,7 +369,7 @@ class _ChatScreenState extends State<ChatScreen> {
children: [ children: [
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
sendMessage(_textController.text); sendMessage();
}, },
child: Text('Send'), child: Text('Send'),
), ),
@@ -349,6 +380,17 @@ class _ChatScreenState extends State<ChatScreen> {
}, },
child: Text('Start New Topic'), child: Text('Start New Topic'),
), ),
if (editingMessageId!= null) ...[
ElevatedButton(
onPressed: () {
_textController.text = "";
setState(() {
editingMessageId = null;
});
},
child: Text('Cancel Edit'),
)
],
], ],
), ),
], ],