messagesStore.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. /**
  2. * Centralized message state mutation utilities
  3. * Eliminates duplicate array cloning logic and type casting from MessagesProvider
  4. */
  5. import type { Message, WebguiPart, SDKMessage, TextPart } from "../types/messages"
  6. /**
  7. * Upsert a message (add new or update existing)
  8. */
  9. export function upsertMessage(messages: Message[], message: Message): Message[] {
  10. const index = messages.findIndex((m) => m.info.id === message.info.id)
  11. if (index >= 0) {
  12. const updated = [...messages]
  13. updated[index] = message
  14. return updated
  15. }
  16. return [...messages, message]
  17. }
  18. /**
  19. * Update message info while preserving parts
  20. */
  21. export function updateMessageInfo(messages: Message[], messageID: string, info: SDKMessage): Message[] {
  22. const index = messages.findIndex((m) => m.info.id === messageID)
  23. if (index >= 0) {
  24. const updated = [...messages]
  25. updated[index] = { ...updated[index], info }
  26. return updated
  27. }
  28. // Create new message if not found
  29. return [...messages, { info, parts: [] }]
  30. }
  31. /**
  32. * Update message with partial data
  33. */
  34. export function updateMessage(messages: Message[], messageID: string, update: Partial<Message>): Message[] {
  35. const index = messages.findIndex((m) => m.info.id === messageID)
  36. if (index < 0) return messages
  37. const updated = [...messages]
  38. updated[index] = { ...updated[index], ...update }
  39. return updated
  40. }
  41. /**
  42. * Remove a message by ID
  43. */
  44. export function removeMessage(messages: Message[], messageID: string): Message[] {
  45. return messages.filter((m) => m.info.id !== messageID)
  46. }
  47. /**
  48. * Upsert a part in a message (add new or update existing)
  49. */
  50. export function upsertPart(messages: Message[], messageID: string, part: WebguiPart): Message[] {
  51. const messageIndex = messages.findIndex((m) => m.info.id === messageID)
  52. if (messageIndex < 0) return messages
  53. const updated = [...messages]
  54. const message = updated[messageIndex]
  55. const partIndex = message.parts.findIndex((p) => p.id === part.id)
  56. if (partIndex >= 0) {
  57. // Update existing part
  58. const updatedParts = [...message.parts]
  59. updatedParts[partIndex] = part
  60. updated[messageIndex] = { ...message, parts: updatedParts }
  61. } else {
  62. // Add new part
  63. updated[messageIndex] = { ...message, parts: [...message.parts, part] }
  64. }
  65. return updated
  66. }
  67. /**
  68. * Apply a text delta to a part (for streaming)
  69. */
  70. export function applyPartDelta(messages: Message[], messageID: string, part: WebguiPart, delta: string): Message[] {
  71. if (part.type !== "text") {
  72. // For non-text parts, just upsert normally
  73. return upsertPart(messages, messageID, part)
  74. }
  75. const messageIndex = messages.findIndex((m) => m.info.id === messageID)
  76. if (messageIndex < 0) return messages
  77. const updated = [...messages]
  78. const message = updated[messageIndex]
  79. const partIndex = message.parts.findIndex((p) => p.id === part.id)
  80. if (partIndex >= 0) {
  81. // Append delta to existing text part
  82. const existingPart = message.parts[partIndex]
  83. if (existingPart.type === "text") {
  84. const updatedParts = [...message.parts]
  85. updatedParts[partIndex] = {
  86. ...existingPart,
  87. text: (existingPart.text || "") + delta,
  88. }
  89. updated[messageIndex] = { ...message, parts: updatedParts }
  90. }
  91. } else {
  92. // New part with delta as initial text
  93. const newPart: TextPart = { ...(part as TextPart), text: delta }
  94. updated[messageIndex] = { ...message, parts: [...message.parts, newPart] }
  95. }
  96. return updated
  97. }
  98. /**
  99. * Update a specific part in a message
  100. */
  101. export function updatePart(messages: Message[], messageID: string, partID: string, update: Partial<WebguiPart>): Message[] {
  102. const messageIndex = messages.findIndex((m) => m.info.id === messageID)
  103. if (messageIndex < 0) return messages
  104. const updated = [...messages]
  105. const message = updated[messageIndex]
  106. const partIndex = message.parts.findIndex((p) => p.id === partID)
  107. if (partIndex < 0) return messages
  108. const updatedParts = [...message.parts]
  109. updatedParts[partIndex] = { ...updatedParts[partIndex], ...update } as WebguiPart
  110. updated[messageIndex] = { ...message, parts: updatedParts }
  111. return updated
  112. }
  113. /**
  114. * Remove a part from a message
  115. */
  116. export function removePart(messages: Message[], messageID: string, partID: string): Message[] {
  117. const messageIndex = messages.findIndex((m) => m.info.id === messageID)
  118. if (messageIndex < 0) return messages
  119. const updated = [...messages]
  120. const message = updated[messageIndex]
  121. updated[messageIndex] = {
  122. ...message,
  123. parts: message.parts.filter((p) => p.id !== partID),
  124. }
  125. return updated
  126. }
  127. /**
  128. * Get messages for a specific session
  129. */
  130. export function getMessagesBySession(messages: Message[], sessionID: string): Message[] {
  131. return messages.filter((m) => m.info.sessionID === sessionID)
  132. }