package application import ( "context" "fmt" "time" "github.com/mmmy/snapgo/internal/domain" "github.com/mmmy/snapgo/internal/infrastructure/clipboard" ) // VisionSummarizer describes a multimodal model capable of reading a public // image URL and returning a textual summary. type VisionSummarizer interface { SummarizeImage(ctx context.Context, prompt string, imageURL string) (string, error) } // CaptureSummaryService wires screenshot bytes -> S3 public URL -> LLM // summary -> clipboard. It intentionally does not notify UI state itself: // the caller emits fine-grained "uploading / recognizing / done" progress. type CaptureSummaryService struct { Provider domain.OSSProvider Summarizer VisionSummarizer Clipboard clipboard.Writer Prompt string PathPrefix string } // UploadImage uploads the screenshot and returns the public URL visible to // the multimodal provider. func (s *CaptureSummaryService) UploadImage(ctx context.Context, pngBytes []byte) (*domain.UploadResult, error) { if s.Provider == nil { return nil, fmt.Errorf("s3 is not configured") } if len(pngBytes) == 0 { return nil, fmt.Errorf("empty screenshot") } key := buildObjectKey(s.PathPrefix) start := time.Now() url, err := s.Provider.Upload(ctx, key, pngBytes, "image/png") if err != nil { return nil, fmt.Errorf("s3 put object: %w", err) } return &domain.UploadResult{ URL: url, Key: key, Provider: s.Provider.Name(), Elapsed: time.Since(start), }, nil } // Summarize asks the configured model to summarize the uploaded image URL. func (s *CaptureSummaryService) Summarize(ctx context.Context, imageURL string) (string, error) { if s.Summarizer == nil { return "", fmt.Errorf("llm is not configured") } return s.Summarizer.SummarizeImage(ctx, s.Prompt, imageURL) } // CopySummary writes the final summary text to the clipboard. func (s *CaptureSummaryService) CopySummary(_ context.Context, summary string) error { if summary == "" { return fmt.Errorf("empty summary") } if s.Clipboard == nil { return fmt.Errorf("clipboard is not configured") } if err := s.Clipboard.WriteText(summary); err != nil { return fmt.Errorf("clipboard write failed: %w", err) } return nil }