From b4319a228df6e0b3004a21eff0f79a86e13306e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 19:15:56 +0000 Subject: [PATCH 1/5] Initial plan From 98c7fd3cf28dd7cbe7b3ef6966e2a21d7afae0e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 19:26:37 +0000 Subject: [PATCH 2/5] Add Google AI file summarization package Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- pkg/aiusechat/google/doc.go | 38 ++++ pkg/aiusechat/google/google-summarize.go | 151 ++++++++++++++++ pkg/aiusechat/google/google-summarize_test.go | 167 ++++++++++++++++++ 3 files changed, 356 insertions(+) create mode 100644 pkg/aiusechat/google/doc.go create mode 100644 pkg/aiusechat/google/google-summarize.go create mode 100644 pkg/aiusechat/google/google-summarize_test.go diff --git a/pkg/aiusechat/google/doc.go b/pkg/aiusechat/google/doc.go new file mode 100644 index 0000000000..4e0c9fe48f --- /dev/null +++ b/pkg/aiusechat/google/doc.go @@ -0,0 +1,38 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Package google provides Google Generative AI integration for WaveTerm. +// +// This package implements file summarization using Google's Gemini models. +// Unlike other AI provider implementations in the aiusechat package, this +// package does NOT implement full SSE streaming. It uses a simple +// request-response API for file summarization. +// +// # Supported File Types +// +// The package supports the same file types as defined in wshcmd-ai.go: +// - Images (PNG, JPEG, etc.): up to 7MB +// - PDFs: up to 5MB +// - Text files: up to 200KB +// +// Binary files are rejected unless they are recognized as images or PDFs. +// +// # Usage +// +// To summarize a file: +// +// ctx := context.Background() +// summary, usage, err := google.SummarizeFile(ctx, "/path/to/file.txt", "YOUR_API_KEY") +// if err != nil { +// log.Fatal(err) +// } +// fmt.Println("Summary:", summary) +// fmt.Printf("Tokens used: %d\n", usage.TotalTokenCount) +// +// # Configuration +// +// The summarization behavior can be customized by modifying the constants: +// - SummarizeModel: The Gemini model to use (default: "gemini-2.5-flash-lite") +// - SummarizePrompt: The prompt sent to the model +// - GoogleAPIURL: The base URL for the API (for reference, not currently used by the SDK) +package google diff --git a/pkg/aiusechat/google/google-summarize.go b/pkg/aiusechat/google/google-summarize.go new file mode 100644 index 0000000000..e167562fe8 --- /dev/null +++ b/pkg/aiusechat/google/google-summarize.go @@ -0,0 +1,151 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package google + +import ( + "context" + "fmt" + "net/http" + "os" + "strings" + + "github.com/google/generative-ai-go/genai" + "github.com/wavetermdev/waveterm/pkg/util/utilfn" + "google.golang.org/api/option" +) + +const ( + // GoogleAPIURL is the base URL for the Google Generative AI API + GoogleAPIURL = "https://generativelanguage.googleapis.com" + + // SummarizePrompt is the prompt used for file summarization + SummarizePrompt = "Please provide a concise summary of this file. Include the main topics, key points, and any notable information." + + // SummarizeModel is the model used for file summarization + SummarizeModel = "gemini-2.5-flash-lite" +) + +// GoogleUsage represents token usage information from Google's Generative AI API +type GoogleUsage struct { + PromptTokenCount int32 `json:"prompt_token_count"` + CachedContentTokenCount int32 `json:"cached_content_token_count"` + CandidatesTokenCount int32 `json:"candidates_token_count"` + TotalTokenCount int32 `json:"total_token_count"` +} + +func detectMimeType(data []byte) string { + mimeType := http.DetectContentType(data) + return strings.Split(mimeType, ";")[0] +} + +func getMaxFileSize(mimeType string) (int, string) { + if mimeType == "application/pdf" { + return 5 * 1024 * 1024, "5MB" + } + if strings.HasPrefix(mimeType, "image/") { + return 7 * 1024 * 1024, "7MB" + } + return 200 * 1024, "200KB" +} + +// SummarizeFile reads a file and generates a summary using Google's Generative AI. +// It supports images, PDFs, and text files based on the limits defined in wshcmd-ai.go. +// Returns the summary text, usage information, and any error encountered. +func SummarizeFile(ctx context.Context, filename string, apiKey string) (string, *GoogleUsage, error) { + // Read the file + data, err := os.ReadFile(filename) + if err != nil { + return "", nil, fmt.Errorf("reading file: %w", err) + } + + // Detect MIME type + mimeType := detectMimeType(data) + + isPDF := mimeType == "application/pdf" + isImage := strings.HasPrefix(mimeType, "image/") + + if !isPDF && !isImage { + mimeType = "text/plain" + if utilfn.ContainsBinaryData(data) { + return "", nil, fmt.Errorf("file contains binary data and cannot be summarized") + } + } + + // Validate file size + maxSize, sizeStr := getMaxFileSize(mimeType) + if len(data) > maxSize { + return "", nil, fmt.Errorf("file exceeds maximum size of %s for %s files", sizeStr, mimeType) + } + + // Create client + client, err := genai.NewClient(ctx, option.WithAPIKey(apiKey)) + if err != nil { + return "", nil, fmt.Errorf("creating Google AI client: %w", err) + } + defer client.Close() + + // Create model + model := client.GenerativeModel(SummarizeModel) + + // Prepare the content parts + var parts []genai.Part + + // Add the prompt + parts = append(parts, genai.Text(SummarizePrompt)) + + // Add the file content based on type + if isImage { + // For images, use Blob + parts = append(parts, genai.Blob{ + MIMEType: mimeType, + Data: data, + }) + } else if isPDF { + // For PDFs, use Blob + parts = append(parts, genai.Blob{ + MIMEType: mimeType, + Data: data, + }) + } else { + // For text files, convert to string + parts = append(parts, genai.Text(string(data))) + } + + // Generate content + resp, err := model.GenerateContent(ctx, parts...) + if err != nil { + return "", nil, fmt.Errorf("generating content: %w", err) + } + + // Check if we got any candidates + if len(resp.Candidates) == 0 { + return "", nil, fmt.Errorf("no response candidates returned") + } + + // Extract the text from the first candidate + candidate := resp.Candidates[0] + if candidate.Content == nil || len(candidate.Content.Parts) == 0 { + return "", nil, fmt.Errorf("no content in response") + } + + var summary strings.Builder + for _, part := range candidate.Content.Parts { + if textPart, ok := part.(genai.Text); ok { + summary.WriteString(string(textPart)) + } + } + + // Convert usage metadata + var usage *GoogleUsage + if resp.UsageMetadata != nil { + usage = &GoogleUsage{ + PromptTokenCount: resp.UsageMetadata.PromptTokenCount, + CachedContentTokenCount: resp.UsageMetadata.CachedContentTokenCount, + CandidatesTokenCount: resp.UsageMetadata.CandidatesTokenCount, + TotalTokenCount: resp.UsageMetadata.TotalTokenCount, + } + } + + return summary.String(), usage, nil +} diff --git a/pkg/aiusechat/google/google-summarize_test.go b/pkg/aiusechat/google/google-summarize_test.go new file mode 100644 index 0000000000..7a91613e2f --- /dev/null +++ b/pkg/aiusechat/google/google-summarize_test.go @@ -0,0 +1,167 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package google + +import ( + "context" + "os" + "path/filepath" + "testing" + "time" +) + +func TestDetectMimeType(t *testing.T) { + tests := []struct { + name string + data []byte + expected string + }{ + { + name: "plain text", + data: []byte("Hello, World!"), + expected: "text/plain", + }, + { + name: "empty file", + data: []byte{}, + expected: "text/plain", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := detectMimeType(tt.data) + if !containsMimeType(result, tt.expected) { + t.Errorf("detectMimeType() = %v, want to contain %v", result, tt.expected) + } + }) + } +} + +func containsMimeType(got, want string) bool { + // DetectContentType may return variations like "text/plain; charset=utf-8" + return got == want || (want == "text/plain" && got == "text/plain; charset=utf-8") +} + +func TestGetMaxFileSize(t *testing.T) { + tests := []struct { + name string + mimeType string + expectedSize int + expectedStr string + }{ + { + name: "PDF file", + mimeType: "application/pdf", + expectedSize: 5 * 1024 * 1024, + expectedStr: "5MB", + }, + { + name: "PNG image", + mimeType: "image/png", + expectedSize: 7 * 1024 * 1024, + expectedStr: "7MB", + }, + { + name: "JPEG image", + mimeType: "image/jpeg", + expectedSize: 7 * 1024 * 1024, + expectedStr: "7MB", + }, + { + name: "text file", + mimeType: "text/plain", + expectedSize: 200 * 1024, + expectedStr: "200KB", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + size, sizeStr := getMaxFileSize(tt.mimeType) + if size != tt.expectedSize { + t.Errorf("getMaxFileSize() size = %v, want %v", size, tt.expectedSize) + } + if sizeStr != tt.expectedStr { + t.Errorf("getMaxFileSize() sizeStr = %v, want %v", sizeStr, tt.expectedStr) + } + }) + } +} + +func TestSummarizeFile_FileNotFound(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + _, _, err := SummarizeFile(ctx, "/nonexistent/file.txt", "fake-api-key") + if err == nil { + t.Error("SummarizeFile() expected error for nonexistent file, got nil") + } +} + +func TestSummarizeFile_BinaryFile(t *testing.T) { + // Create a temporary binary file + tmpDir := t.TempDir() + binFile := filepath.Join(tmpDir, "test.bin") + + // Create binary data (not text, image, or PDF) + binaryData := []byte{0x00, 0x01, 0x02, 0x03, 0x7F, 0x80, 0xFF} + if err := os.WriteFile(binFile, binaryData, 0644); err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + _, _, err := SummarizeFile(ctx, binFile, "fake-api-key") + if err == nil { + t.Error("SummarizeFile() expected error for binary file, got nil") + } + if err != nil && !containsString(err.Error(), "binary data") { + t.Errorf("SummarizeFile() error = %v, want error containing 'binary data'", err) + } +} + +func TestSummarizeFile_FileTooLarge(t *testing.T) { + // Create a temporary text file that exceeds the limit + tmpDir := t.TempDir() + textFile := filepath.Join(tmpDir, "large.txt") + + // Create a file larger than 200KB (text file limit) + largeData := make([]byte, 201*1024) + for i := range largeData { + largeData[i] = 'a' + } + if err := os.WriteFile(textFile, largeData, 0644); err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + _, _, err := SummarizeFile(ctx, textFile, "fake-api-key") + if err == nil { + t.Error("SummarizeFile() expected error for file too large, got nil") + } + if err != nil && !containsString(err.Error(), "exceeds maximum size") { + t.Errorf("SummarizeFile() error = %v, want error containing 'exceeds maximum size'", err) + } +} + +func containsString(s, substr string) bool { + return len(s) >= len(substr) && (s == substr || len(substr) == 0 || + (len(s) > 0 && len(substr) > 0 && stringContains(s, substr))) +} + +func stringContains(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} + +// Note: We don't test the actual API call without a real API key +// Integration tests would require setting GOOGLE_API_KEY environment variable From 802601916e05af55a519b66fc3353841ba656e8a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 19:30:04 +0000 Subject: [PATCH 3/5] Final implementation of Google AI file summarization Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- tsunami/demo/cpuchart/go.mod | 7 +------ tsunami/demo/cpuchart/go.sum | 12 ------------ tsunami/demo/githubaction/go.mod | 7 ------- tsunami/demo/githubaction/go.sum | 4 ---- tsunami/demo/pomodoro/go.mod | 7 ------- tsunami/demo/pomodoro/go.sum | 4 ---- tsunami/demo/recharts/go.mod | 7 ------- tsunami/demo/recharts/go.sum | 4 ---- tsunami/demo/tabletest/go.mod | 7 ------- tsunami/demo/tabletest/go.sum | 4 ---- tsunami/demo/todo/go.mod | 7 ------- tsunami/demo/todo/go.sum | 4 ---- tsunami/demo/tsunamiconfig/go.mod | 7 ------- tsunami/demo/tsunamiconfig/go.sum | 4 ---- 14 files changed, 1 insertion(+), 84 deletions(-) diff --git a/tsunami/demo/cpuchart/go.mod b/tsunami/demo/cpuchart/go.mod index 0140d12180..864522e4e6 100644 --- a/tsunami/demo/cpuchart/go.mod +++ b/tsunami/demo/cpuchart/go.mod @@ -2,17 +2,12 @@ module tsunami/app/cpuchart go 1.24.6 -require ( - github.com/shirou/gopsutil/v4 v4.25.8 - github.com/wavetermdev/waveterm/tsunami v0.0.0 -) +require github.com/shirou/gopsutil/v4 v4.25.8 require ( github.com/ebitengine/purego v0.8.4 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect diff --git a/tsunami/demo/cpuchart/go.sum b/tsunami/demo/cpuchart/go.sum index 4d3c872cfc..965d0ed1e9 100644 --- a/tsunami/demo/cpuchart/go.sum +++ b/tsunami/demo/cpuchart/go.sum @@ -1,26 +1,16 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970= github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= @@ -32,5 +22,3 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tsunami/demo/githubaction/go.mod b/tsunami/demo/githubaction/go.mod index cf51505f61..7ba875a5ab 100644 --- a/tsunami/demo/githubaction/go.mod +++ b/tsunami/demo/githubaction/go.mod @@ -2,11 +2,4 @@ module tsunami/app/githubaction go 1.24.6 -require github.com/wavetermdev/waveterm/tsunami v0.0.0 - -require ( - github.com/google/uuid v1.6.0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect -) - replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/githubaction/go.sum b/tsunami/demo/githubaction/go.sum index 4c44991dfc..e69de29bb2 100644 --- a/tsunami/demo/githubaction/go.sum +++ b/tsunami/demo/githubaction/go.sum @@ -1,4 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/pomodoro/go.mod b/tsunami/demo/pomodoro/go.mod index 9e5e8362d4..bf8b7c0096 100644 --- a/tsunami/demo/pomodoro/go.mod +++ b/tsunami/demo/pomodoro/go.mod @@ -2,11 +2,4 @@ module tsunami/app/pomodoro go 1.24.6 -require github.com/wavetermdev/waveterm/tsunami v0.0.0 - -require ( - github.com/google/uuid v1.6.0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect -) - replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/pomodoro/go.sum b/tsunami/demo/pomodoro/go.sum index 4c44991dfc..e69de29bb2 100644 --- a/tsunami/demo/pomodoro/go.sum +++ b/tsunami/demo/pomodoro/go.sum @@ -1,4 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/recharts/go.mod b/tsunami/demo/recharts/go.mod index dfe7fef813..9d2536376d 100644 --- a/tsunami/demo/recharts/go.mod +++ b/tsunami/demo/recharts/go.mod @@ -2,11 +2,4 @@ module tsunami/app/recharts go 1.24.6 -require github.com/wavetermdev/waveterm/tsunami v0.0.0 - -require ( - github.com/google/uuid v1.6.0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect -) - replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/recharts/go.sum b/tsunami/demo/recharts/go.sum index 4c44991dfc..e69de29bb2 100644 --- a/tsunami/demo/recharts/go.sum +++ b/tsunami/demo/recharts/go.sum @@ -1,4 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/tabletest/go.mod b/tsunami/demo/tabletest/go.mod index 86b7209a5a..6f2d6aad21 100644 --- a/tsunami/demo/tabletest/go.mod +++ b/tsunami/demo/tabletest/go.mod @@ -2,11 +2,4 @@ module tsunami/app/tabletest go 1.24.6 -require github.com/wavetermdev/waveterm/tsunami v0.0.0 - -require ( - github.com/google/uuid v1.6.0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect -) - replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/tabletest/go.sum b/tsunami/demo/tabletest/go.sum index 4c44991dfc..e69de29bb2 100644 --- a/tsunami/demo/tabletest/go.sum +++ b/tsunami/demo/tabletest/go.sum @@ -1,4 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/todo/go.mod b/tsunami/demo/todo/go.mod index f82b52b1cc..f5b6f93578 100644 --- a/tsunami/demo/todo/go.mod +++ b/tsunami/demo/todo/go.mod @@ -2,11 +2,4 @@ module tsunami/app/todo go 1.24.6 -require github.com/wavetermdev/waveterm/tsunami v0.0.0 - -require ( - github.com/google/uuid v1.6.0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect -) - replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/todo/go.sum b/tsunami/demo/todo/go.sum index 4c44991dfc..e69de29bb2 100644 --- a/tsunami/demo/todo/go.sum +++ b/tsunami/demo/todo/go.sum @@ -1,4 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/tsunamiconfig/go.mod b/tsunami/demo/tsunamiconfig/go.mod index 165622f27a..6c3f08b497 100644 --- a/tsunami/demo/tsunamiconfig/go.mod +++ b/tsunami/demo/tsunamiconfig/go.mod @@ -2,11 +2,4 @@ module tsunami/app/tsunamiconfig go 1.24.6 -require github.com/wavetermdev/waveterm/tsunami v0.0.0 - -require ( - github.com/google/uuid v1.6.0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect -) - replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/tsunamiconfig/go.sum b/tsunami/demo/tsunamiconfig/go.sum index 4c44991dfc..e69de29bb2 100644 --- a/tsunami/demo/tsunamiconfig/go.sum +++ b/tsunami/demo/tsunamiconfig/go.sum @@ -1,4 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= From 4aa6c17a6abacce7b68a8f8c35743eb7950d11d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 20:05:42 +0000 Subject: [PATCH 4/5] Revert unintended changes to tsunami demo go.mod and go.sum files Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- tsunami/demo/cpuchart/go.mod | 7 ++++++- tsunami/demo/cpuchart/go.sum | 12 ++++++++++++ tsunami/demo/githubaction/go.mod | 7 +++++++ tsunami/demo/githubaction/go.sum | 4 ++++ tsunami/demo/pomodoro/go.mod | 7 +++++++ tsunami/demo/pomodoro/go.sum | 4 ++++ tsunami/demo/recharts/go.mod | 7 +++++++ tsunami/demo/recharts/go.sum | 4 ++++ tsunami/demo/tabletest/go.mod | 7 +++++++ tsunami/demo/tabletest/go.sum | 4 ++++ tsunami/demo/todo/go.mod | 7 +++++++ tsunami/demo/todo/go.sum | 4 ++++ tsunami/demo/tsunamiconfig/go.mod | 7 +++++++ tsunami/demo/tsunamiconfig/go.sum | 4 ++++ 14 files changed, 84 insertions(+), 1 deletion(-) diff --git a/tsunami/demo/cpuchart/go.mod b/tsunami/demo/cpuchart/go.mod index 864522e4e6..0140d12180 100644 --- a/tsunami/demo/cpuchart/go.mod +++ b/tsunami/demo/cpuchart/go.mod @@ -2,12 +2,17 @@ module tsunami/app/cpuchart go 1.24.6 -require github.com/shirou/gopsutil/v4 v4.25.8 +require ( + github.com/shirou/gopsutil/v4 v4.25.8 + github.com/wavetermdev/waveterm/tsunami v0.0.0 +) require ( github.com/ebitengine/purego v0.8.4 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect diff --git a/tsunami/demo/cpuchart/go.sum b/tsunami/demo/cpuchart/go.sum index 965d0ed1e9..4d3c872cfc 100644 --- a/tsunami/demo/cpuchart/go.sum +++ b/tsunami/demo/cpuchart/go.sum @@ -1,16 +1,26 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970= github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= @@ -22,3 +32,5 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tsunami/demo/githubaction/go.mod b/tsunami/demo/githubaction/go.mod index 7ba875a5ab..cf51505f61 100644 --- a/tsunami/demo/githubaction/go.mod +++ b/tsunami/demo/githubaction/go.mod @@ -2,4 +2,11 @@ module tsunami/app/githubaction go 1.24.6 +require github.com/wavetermdev/waveterm/tsunami v0.0.0 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect +) + replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/githubaction/go.sum b/tsunami/demo/githubaction/go.sum index e69de29bb2..4c44991dfc 100644 --- a/tsunami/demo/githubaction/go.sum +++ b/tsunami/demo/githubaction/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/pomodoro/go.mod b/tsunami/demo/pomodoro/go.mod index bf8b7c0096..9e5e8362d4 100644 --- a/tsunami/demo/pomodoro/go.mod +++ b/tsunami/demo/pomodoro/go.mod @@ -2,4 +2,11 @@ module tsunami/app/pomodoro go 1.24.6 +require github.com/wavetermdev/waveterm/tsunami v0.0.0 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect +) + replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/pomodoro/go.sum b/tsunami/demo/pomodoro/go.sum index e69de29bb2..4c44991dfc 100644 --- a/tsunami/demo/pomodoro/go.sum +++ b/tsunami/demo/pomodoro/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/recharts/go.mod b/tsunami/demo/recharts/go.mod index 9d2536376d..dfe7fef813 100644 --- a/tsunami/demo/recharts/go.mod +++ b/tsunami/demo/recharts/go.mod @@ -2,4 +2,11 @@ module tsunami/app/recharts go 1.24.6 +require github.com/wavetermdev/waveterm/tsunami v0.0.0 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect +) + replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/recharts/go.sum b/tsunami/demo/recharts/go.sum index e69de29bb2..4c44991dfc 100644 --- a/tsunami/demo/recharts/go.sum +++ b/tsunami/demo/recharts/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/tabletest/go.mod b/tsunami/demo/tabletest/go.mod index 6f2d6aad21..86b7209a5a 100644 --- a/tsunami/demo/tabletest/go.mod +++ b/tsunami/demo/tabletest/go.mod @@ -2,4 +2,11 @@ module tsunami/app/tabletest go 1.24.6 +require github.com/wavetermdev/waveterm/tsunami v0.0.0 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect +) + replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/tabletest/go.sum b/tsunami/demo/tabletest/go.sum index e69de29bb2..4c44991dfc 100644 --- a/tsunami/demo/tabletest/go.sum +++ b/tsunami/demo/tabletest/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/todo/go.mod b/tsunami/demo/todo/go.mod index f5b6f93578..f82b52b1cc 100644 --- a/tsunami/demo/todo/go.mod +++ b/tsunami/demo/todo/go.mod @@ -2,4 +2,11 @@ module tsunami/app/todo go 1.24.6 +require github.com/wavetermdev/waveterm/tsunami v0.0.0 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect +) + replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/todo/go.sum b/tsunami/demo/todo/go.sum index e69de29bb2..4c44991dfc 100644 --- a/tsunami/demo/todo/go.sum +++ b/tsunami/demo/todo/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/tsunamiconfig/go.mod b/tsunami/demo/tsunamiconfig/go.mod index 6c3f08b497..165622f27a 100644 --- a/tsunami/demo/tsunamiconfig/go.mod +++ b/tsunami/demo/tsunamiconfig/go.mod @@ -2,4 +2,11 @@ module tsunami/app/tsunamiconfig go 1.24.6 +require github.com/wavetermdev/waveterm/tsunami v0.0.0 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect +) + replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/tsunamiconfig/go.sum b/tsunami/demo/tsunamiconfig/go.sum index e69de29bb2..4c44991dfc 100644 --- a/tsunami/demo/tsunamiconfig/go.sum +++ b/tsunami/demo/tsunamiconfig/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= From 025761e822db446dfd19dc61cdded6bdc0e4046b Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 17 Oct 2025 17:20:19 -0700 Subject: [PATCH 5/5] add 'modes' and add a cmd to test --- cmd/testsummarize/main-testsummarize.go | 104 ++++++++++++ pkg/aiusechat/google/doc.go | 5 +- pkg/aiusechat/google/google-summarize.go | 148 +++++++++++++++++- pkg/aiusechat/google/google-summarize_test.go | 61 ++------ 4 files changed, 260 insertions(+), 58 deletions(-) create mode 100644 cmd/testsummarize/main-testsummarize.go diff --git a/cmd/testsummarize/main-testsummarize.go b/cmd/testsummarize/main-testsummarize.go new file mode 100644 index 0000000000..fc16e59e04 --- /dev/null +++ b/cmd/testsummarize/main-testsummarize.go @@ -0,0 +1,104 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "context" + "flag" + "fmt" + "os" + "time" + + "github.com/wavetermdev/waveterm/pkg/aiusechat/google" +) + +func printUsage() { + fmt.Println("Usage: go run main-testsummarize.go [--help] [--mode MODE] ") + fmt.Println("Examples:") + fmt.Println(" go run main-testsummarize.go README.md") + fmt.Println(" go run main-testsummarize.go --mode useful /path/to/image.png") + fmt.Println(" go run main-testsummarize.go -m publiccode document.pdf") + fmt.Println("") + fmt.Println("Supported file types:") + fmt.Println(" - Text files (up to 200KB)") + fmt.Println(" - Images (up to 7MB)") + fmt.Println(" - PDFs (up to 5MB)") + fmt.Println("") + fmt.Println("Flags:") + fmt.Println(" --mode, -m Summarization mode (default: quick)") + fmt.Println(" Options: quick, useful, publiccode, htmlcontent, htmlfull") + fmt.Println("") + fmt.Println("Environment variables:") + fmt.Println(" GOOGLE_APIKEY (required)") +} + +func main() { + var showHelp bool + var mode string + flag.BoolVar(&showHelp, "help", false, "Show usage information") + flag.StringVar(&mode, "mode", "quick", "Summarization mode") + flag.StringVar(&mode, "m", "quick", "Summarization mode (shorthand)") + flag.Parse() + + if showHelp { + printUsage() + os.Exit(0) + } + + apiKey := os.Getenv("GOOGLE_APIKEY") + if apiKey == "" { + fmt.Println("Error: GOOGLE_APIKEY environment variable not set") + printUsage() + os.Exit(1) + } + + args := flag.Args() + if len(args) == 0 { + fmt.Println("Error: filename required") + printUsage() + os.Exit(1) + } + + filename := args[0] + + // Check if file exists + if _, err := os.Stat(filename); os.IsNotExist(err) { + fmt.Printf("Error: file '%s' does not exist\n", filename) + os.Exit(1) + } + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + fmt.Printf("Summarizing file: %s\n", filename) + fmt.Printf("Model: %s\n", google.SummarizeModel) + fmt.Printf("Mode: %s\n", mode) + + startTime := time.Now() + summary, usage, err := google.SummarizeFile(ctx, filename, google.SummarizeOpts{ + APIKey: apiKey, + Mode: mode, + }) + latency := time.Since(startTime) + + fmt.Printf("Latency: %d ms\n", latency.Milliseconds()) + fmt.Println("===") + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + fmt.Println("\nSummary:") + fmt.Println("---") + fmt.Println(summary) + fmt.Println("---") + + if usage != nil { + fmt.Println("\nUsage Statistics:") + fmt.Printf(" Prompt tokens: %d\n", usage.PromptTokenCount) + fmt.Printf(" Cached tokens: %d\n", usage.CachedContentTokenCount) + fmt.Printf(" Response tokens: %d\n", usage.CandidatesTokenCount) + fmt.Printf(" Total tokens: %d\n", usage.TotalTokenCount) + } +} \ No newline at end of file diff --git a/pkg/aiusechat/google/doc.go b/pkg/aiusechat/google/doc.go index 4e0c9fe48f..caab8a4ecd 100644 --- a/pkg/aiusechat/google/doc.go +++ b/pkg/aiusechat/google/doc.go @@ -22,7 +22,10 @@ // To summarize a file: // // ctx := context.Background() -// summary, usage, err := google.SummarizeFile(ctx, "/path/to/file.txt", "YOUR_API_KEY") +// summary, usage, err := google.SummarizeFile(ctx, "/path/to/file.txt", google.SummarizeOpts{ +// APIKey: "YOUR_API_KEY", +// Mode: google.ModeQuickSummary, +// }) // if err != nil { // log.Fatal(err) // } diff --git a/pkg/aiusechat/google/google-summarize.go b/pkg/aiusechat/google/google-summarize.go index e167562fe8..67d3cd3eb5 100644 --- a/pkg/aiusechat/google/google-summarize.go +++ b/pkg/aiusechat/google/google-summarize.go @@ -19,13 +19,121 @@ const ( // GoogleAPIURL is the base URL for the Google Generative AI API GoogleAPIURL = "https://generativelanguage.googleapis.com" - // SummarizePrompt is the prompt used for file summarization - SummarizePrompt = "Please provide a concise summary of this file. Include the main topics, key points, and any notable information." - // SummarizeModel is the model used for file summarization SummarizeModel = "gemini-2.5-flash-lite" + + // Mode constants + ModeQuickSummary = "quick" + ModeUseful = "useful" + ModePublicCode = "publiccode" + ModeHTMLContent = "htmlcontent" + ModeHTMLFull = "htmlfull" + + // SummarizePrompt is the default prompt used for file summarization + SummarizePrompt = "Please provide a concise summary of this file. Include the main topics, key points, and any notable information." + + // QuickSummaryPrompt is the prompt for quick file summaries + QuickSummaryPrompt = `Summarize the following file for another AI agent that is deciding which files to read. + +If the content is HTML or web page markup, ignore layout elements such as headers, footers, sidebars, navigation menus, cookie banners, pop-ups, ads, and search boxes. +Focus only on the visible main content that describes the page’s subject or purpose. + +Keep the summary extremely concise — one or two sentences at most. +Explain what the file appears to be and its main purpose or contents. +If it's code, mention the language and what it implements (e.g., a CLI, library, test, or config). +Avoid speculation or verbose explanations. +Do not include markdown, bullets, or formatting — just a plain text summary.` + + // UsefulSummaryPrompt is the prompt for useful file summaries with more detail + UsefulSummaryPrompt = `You are summarizing a single file so that another AI agent can understand its purpose and structure. + +If the content is HTML or web page markup, ignore layout elements such as headers, footers, sidebars, navigation menus, cookie banners, pop-ups, ads, and search boxes. +Focus only on the visible main content that describes the page’s subject or purpose. + +Start with a short overview (2–4 sentences) describing the overall purpose of the file. +If the file is large (more than about 150 lines) or has multiple major sections or functions, +then briefly summarize each major section (1–2 sentences per section) and include an approximate line range in parentheses like "(lines 80–220)". + +Keep section summaries extremely concise — only include the most important parts or entry points. +If it's code, mention key functions or classes and what they do. +If it's documentation, describe key topics or sections. +If it's a data or config file, summarize the structure and purpose of the values. + +Never produce more text than would fit comfortably on one screen (roughly under 200 words total). +Plain text only — no lists, no markdown, no JSON.` + + // PublicCodeSummaryPrompt is the prompt for public API summaries + PublicCodeSummaryPrompt = `You are summarizing a SINGLE source file to expose its PUBLIC API to another AI client. + +GOAL +Produce a compact, header-like listing of all PUBLIC symbols callers would use. + +OUTPUT FORMAT (plain text only; no bullets/markdown/JSON): +1) Public data structures required by public functions (types/structs/interfaces/enums/const groups): + (lines A–B) + + +2) Public functions/methods in order of appearance: + (lines A–B) + + +RULES +- PUBLIC means exported/externally visible for the language (Go: capitalized; Java/C#/TS: public; Rust: pub; Python: not underscore-prefixed, etc.). +- Include ALL public functions/methods. +- Include public data structures ONLY if referenced by any public function OR commonly constructed/consumed by callers. +- For multi-line declarations, emit a single-line canonical form by collapsing internal whitespace while preserving tokens and order. +- The one-line comment is either a compressed docstring or, if absent, a concise inferred purpose (≤ 20 words). +- Include approximate line ranges as "(lines A–B)". +- Skip private helpers, tests, examples, and internal-only constants. +- Preserve generics/annotations/modifiers as they appear (e.g., type params, async, const, noexcept). +- No preface or epilogue text—just the listing. + +EXAMPLE STYLE (illustrative; use the target language's comment syntax): +// Configuration for the proxy (lines 10–42) +type ProxyConfig struct { ... } + +// Creates and configures a new proxy instance (lines 60–92) +func NewProxy(cfg ProxyConfig) (*Proxy, error) + +// Handles a single HTTP request (lines 95–168) +func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request)` + + // HTMLContentPrompt is the prompt for converting HTML to content-focused Markdown + HTMLContentPrompt = `Convert the following stripped HTML into clean Markdown for READING CONTENT ONLY. + +- Output Markdown ONLY (no explanations, no JSON, no code fences). +- Keep document title as a single H1 if present (from or first <h1>). +- Preserve headings (h1–h6), paragraphs, strong/emphasis, inline code. +- Convert <a> to [text](absolute_url). If href is relative, resolve against BASE_URL: {{BASE_URL}}. Do not output javascript:void links. +- Preserve lists (ul/ol, nested), blockquotes, and code blocks (<pre><code>) as fenced code (include language if obvious). +- Convert tables to Markdown tables; keep header row; include up to 50 data rows, then append "… (more rows)". +- Keep images ONLY if alt text is descriptive; render as ![alt](absolute_url). Skip tracking pixels and decorative images. +- Discard navigation, site header/footer, sidebars, cookie banners, search bars, newsletter/signup, social share, repetitive link clouds, and legal boilerplate unless they are the ONLY content. +- Preserve in-page structure order; do not invent content; do not summarize prose—extract faithfully. +- Normalize whitespace, collapse repeated blank lines to one. +` + + // HTMLFullPrompt is the prompt for converting HTML to navigation-focused Markdown + HTMLFullPrompt = `Convert the following stripped HTML into Markdown optimized for SITE NAVIGATION. + +- Output Markdown ONLY (no explanations, no JSON, no code fences). +- Start with a top-level title (from <title> or first <h1>) as H1. +- Include primary navigation as a section "## Navigation" with bullet lists of top-level links (use visible link text; dedupe exact duplicates). +- Include secondary nav/footer links under "## Footer Links". +- Then extract the main page content as Markdown (headings, paragraphs, lists, blockquotes, code blocks). +- Convert <a> to [text](absolute_url). If href is relative, resolve against BASE_URL: {{BASE_URL}}. +- Convert tables to Markdown tables; keep header + up to 50 rows, then "… (more rows)". +- Keep images with meaningful alt as ![alt](absolute_url); otherwise skip. +- Preserve order as it appears in the page; do not summarize prose—extract faithfully. +- Normalize whitespace; collapse repeated blank lines.` ) +// SummarizeOpts contains options for file summarization +type SummarizeOpts struct { + APIKey string + Mode string +} + // GoogleUsage represents token usage information from Google's Generative AI API type GoogleUsage struct { PromptTokenCount int32 `json:"prompt_token_count"` @@ -39,20 +147,27 @@ func detectMimeType(data []byte) string { return strings.Split(mimeType, ";")[0] } -func getMaxFileSize(mimeType string) (int, string) { +func getMaxFileSize(mimeType, mode string) (int, string) { if mimeType == "application/pdf" { return 5 * 1024 * 1024, "5MB" } if strings.HasPrefix(mimeType, "image/") { return 7 * 1024 * 1024, "7MB" } + if mode == ModeHTMLContent || mode == ModeHTMLFull { + return 500 * 1024, "500KB" + } return 200 * 1024, "200KB" } // SummarizeFile reads a file and generates a summary using Google's Generative AI. // It supports images, PDFs, and text files based on the limits defined in wshcmd-ai.go. // Returns the summary text, usage information, and any error encountered. -func SummarizeFile(ctx context.Context, filename string, apiKey string) (string, *GoogleUsage, error) { +func SummarizeFile(ctx context.Context, filename string, opts SummarizeOpts) (string, *GoogleUsage, error) { + if opts.Mode == "" { + return "", nil, fmt.Errorf("mode is required") + } + // Read the file data, err := os.ReadFile(filename) if err != nil { @@ -73,13 +188,13 @@ func SummarizeFile(ctx context.Context, filename string, apiKey string) (string, } // Validate file size - maxSize, sizeStr := getMaxFileSize(mimeType) + maxSize, sizeStr := getMaxFileSize(mimeType, opts.Mode) if len(data) > maxSize { return "", nil, fmt.Errorf("file exceeds maximum size of %s for %s files", sizeStr, mimeType) } // Create client - client, err := genai.NewClient(ctx, option.WithAPIKey(apiKey)) + client, err := genai.NewClient(ctx, option.WithAPIKey(opts.APIKey)) if err != nil { return "", nil, fmt.Errorf("creating Google AI client: %w", err) } @@ -88,11 +203,28 @@ func SummarizeFile(ctx context.Context, filename string, apiKey string) (string, // Create model model := client.GenerativeModel(SummarizeModel) + // Select prompt based on mode + var prompt string + switch opts.Mode { + case ModeQuickSummary: + prompt = QuickSummaryPrompt + case ModeUseful: + prompt = UsefulSummaryPrompt + case ModePublicCode: + prompt = PublicCodeSummaryPrompt + case ModeHTMLContent: + prompt = HTMLContentPrompt + case ModeHTMLFull: + prompt = HTMLFullPrompt + default: + prompt = SummarizePrompt + } + // Prepare the content parts var parts []genai.Part // Add the prompt - parts = append(parts, genai.Text(SummarizePrompt)) + parts = append(parts, genai.Text(prompt)) // Add the file content based on type if isImage { diff --git a/pkg/aiusechat/google/google-summarize_test.go b/pkg/aiusechat/google/google-summarize_test.go index 7a91613e2f..1dd1c45733 100644 --- a/pkg/aiusechat/google/google-summarize_test.go +++ b/pkg/aiusechat/google/google-summarize_test.go @@ -44,57 +44,14 @@ func containsMimeType(got, want string) bool { return got == want || (want == "text/plain" && got == "text/plain; charset=utf-8") } -func TestGetMaxFileSize(t *testing.T) { - tests := []struct { - name string - mimeType string - expectedSize int - expectedStr string - }{ - { - name: "PDF file", - mimeType: "application/pdf", - expectedSize: 5 * 1024 * 1024, - expectedStr: "5MB", - }, - { - name: "PNG image", - mimeType: "image/png", - expectedSize: 7 * 1024 * 1024, - expectedStr: "7MB", - }, - { - name: "JPEG image", - mimeType: "image/jpeg", - expectedSize: 7 * 1024 * 1024, - expectedStr: "7MB", - }, - { - name: "text file", - mimeType: "text/plain", - expectedSize: 200 * 1024, - expectedStr: "200KB", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - size, sizeStr := getMaxFileSize(tt.mimeType) - if size != tt.expectedSize { - t.Errorf("getMaxFileSize() size = %v, want %v", size, tt.expectedSize) - } - if sizeStr != tt.expectedStr { - t.Errorf("getMaxFileSize() sizeStr = %v, want %v", sizeStr, tt.expectedStr) - } - }) - } -} - func TestSummarizeFile_FileNotFound(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - _, _, err := SummarizeFile(ctx, "/nonexistent/file.txt", "fake-api-key") + _, _, err := SummarizeFile(ctx, "/nonexistent/file.txt", SummarizeOpts{ + APIKey: "fake-api-key", + Mode: ModeQuickSummary, + }) if err == nil { t.Error("SummarizeFile() expected error for nonexistent file, got nil") } @@ -114,7 +71,10 @@ func TestSummarizeFile_BinaryFile(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - _, _, err := SummarizeFile(ctx, binFile, "fake-api-key") + _, _, err := SummarizeFile(ctx, binFile, SummarizeOpts{ + APIKey: "fake-api-key", + Mode: ModeQuickSummary, + }) if err == nil { t.Error("SummarizeFile() expected error for binary file, got nil") } @@ -140,7 +100,10 @@ func TestSummarizeFile_FileTooLarge(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - _, _, err := SummarizeFile(ctx, textFile, "fake-api-key") + _, _, err := SummarizeFile(ctx, textFile, SummarizeOpts{ + APIKey: "fake-api-key", + Mode: ModeQuickSummary, + }) if err == nil { t.Error("SummarizeFile() expected error for file too large, got nil") }