223 lines
6.6 KiB
Go
223 lines
6.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
afterSalesFileContentLimit = 6000
|
|
afterSalesFilePromptLimit = 1800
|
|
|
|
afterSalesFileStatusReady = "parsed"
|
|
afterSalesFileStatusSaved = "saved"
|
|
afterSalesFileStatusUnsupported = "unsupported"
|
|
afterSalesFileStatusMissing = "missing"
|
|
afterSalesFileStatusDownloadFail = "download_failed"
|
|
afterSalesFileStatusExtractFailed = "extract_failed"
|
|
)
|
|
|
|
func extractAfterSalesFileFromMessage(msg autoReplyMessage, raw map[string]interface{}, sourceMessageID string) AfterSalesFileAttachment {
|
|
if !looksLikeAfterSalesFileMessage(msg, raw) {
|
|
return AfterSalesFileAttachment{}
|
|
}
|
|
attachment := AfterSalesFileAttachment{
|
|
Name: strings.TrimSpace(msg.MediaFileName),
|
|
Ref: firstNonEmpty(strings.TrimSpace(msg.MediaURL), strings.TrimSpace(msg.MediaFileID)),
|
|
SourceMessageID: strings.TrimSpace(sourceMessageID),
|
|
}
|
|
for _, value := range []string{msg.MediaLocalPath, firstLocalMediaPathFromValue(raw)} {
|
|
if path := normalizedExistingFilePath(value); path != "" {
|
|
attachment.Path = path
|
|
break
|
|
}
|
|
}
|
|
if attachment.Path == "" {
|
|
if path, err := ensureAutoReplyMediaLocalPath(msg); err == nil {
|
|
attachment.Path = normalizedExistingFilePath(path)
|
|
} else {
|
|
attachment.ExtractStatus = afterSalesFileStatusDownloadFail
|
|
}
|
|
}
|
|
if attachment.Name == "" {
|
|
attachment.Name = firstNonEmpty(filepath.Base(attachment.Path), filepath.Base(strings.TrimSpace(msg.MediaURL)), strings.TrimSpace(msg.MediaFileID), "客户附件")
|
|
}
|
|
if attachment.Path == "" {
|
|
if attachment.ExtractStatus == "" {
|
|
attachment.ExtractStatus = afterSalesFileStatusDownloadFail
|
|
}
|
|
return attachment
|
|
}
|
|
content, status := extractAfterSalesFileContent(attachment.Path)
|
|
attachment.Content = content
|
|
attachment.ExtractStatus = status
|
|
return normalizeAfterSalesFileAttachment(attachment)
|
|
}
|
|
|
|
func looksLikeAfterSalesFileMessage(msg autoReplyMessage, raw map[string]interface{}) bool {
|
|
if strings.TrimSpace(msg.MediaKind) == "file" || msg.RawType == 11045 {
|
|
return true
|
|
}
|
|
if raw != nil {
|
|
if typeVal, ok := raw["type"]; ok && intFromAny(typeVal) == 11045 {
|
|
return true
|
|
}
|
|
if event := strings.TrimSpace(stringFromAny(raw["event"])); event == "20005" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func normalizedExistingFilePath(value string) string {
|
|
value = strings.Trim(strings.TrimSpace(value), "\"'")
|
|
if value == "" {
|
|
return ""
|
|
}
|
|
if filepath.IsAbs(value) {
|
|
if _, err := os.Stat(value); err == nil {
|
|
return value
|
|
}
|
|
return ""
|
|
}
|
|
candidate := resolveAutoReplyPath(value)
|
|
if _, err := os.Stat(candidate); err == nil {
|
|
return candidate
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func extractAfterSalesFileContent(path string) (string, string) {
|
|
if strings.TrimSpace(path) == "" {
|
|
return "", afterSalesFileStatusMissing
|
|
}
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return "", afterSalesFileStatusMissing
|
|
}
|
|
if info.IsDir() {
|
|
return "", afterSalesFileStatusUnsupported
|
|
}
|
|
ext := strings.ToLower(filepath.Ext(path))
|
|
if ext == ".zip" {
|
|
return "", "zip_not_parsed"
|
|
}
|
|
switch ext {
|
|
case ".txt", ".md", ".csv", ".xlsx", ".docx", ".pdf":
|
|
default:
|
|
return "", afterSalesFileStatusUnsupported
|
|
}
|
|
blocks, err := parseKnowledgeFile(path, filepath.Dir(path))
|
|
if err != nil {
|
|
var warning knowledgeParseWarning
|
|
if !errorAs(err, &warning) {
|
|
return "", afterSalesFileStatusExtractFailed + ": " + truncateText(err.Error(), 160)
|
|
}
|
|
}
|
|
parts := make([]string, 0, len(blocks))
|
|
for _, block := range blocks {
|
|
text := strings.TrimSpace(block.Content)
|
|
if text == "" {
|
|
continue
|
|
}
|
|
if strings.TrimSpace(block.Title) != "" {
|
|
text = strings.TrimSpace(block.Title) + "\n" + text
|
|
}
|
|
parts = append(parts, text)
|
|
}
|
|
content := truncateText(strings.TrimSpace(strings.Join(parts, "\n\n")), afterSalesFileContentLimit)
|
|
if content == "" {
|
|
return "", afterSalesFileStatusSaved
|
|
}
|
|
return content, afterSalesFileStatusReady
|
|
}
|
|
|
|
func normalizeAfterSalesFileAttachment(item AfterSalesFileAttachment) AfterSalesFileAttachment {
|
|
item.Name = strings.TrimSpace(item.Name)
|
|
item.Path = strings.Trim(strings.TrimSpace(item.Path), "\"'")
|
|
item.Ref = strings.TrimSpace(item.Ref)
|
|
item.Content = truncateText(strings.TrimSpace(item.Content), afterSalesFileContentLimit)
|
|
item.ExtractStatus = strings.TrimSpace(item.ExtractStatus)
|
|
item.SourceMessageID = strings.TrimSpace(item.SourceMessageID)
|
|
if item.Name == "" {
|
|
item.Name = firstNonEmpty(filepath.Base(item.Path), filepath.Base(item.Ref), "客户附件")
|
|
}
|
|
if item.ExtractStatus == "" {
|
|
if item.Content != "" {
|
|
item.ExtractStatus = afterSalesFileStatusReady
|
|
} else if item.Path != "" {
|
|
item.ExtractStatus = afterSalesFileStatusSaved
|
|
} else {
|
|
item.ExtractStatus = afterSalesFileStatusDownloadFail
|
|
}
|
|
}
|
|
return item
|
|
}
|
|
|
|
func normalizeAfterSalesFileAttachments(items []AfterSalesFileAttachment) []AfterSalesFileAttachment {
|
|
seen := make(map[string]struct{})
|
|
result := make([]AfterSalesFileAttachment, 0, len(items))
|
|
for _, item := range items {
|
|
item = normalizeAfterSalesFileAttachment(item)
|
|
if item.Path == "" && item.Ref == "" && item.Name == "" && item.Content == "" {
|
|
continue
|
|
}
|
|
key := firstNonEmpty(item.SourceMessageID, item.Path, item.Ref, item.Name+"|"+item.Content)
|
|
if _, exists := seen[key]; exists {
|
|
continue
|
|
}
|
|
seen[key] = struct{}{}
|
|
result = append(result, item)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func afterSalesFilePromptText(files []AfterSalesFileAttachment, limit int) string {
|
|
if limit <= 0 {
|
|
limit = afterSalesFilePromptLimit
|
|
}
|
|
var b strings.Builder
|
|
for _, file := range normalizeAfterSalesFileAttachments(files) {
|
|
if b.Len() > 0 {
|
|
b.WriteString("\n")
|
|
}
|
|
b.WriteString("文件:")
|
|
b.WriteString(firstNonEmpty(file.Name, filepath.Base(file.Path), file.Ref))
|
|
if file.ExtractStatus != "" {
|
|
b.WriteString(" 状态:")
|
|
b.WriteString(file.ExtractStatus)
|
|
}
|
|
if file.Content != "" {
|
|
b.WriteString("\n内容:\n")
|
|
b.WriteString(truncateText(file.Content, limit))
|
|
}
|
|
}
|
|
return strings.TrimSpace(b.String())
|
|
}
|
|
|
|
func formatAfterSalesFileAttachmentsForExcel(files []AfterSalesFileAttachment, includeContent bool) string {
|
|
files = normalizeAfterSalesFileAttachments(files)
|
|
if len(files) == 0 {
|
|
return ""
|
|
}
|
|
lines := make([]string, 0, len(files))
|
|
for _, file := range files {
|
|
line := firstNonEmpty(file.Name, filepath.Base(file.Path), file.Ref, "客户附件")
|
|
if file.Path != "" {
|
|
line += " | " + file.Path
|
|
} else if file.Ref != "" {
|
|
line += " | " + file.Ref
|
|
}
|
|
if file.ExtractStatus != "" {
|
|
line += " | " + file.ExtractStatus
|
|
}
|
|
if includeContent && file.Content != "" {
|
|
line += fmt.Sprintf("\n%s", truncateText(file.Content, 1200))
|
|
}
|
|
lines = append(lines, line)
|
|
}
|
|
return strings.Join(lines, "\n\n")
|
|
}
|