mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
bc374fe8cd
* refactor(attachment): multimodal attachment refactor with snapshot schema and storage layer - Add snapshot schema migration (0008) and update init/versions/snapshots - Add internal/attachment and internal/channel normalize for unified attachment handling - Move containerfs provider from internal/media to internal/storage - Update agent types, channel adapters (Telegram/Feishu), inbound and handlers - Add containerd snapshot lineage and local_channel tests - Regenerate sqlc, swagger and SDK * refactor(media): content-addressed asset system with unified naming - Replace asset_id foreign key with content_hash as sole identifier for bot_history_message_assets (pure soft-link model) - Remove mime, size_bytes, storage_key from DB; derive at read time via media.Resolve from actual storage - Merge migrations 0008/0009 into single 0008; keep 0001 as canonical schema - Add Docker initdb script for deterministic migration execution order - Fix cross-channel real-time image display (Telegram → WebUI SSE) - Fix message disappearing on refresh (null assets fallback) - Fix file icon instead of image preview (mime derivation from storage) - Unify AssetID → ContentHash naming across Go, Agent, and Frontend - Change storage key prefix from 4-char to 2-char for directory sharding - Add server-entrypoint.sh for Docker deployment migration handling * refactor(infra): embedded migrations, Docker simplification, and config consolidation - Embed SQL migrations into Go binary, removing shell-based migration scripts - Consolidate config files into conf/ directory (app.example.toml, app.docker.toml, app.dev.toml) - Simplify Docker setup: remove initdb.d scripts, streamline nginx config and entrypoint - Remove legacy CLI, feishu-echo commands, and obsolete incremental migration files - Update install script and docs to require sudo for one-click install - Add mise tasks for dev environment orchestration * chore: recover migrations --------- Co-authored-by: Acbox <acbox0328@gmail.com>
103 lines
3.3 KiB
Go
103 lines
3.3 KiB
Go
package flow
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/memohai/memoh/internal/models"
|
|
)
|
|
|
|
const (
|
|
gatewayTransportInlineDataURL = "inline_data_url"
|
|
gatewayTransportPublicURL = "public_url"
|
|
gatewayTransportToolFileRef = "tool_file_ref"
|
|
)
|
|
|
|
// attachmentModality maps an attachment type string to the input modality it requires.
|
|
var attachmentModality = map[string]string{
|
|
"image": models.ModelInputImage,
|
|
"audio": models.ModelInputAudio,
|
|
"video": models.ModelInputVideo,
|
|
"file": models.ModelInputFile,
|
|
}
|
|
|
|
// gatewayAttachment is the strict server-to-gateway attachment contract.
|
|
// ContentHash is the content reference (replaces legacy assetId).
|
|
type gatewayAttachment struct {
|
|
ContentHash string `json:"contentHash,omitempty"`
|
|
Type string `json:"type"`
|
|
Mime string `json:"mime,omitempty"`
|
|
Size int64 `json:"size,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Transport string `json:"transport"`
|
|
Payload string `json:"payload"`
|
|
Metadata map[string]any `json:"metadata,omitempty"`
|
|
|
|
// FallbackPath is an internal helper only used by server-side routing.
|
|
FallbackPath string `json:"-"`
|
|
}
|
|
|
|
// capabilityRouteResult holds the outcome of splitting attachments by model capability.
|
|
type capabilityRouteResult struct {
|
|
// Native are attachments the model can consume directly as multimodal input.
|
|
Native []gatewayAttachment
|
|
// Fallback are attachments whose modality is unsupported; they are converted
|
|
// to container file path references for the LLM to access via tools.
|
|
Fallback []gatewayAttachment
|
|
}
|
|
|
|
// routeAttachmentsByCapability splits attachments based on both model capability
|
|
// and gateway native support. Unsupported items are routed through fallback.
|
|
func routeAttachmentsByCapability(modalities []string, attachments []gatewayAttachment) capabilityRouteResult {
|
|
supported := make(map[string]struct{}, len(modalities))
|
|
for _, m := range modalities {
|
|
supported[strings.ToLower(strings.TrimSpace(m))] = struct{}{}
|
|
}
|
|
|
|
result := capabilityRouteResult{
|
|
Native: make([]gatewayAttachment, 0, len(attachments)),
|
|
Fallback: make([]gatewayAttachment, 0),
|
|
}
|
|
for _, att := range attachments {
|
|
att.Type = strings.ToLower(strings.TrimSpace(att.Type))
|
|
att.Transport = strings.ToLower(strings.TrimSpace(att.Transport))
|
|
requiredModality, known := attachmentModality[att.Type]
|
|
if !known {
|
|
// Unknown attachment types always go through fallback path.
|
|
result.Fallback = append(result.Fallback, att)
|
|
continue
|
|
}
|
|
if !isGatewayNativeAttachment(att) {
|
|
result.Fallback = append(result.Fallback, att)
|
|
continue
|
|
}
|
|
if _, ok := supported[requiredModality]; ok {
|
|
result.Native = append(result.Native, att)
|
|
} else {
|
|
result.Fallback = append(result.Fallback, att)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func isGatewayNativeAttachment(att gatewayAttachment) bool {
|
|
switch att.Type {
|
|
case "image":
|
|
transport := strings.ToLower(strings.TrimSpace(att.Transport))
|
|
if transport != gatewayTransportInlineDataURL && transport != gatewayTransportPublicURL {
|
|
return false
|
|
}
|
|
return strings.TrimSpace(att.Payload) != ""
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// attachmentsToAny converts typed gateway attachments to []any for JSON serialization.
|
|
func attachmentsToAny(atts []gatewayAttachment) []any {
|
|
out := make([]any, 0, len(atts))
|
|
for _, a := range atts {
|
|
out = append(out, a)
|
|
}
|
|
return out
|
|
}
|