summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrail Finder <wohilas@gmail.com>2026-02-25 16:57:55 +0300
committerGrail Finder <wohilas@gmail.com>2026-02-25 16:57:55 +0300
commit9f51bd385336e7b314316c372bc31de2a3c374f4 (patch)
treebaefc4ba8e5bec89d5df89c05be8d9df9d820025
parentb386c1181f0424418999fe143f1285d395b0db0f (diff)
Fix: text manipulation for multimodal messages
-rw-r--r--bot.go11
-rw-r--r--helpfuncs.go11
-rw-r--r--models/models.go60
-rw-r--r--tui.go20
4 files changed, 81 insertions, 21 deletions
diff --git a/bot.go b/bot.go
index 2d6af0c..bdf71b9 100644
--- a/bot.go
+++ b/bot.go
@@ -119,7 +119,7 @@ func processMessageTag(msg *models.RoleMsg) *models.RoleMsg {
}
// If KnownTo already set, assume tag already processed (content cleaned).
// However, we still check for new tags (maybe added later).
- knownTo := parseKnownToTag(msg.Content)
+ knownTo := parseKnownToTag(msg.GetText())
// If tag found, replace KnownTo with new list (merge with existing?)
// For simplicity, if knownTo is not nil, replace.
if knownTo == nil {
@@ -1303,12 +1303,9 @@ func removeThinking(chatBody *models.ChatBody) {
if msg.Role == cfg.ToolRole {
continue
}
- // find thinking and remove it
- rm := models.RoleMsg{
- Role: msg.Role,
- Content: thinkRE.ReplaceAllString(msg.Content, ""),
- }
- msgs = append(msgs, rm)
+ // find thinking and remove it - use SetText to preserve ContentParts
+ msg.SetText(thinkRE.ReplaceAllString(msg.GetText(), ""))
+ msgs = append(msgs, msg)
}
chatBody.Messages = msgs
}
diff --git a/helpfuncs.go b/helpfuncs.go
index 8719aab..99024a0 100644
--- a/helpfuncs.go
+++ b/helpfuncs.go
@@ -75,15 +75,16 @@ func stripThinkingFromMsg(msg *models.RoleMsg) *models.RoleMsg {
if !cfg.StripThinkingFromAPI {
return msg
}
- // Skip user, tool, and system messages - they might contain thinking examples
+ // Skip user, tool, they might contain thinking and system messages - examples
if msg.Role == cfg.UserRole || msg.Role == cfg.ToolRole || msg.Role == "system" {
return msg
}
// Strip thinking from assistant messages
- if thinkRE.MatchString(msg.Content) {
- msg.Content = thinkRE.ReplaceAllString(msg.Content, "")
- // Clean up any double newlines that might result
- msg.Content = strings.TrimSpace(msg.Content)
+ msgText := msg.GetText()
+ if thinkRE.MatchString(msgText) {
+ cleanedText := thinkRE.ReplaceAllString(msgText, "")
+ cleanedText = strings.TrimSpace(cleanedText)
+ msg.SetText(cleanedText)
}
return msg
}
diff --git a/models/models.go b/models/models.go
index ee13928..21d71d8 100644
--- a/models/models.go
+++ b/models/models.go
@@ -329,6 +329,66 @@ func (m *RoleMsg) Copy() RoleMsg {
}
}
+// GetText returns the text content of the message, handling both
+// simple Content and multimodal ContentParts formats.
+func (m *RoleMsg) GetText() string {
+ if !m.hasContentParts {
+ return m.Content
+ }
+ var textParts []string
+ for _, part := range m.ContentParts {
+ switch p := part.(type) {
+ case TextContentPart:
+ if p.Type == "text" {
+ textParts = append(textParts, p.Text)
+ }
+ case map[string]any:
+ if partType, exists := p["type"]; exists {
+ if partType == "text" {
+ if textVal, textExists := p["text"]; textExists {
+ if textStr, isStr := textVal.(string); isStr {
+ textParts = append(textParts, textStr)
+ }
+ }
+ }
+ }
+ }
+ }
+ return strings.Join(textParts, " ")
+}
+
+// SetText updates the text content of the message. If the message has
+// ContentParts (multimodal), it updates the text parts while preserving
+// images. If not, it sets the simple Content field.
+func (m *RoleMsg) SetText(text string) {
+ if !m.hasContentParts {
+ m.Content = text
+ return
+ }
+ var newParts []any
+ for _, part := range m.ContentParts {
+ switch p := part.(type) {
+ case TextContentPart:
+ if p.Type == "text" {
+ p.Text = text
+ newParts = append(newParts, p)
+ } else {
+ newParts = append(newParts, p)
+ }
+ case map[string]any:
+ if partType, exists := p["type"]; exists && partType == "text" {
+ p["text"] = text
+ newParts = append(newParts, p)
+ } else {
+ newParts = append(newParts, p)
+ }
+ default:
+ newParts = append(newParts, part)
+ }
+ }
+ m.ContentParts = newParts
+}
+
// AddTextPart adds a text content part to the message
func (m *RoleMsg) AddTextPart(text string) {
if !m.hasContentParts {
diff --git a/tui.go b/tui.go
index ca40c58..ddddd35 100644
--- a/tui.go
+++ b/tui.go
@@ -264,7 +264,7 @@ func init() {
pages.RemovePage(editMsgPage)
return nil
}
- chatBody.Messages[selectedIndex].Content = editedMsg
+ chatBody.Messages[selectedIndex].SetText(editedMsg)
// change textarea
textView.SetText(chatToText(chatBody.Messages, cfg.ShowSys))
pages.RemovePage(editMsgPage)
@@ -352,13 +352,14 @@ func init() {
case editMode:
hideIndexBar() // Hide overlay first
pages.AddPage(editMsgPage, editArea, true, true)
- editArea.SetText(m.Content, true)
+ editArea.SetText(m.GetText(), true)
default:
- if err := copyToClipboard(m.Content); err != nil {
+ msgText := m.GetText()
+ if err := copyToClipboard(msgText); err != nil {
logger.Error("failed to copy to clipboard", "error", err)
}
- previewLen := min(30, len(m.Content))
- notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:previewLen])
+ previewLen := min(30, len(msgText))
+ notification := fmt.Sprintf("msg '%s' was copied to the clipboard", msgText[:previewLen])
if err := notifyUser("copied", notification); err != nil {
logger.Error("failed to send notification", "error", err)
}
@@ -648,11 +649,12 @@ func init() {
// copy msg to clipboard
editMode = false
m := chatBody.Messages[len(chatBody.Messages)-1]
- if err := copyToClipboard(m.Content); err != nil {
+ msgText := m.GetText()
+ if err := copyToClipboard(msgText); err != nil {
logger.Error("failed to copy to clipboard", "error", err)
}
- previewLen := min(30, len(m.Content))
- notification := fmt.Sprintf("msg '%s' was copied to the clipboard", m.Content[:previewLen])
+ previewLen := min(30, len(msgText))
+ notification := fmt.Sprintf("msg '%s' was copied to the clipboard", msgText[:previewLen])
if err := notifyUser("copied", notification); err != nil {
logger.Error("failed to send notification", "error", err)
}
@@ -847,7 +849,7 @@ func init() {
// Stop any currently playing TTS first
TTSDoneChan <- true
lastMsg := chatBody.Messages[len(chatBody.Messages)-1]
- cleanedText := models.CleanText(lastMsg.Content)
+ cleanedText := models.CleanText(lastMsg.GetText())
if cleanedText != "" {
// nolint: errcheck
go orator.Speak(cleanedText)