mirror of
https://github.com/mudler/LocalAGI.git
synced 2026-07-23 18:55:52 -04:00
fix(slack): correct thread timestamp and improve file upload handling (#447)
Root cause: uploadJobResultFiles was passing msgTs (placeholder reply timestamp)
as thread_ts for file uploads. Slack's API rejects this - it requires the
parent thread timestamp, not a reply's timestamp.
Changes:
1. Use 'ts' (thread root timestamp) instead of 'msgTs' in replyToUpdateMessage
2. Fix type handling in attachmentsFromMetadataOnly to handle []interface{}
3. Download and upload generated images (e.g., DALL-E) as files instead of
just adding link attachments, to preserve temporary URLs
4. Remove dead code: generateAttachmentsFromJobResponse was never called
This matches Telegram connector behavior where files are properly uploaded
rather than just referenced by URL.
Co-authored-by: localai-bot <localai-bot@noreply.github.com>
This commit is contained in:
@@ -4,7 +4,9 @@ import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -176,25 +178,21 @@ func attachmentsFromMetadataOnly(metadata map[string]interface{}) (attachments [
|
||||
return nil
|
||||
}
|
||||
if urls, exists := metadata[actions.MetadataUrls]; exists {
|
||||
if sl, ok := urls.([]string); ok {
|
||||
for _, url := range xstrings.UniqueSlice(sl) {
|
||||
attachments = append(attachments, slack.Attachment{
|
||||
Title: "URL",
|
||||
TitleLink: url,
|
||||
Text: url,
|
||||
})
|
||||
}
|
||||
for _, url := range xstrings.UniqueSlice(stringSliceFromMetadata(urls)) {
|
||||
attachments = append(attachments, slack.Attachment{
|
||||
Title: "URL",
|
||||
TitleLink: url,
|
||||
Text: url,
|
||||
})
|
||||
}
|
||||
}
|
||||
if imagesUrls, exists := metadata[actions.MetadataImages]; exists {
|
||||
if sl, ok := imagesUrls.([]string); ok {
|
||||
for _, url := range xstrings.UniqueSlice(sl) {
|
||||
attachments = append(attachments, slack.Attachment{
|
||||
Title: "Image",
|
||||
TitleLink: url,
|
||||
ImageURL: url,
|
||||
})
|
||||
}
|
||||
for _, url := range xstrings.UniqueSlice(stringSliceFromMetadata(imagesUrls)) {
|
||||
attachments = append(attachments, slack.Attachment{
|
||||
Title: "Image",
|
||||
TitleLink: url,
|
||||
ImageURL: url,
|
||||
})
|
||||
}
|
||||
}
|
||||
return attachments
|
||||
@@ -295,6 +293,39 @@ func uploadFilesFromMetadata(metadata map[string]interface{}, api *slack.Client,
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle generated images (download from URL and upload as file, so temporary URLs like DALL-E are preserved)
|
||||
if imageUrls, exists := metadata[actions.MetadataImages]; exists {
|
||||
sl := stringSliceFromMetadata(imageUrls)
|
||||
for _, imgURL := range xstrings.UniqueSlice(sl) {
|
||||
resp, err := http.Get(imgURL)
|
||||
if err != nil {
|
||||
xlog.Error("Error downloading image for Slack upload", "url", imgURL, "error", err)
|
||||
continue
|
||||
}
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
xlog.Error("Error reading image body for Slack upload", "url", imgURL, "error", err)
|
||||
continue
|
||||
}
|
||||
if len(data) == 0 {
|
||||
xlog.Error("Empty image body for Slack upload", "url", imgURL)
|
||||
continue
|
||||
}
|
||||
_, err = api.UploadFileV2(slack.UploadFileV2Parameters{
|
||||
Reader: bytes.NewReader(data),
|
||||
FileSize: len(data),
|
||||
ThreadTimestamp: threadTs,
|
||||
Channel: channelID,
|
||||
Filename: "image.png",
|
||||
Title: "Generated image",
|
||||
InitialComment: "Generated image",
|
||||
})
|
||||
if err != nil {
|
||||
xlog.Error("Slack UploadFileV2 failed for image", "error", err, "url", imgURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// attachmentsAndUploadsFromMetadata returns link/image attachments and uploads files (songs, PDFs)
|
||||
@@ -329,16 +360,6 @@ func uploadJobResultFiles(res *types.JobResult, api *slack.Client, channelID, th
|
||||
}
|
||||
}
|
||||
|
||||
func generateAttachmentsFromJobResponse(j *types.JobResult, api *slack.Client, channelID, ts string) (attachments []slack.Attachment) {
|
||||
if j == nil {
|
||||
return nil
|
||||
}
|
||||
for _, state := range j.State {
|
||||
attachments = append(attachments, attachmentsAndUploadsFromMetadata(state.Metadata, api, channelID, ts)...)
|
||||
}
|
||||
return attachments
|
||||
}
|
||||
|
||||
// ImageData represents a single image with its metadata
|
||||
type ImageData struct {
|
||||
Data []byte
|
||||
@@ -641,7 +662,9 @@ func replyWithPostMessage(finalResponse string, api *slack.Client, ev *slackeven
|
||||
|
||||
func replyToUpdateMessage(finalResponse string, api *slack.Client, ev *slackevents.AppMentionEvent, msgTs string, ts string, postMessageParams slack.PostMessageParameters, res *types.JobResult) {
|
||||
attachments := attachmentsFromJobResponseOnly(res)
|
||||
uploadJobResultFiles(res, api, ev.Channel, msgTs)
|
||||
// Use the thread root timestamp (ts), not the placeholder reply timestamp (msgTs).
|
||||
// Slack API: "Never use a reply's ts value; use its parent instead."
|
||||
uploadJobResultFiles(res, api, ev.Channel, ts)
|
||||
if len(finalResponse) > 3000 {
|
||||
messages := xstrings.SplitParagraph(finalResponse, 3000)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user