diff --git a/examples/tui/tui.nim b/examples/tui/tui.nim index 15adfa7..9ce55c4 100644 --- a/examples/tui/tui.nim +++ b/examples/tui/tui.nim @@ -35,6 +35,8 @@ type sender: string content: string timestamp: DateTime + id: string + isAcknowledged: bool ConvoInfo = object name: string @@ -71,13 +73,15 @@ proc `==`(a,b: ConvoInfo):bool = # Data Management ################################################# -proc addMessage(conv: var ConvoInfo, sender: string, content: string) = +proc addMessage(conv: var ConvoInfo, messageId: MessageId, sender: string, content: string) = let now = now() conv.messages.add(Message( sender: sender, + id: messageId, content: content, - timestamp: now + timestamp: now, + isAcknowledged: false )) conv.lastMsgTime = now @@ -117,10 +121,13 @@ proc createConvo(app: ChatApp) {.async.} = discard await app.client.newPrivateConversation(toBundle(app.inputInviteBuffer.strip()).get()) proc sendMessage(app: ChatApp, convoInfo: ptr ConvoInfo, msg: string) {.async.} = - convoInfo[].addMessage("You", app.inputBuffer) + var msgId = "" if convoInfo.convo != nil: - await convoInfo.convo.sendMessage(app.client.ds, initTextFrame(msg).toContentFrame()) + msgId = await convoInfo.convo.sendMessage(app.client.ds, initTextFrame(msg).toContentFrame()) + + convoInfo[].addMessage(msgId, "You", app.inputBuffer) + proc setupChatSdk(app: ChatApp) = @@ -149,6 +156,16 @@ proc setupChatSdk(app: ChatApp) = app.client.onDeliveryAck(proc(convo: Conversation, msgId: string) {.async.} = info "DeliveryAck", msgId=msgId app.logMsgs.add(LogEntry(level: "info",ts: now(), msg: fmt"Ack:{msgId}")) + + var s = "" + var msgs = addr app.conversations[convo.id()].messages + for i in countdown(msgs[].high, 0): + s = fmt"{s},{msgs[i].id}" + var m = addr msgs[i] + + if m.id == msgId: + m.isAcknowledged = true + break # Stop after ) @@ -265,11 +282,12 @@ proc drawMsgPane( app: ChatApp, layout: Pane) = let m = convo.messages[i] let timeStr = m.timestamp.format("HH:mm:ss") - + var deliveryIcon = " " var remainingText = m.content if m.sender == "You": tb.setForegroundColor(fgYellow) + deliveryIcon = if m.isAcknowledged: "✔" else: "◯" else: tb.setForegroundColor(fgGreen) @@ -278,7 +296,7 @@ proc drawMsgPane( app: ChatApp, layout: Pane) = app.logMsgs.add(LogEntry(level: "info",ts: now(), msg: fmt" TOO LONG: {convo.name}")) return - tb.write(x, y, fmt"[{timeStr}] {m.sender}") + tb.write(x, y, fmt"[{timeStr}] {deliveryIcon} {m.sender}") y = y + 1 while remainingText.len > 0: @@ -300,11 +318,12 @@ proc drawMsgPane( app: ChatApp, layout: Pane) = let m = convo.messages[i] let timeStr = m.timestamp.format("HH:mm:ss") - + var deliveryIcon = " " var remainingText = m.content if m.sender == "You": tb.setForegroundColor(fgYellow) + deliveryIcon = if m.isAcknowledged: "✔" else: "◯" else: tb.setForegroundColor(fgGreen) @@ -322,7 +341,7 @@ proc drawMsgPane( app: ChatApp, layout: Pane) = tb.write(x+3, y, line) y = y - 1 - tb.write(x, y, fmt"[{timeStr}] {m.sender}") + tb.write(x, y, fmt"[{timeStr}] {deliveryIcon} {m.sender}") y = y - 2 proc drawMsgInput( app: ChatApp, layout: Pane) = @@ -528,59 +547,10 @@ proc initChatApp(client: Client): Future[ChatApp] {.async.} = # Add some sample conversations with messages - var conv1 = ConvoInfo(name: "Alice", messages: @[]) - conv1.addMessage("36Alice", "Hey there! How are you doing?") - conv1.addMessage("35You", "I'm doing well, thanks! Working on a new project.") - conv1.addMessage("34Alice", "That sounds exciting! What kind of project?") - conv1.addMessage("33You", "A terminal-based chat application in Nim!") - conv1.addMessage("32Alice", "Hey there! How are you doing?") - conv1.addMessage("31You", "I'm doing well, thanks! Working on a new project.") - conv1.addMessage("30Alice", "That sounds exciting! What kind of project?") - conv1.addMessage("29You", "A terminal-based chat application in Nim!") - conv1.addMessage("28Alice", "Hey there! How are you doing?") - conv1.addMessage("27You", "I'm doing well, thanks! Working on a new project.") - conv1.addMessage("26Alice", "That sounds exciting! What kind of project?") - conv1.addMessage("25You", "A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim! A terminal-based chat application in Nim!") - conv1.addMessage("24Alice", "Hey there! How are you doing?") - conv1.addMessage("23You", "I'm doing well, thanks! Working on a new project.") - conv1.addMessage("22Alice", "That sounds exciting! What kind of project?") - conv1.addMessage("21You", "A terminal-based chat application in Nim!") - conv1.addMessage("20Alice", "Hey there! How are you doing?") - conv1.addMessage("19You", "I'm doing well, thanks! Working on a new project.") - conv1.addMessage("18Alice", "That sounds exciting! What kind of project?") - conv1.addMessage("17You", "A terminal-based chat application in Nim!") - conv1.addMessage("16Alice", "Hey there! How are you doing?") - conv1.addMessage("15You", "I'm doing well, thanks! Working on a new project.") - conv1.addMessage("14Alice", "That sounds exciting! What kind of project?") - conv1.addMessage("13You", "A terminal-based chat application in Nim!") - conv1.addMessage("12Alice", "Hey there! How are you doing?") - conv1.addMessage("11You", "I'm doing well, thanks! Working on a new project.") - conv1.addMessage("10Alice", "That sounds exciting! What kind of project?") - conv1.addMessage("9You", "The system architecture consists of three main components: the client interface, the processing engine, and the data storage layer. Each component communicates through well-defined APIs that ensure scalability and maintainability. The client interface handles user interactions and validates input data before forwarding requests to the processing engine.") - conv1.addMessage("8Alice", "Hey there! How are you doing?") - conv1.addMessage("7You", "I'm doing well, thanks! Working on a new project.") - conv1.addMessage("6Alice", "That sounds exciting! What kind of project?") - conv1.addMessage("5You", "A terminal-based chat application in Nim!") - conv1.addMessage("4Alice", "Hey there! How are you doing?") - conv1.addMessage("3You", "I'm doing well, thanks! Working on a new project.") - conv1.addMessage("2Alice", "That sounds exciting! What kind of project?") - conv1.addMessage("1You", "A terminal-based chat application in Nim!") + var conv1 = ConvoInfo(name: "ReadMe", messages: @[]) + conv1.addMessage("","Bob", "TODO") app.conversations[conv1.name] = conv1 - var conv2 = ConvoInfo(name: "Bob", messages: @[]) - conv2.addMessage("Bob", "Did you see the game last night?") - conv2.addMessage("You", "The system architecture consists of three main components: the client interface, the processing engine, and the data storage layer. Each component communicates through well-defined APIs that ensure scalability and maintainability. The client interface handles user interactions and validates input data before forwarding requests to the processing engine") - conv2.addMessage("Bob", "The home team crushed it! 3-0") - app.conversations[conv2.name] = conv2 - - var conv3 = ConvoInfo(name: "Development Team", messages: @[]) - conv3.addMessage("Sarah", "Morning standup in 10 minutes") - conv3.addMessage("Mike", "I'll be there. Just finishing up the last bug fix.") - conv3.addMessage("You", "On my way!") - conv3.addMessage("Sarah", "Great! Let's review the sprint progress today.") - app.conversations[conv3.name] = conv3 - - return app diff --git a/src/chat_sdk.nim b/src/chat_sdk.nim index 4d8ef69..957117e 100644 --- a/src/chat_sdk.nim +++ b/src/chat_sdk.nim @@ -4,11 +4,12 @@ import chat_sdk/[ delivery/waku_client, identity, links, - proto_types + proto_types, + types ] export client, conversations, waku_client, identity, links #export specific frames need by applications -export ContentFrame +export ContentFrame, MessageId diff --git a/src/chat_sdk/client.nim b/src/chat_sdk/client.nim index c3b9fff..cf09226 100644 --- a/src/chat_sdk/client.nim +++ b/src/chat_sdk/client.nim @@ -231,8 +231,6 @@ proc newPrivateConversation*(client: Client, proc parseMessage(client: Client, msg: ChatPayload) {.raises: [ValueError, SerializationError].} = ## Receives a incoming payload, decodes it, and processes it. - info "Parse", clientId = client.getId(), msg = msg, - contentTopic = msg.contentTopic let envelope = decode(msg.bytes, WapEnvelopeV1).valueOr: raise newException(ValueError, "Failed to decode WapEnvelopeV1: " & error) diff --git a/src/chat_sdk/conversations/convo_type.nim b/src/chat_sdk/conversations/convo_type.nim index 66caf05..3be91c1 100644 --- a/src/chat_sdk/conversations/convo_type.nim +++ b/src/chat_sdk/conversations/convo_type.nim @@ -5,6 +5,7 @@ import strutils import ../proto_types import ../delivery/waku_client import ../utils +import ../types type ConvoTypes* = enum @@ -25,6 +26,6 @@ method id*(self: Conversation): string {.raises: [Defect, ValueError].} = panic("ProgramError: Missing concrete implementation") method sendMessage*(convo: Conversation, ds: WakuClient, - content_frame: ContentFrame) {.async, base, gcsafe.} = + content_frame: ContentFrame) : Future[MessageId] {.async, base, gcsafe.} = # TODO: make this a compile time check panic("ProgramError: Missing concrete implementation") diff --git a/src/chat_sdk/conversations/private_v1.nim b/src/chat_sdk/conversations/private_v1.nim index 00d2762..d64ce99 100644 --- a/src/chat_sdk/conversations/private_v1.nim +++ b/src/chat_sdk/conversations/private_v1.nim @@ -116,7 +116,7 @@ proc initPrivateV1*(owner: Identity, participant: PublicKey, raise newException(ValueError, "bad sds channel") proc sendFrame(self: PrivateV1, ds: WakuClient, - msg: PrivateV1Frame): Future[void]{.async.} = + msg: PrivateV1Frame): Future[MessageId]{.async.} = let frameBytes = encode(msg) let msgId = self.calcMsgId(frameBytes) @@ -130,6 +130,9 @@ proc sendFrame(self: PrivateV1, ds: WakuClient, discard ds.sendPayload(self.getTopic(), encryptedBytes.toEnvelope( self.getConvoId())) + result = msgId + + method id*(self: PrivateV1): string = return getConvoIdRaw(self.participants, self.discriminator) @@ -166,13 +169,13 @@ proc handleFrame*[T: ConversationStore](convo: PrivateV1, client: T, method sendMessage*(convo: PrivateV1, ds: WakuClient, - content_frame: ContentFrame) {.async.} = + content_frame: ContentFrame) : Future[MessageId] {.async.} = try: let frame = PrivateV1Frame(sender: @(convo.owner.getPubkey().bytes()), timestamp: getCurrentTimestamp(), content: content_frame) - await convo.sendFrame(ds, frame) + result = await convo.sendFrame(ds, frame) except Exception as e: error "Unknown error in PrivateV1:SendMessage" diff --git a/src/chat_sdk/inbox.nim b/src/chat_sdk/inbox.nim index fe933dc..d31dac5 100644 --- a/src/chat_sdk/inbox.nim +++ b/src/chat_sdk/inbox.nim @@ -11,6 +11,7 @@ import crypto, delivery/waku_client, proto_types, + types, utils logScope: @@ -104,5 +105,6 @@ proc handleFrame*[T: ConversationStore](convo: Inbox, client: T, bytes: seq[ method sendMessage*(convo: Inbox, ds: WakuClient, - content_frame: ContentFrame) {.async.} = + content_frame: ContentFrame) : Future[MessageId] {.async.} = warn "Cannot send message to Inbox" + result = "program_error"