package files import ( "os" "path/filepath" "strings" ) type SearchResult struct { Path string `json:"path"` Snippet string `json:"snippet"` } // Search finds files containing the query string (case-insensitive). func Search(dataDir, userID, query string) ([]SearchResult, error) { root := UserDir(dataDir, userID) query = strings.ToLower(query) var results []SearchResult filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { return nil } rel, _ := filepath.Rel(root, path) // Match filename if strings.Contains(strings.ToLower(info.Name()), query) { results = append(results, SearchResult{Path: rel, Snippet: "(filename match)"}) return nil } // Match content (only for .md and .txt) ext := strings.ToLower(filepath.Ext(path)) if ext == ".md" || ext == ".txt" { content, err := os.ReadFile(path) if err != nil { return nil } lower := strings.ToLower(string(content)) idx := strings.Index(lower, query) if idx >= 0 { // Extract snippet start := idx - 40 if start < 0 { start = 0 } end := idx + len(query) + 40 if end > len(content) { end = len(content) } snippet := string(content[start:end]) results = append(results, SearchResult{Path: rel, Snippet: snippet}) } } if len(results) >= 50 { return filepath.SkipAll } return nil }) if results == nil { results = []SearchResult{} } return results, nil }