feat(weixin): add workspace file handling and MIME type detection

- Implemented support for reading files from workspace URIs in the Weixin reply process, enhancing attachment management.
- Introduced a new `mimeFromExt` function to determine content types based on file extensions, improving file handling capabilities.
- Updated the `sendMediaFromURL` method to accommodate workspace file retrieval, ensuring robust error handling and logging for file operations.
This commit is contained in:
Max 2026-05-07 20:28:28 +08:00
parent f230f1e90c
commit 4376ac9dad

View file

@ -5,12 +5,14 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"path/filepath"
"strings" "strings"
agentcontext "github.com/yaoapp/yao/agent/context" agentcontext "github.com/yaoapp/yao/agent/context"
events "github.com/yaoapp/yao/agent/robot/events" events "github.com/yaoapp/yao/agent/robot/events"
"github.com/yaoapp/yao/attachment" "github.com/yaoapp/yao/attachment"
weixinapi "github.com/yaoapp/yao/integrations/weixin" weixinapi "github.com/yaoapp/yao/integrations/weixin"
"github.com/yaoapp/yao/workspace"
) )
func (a *Adapter) Reply(ctx context.Context, msg *agentcontext.Message, metadata *events.MessageMetadata) error { func (a *Adapter) Reply(ctx context.Context, msg *agentcontext.Message, metadata *events.MessageMetadata) error {
@ -168,7 +170,37 @@ func (a *Adapter) sendMediaFromURL(ctx context.Context, entry *botEntry, toUserI
var plaintext []byte var plaintext []byte
var contentType string var contentType string
if isWrapper(fileURL) { if strings.HasPrefix(fileURL, "workspace://") {
rest := strings.TrimPrefix(fileURL, "workspace://")
slashIdx := strings.Index(rest, "/")
if slashIdx < 0 {
return fmt.Errorf("invalid workspace URL: %s", fileURL)
}
wsID := rest[:slashIdx]
filePath := rest[slashIdx+1:]
wsm := workspace.M()
if wsm == nil {
return fmt.Errorf("workspace manager not initialized")
}
wsFS, err := wsm.FS(ctx, wsID)
if err != nil {
return fmt.Errorf("open workspace %s: %w", wsID, err)
}
defer wsFS.Close()
plaintext, err = wsFS.ReadFile(filePath)
if err != nil {
return fmt.Errorf("read workspace file %s/%s: %w", wsID, filePath, err)
}
contentType = mimeFromExt(filepath.Ext(filePath))
if fileName == "" {
fileName = filepath.Base(filePath)
}
log.Info("weixin sendMedia: workspace read bytes=%d contentType=%q fileName=%q", len(plaintext), contentType, fileName)
} else if isWrapper(fileURL) {
managerName, fileID, err := parseWrapper(fileURL) managerName, fileID, err := parseWrapper(fileURL)
if err != nil { if err != nil {
return err return err
@ -291,6 +323,37 @@ func toContentParts(content interface{}) ([]agentcontext.ContentPart, bool) {
return parts, ok return parts, ok
} }
func mimeFromExt(ext string) string {
switch strings.ToLower(ext) {
case ".pdf":
return "application/pdf"
case ".html", ".htm":
return "text/html"
case ".png":
return "image/png"
case ".jpg", ".jpeg":
return "image/jpeg"
case ".gif":
return "image/gif"
case ".webp":
return "image/webp"
case ".md":
return "text/markdown"
case ".txt":
return "text/plain"
case ".csv":
return "text/csv"
case ".json":
return "application/json"
case ".xlsx":
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
case ".pptx":
return "application/vnd.openxmlformats-officedocument.presentationml.presentation"
default:
return "application/octet-stream"
}
}
func (a *Adapter) resolveByAccountID(accountID string) *botEntry { func (a *Adapter) resolveByAccountID(accountID string) *botEntry {
if accountID == "" { if accountID == "" {
return nil return nil