@@ -41,6 +41,7 @@ import (
4141 "github.com/1Panel-dev/1Panel/agent/global"
4242 "github.com/1Panel-dev/1Panel/agent/utils/common"
4343 "github.com/1Panel-dev/1Panel/agent/utils/files"
44+ terminalai "github.com/1Panel-dev/1Panel/agent/utils/terminal/ai"
4445 "github.com/pkg/errors"
4546)
4647
@@ -78,10 +79,7 @@ type IFileService interface {
7879 ConvertLog (req dto.PageInfo ) (int64 , []response.FileConvertLog , error )
7980 BatchGetRemarks (req request.FileRemarkBatch ) map [string ]string
8081 SetRemark (req request.FileRemarkUpdate ) error
81- }
82-
83- var filteredPaths = []string {
84- "/.1panel_clash" ,
82+ AISearch (req request.FileAISearch ) (* response.FileAISearchResult , error )
8583}
8684
8785const (
@@ -170,14 +168,7 @@ func (f *FileService) GetFileTree(op request.FileOption) ([]response.FileTree, e
170168}
171169
172170func shouldFilterPath (path string ) bool {
173- cleanedPath := filepath .Clean (path )
174- for _ , filteredPath := range filteredPaths {
175- cleanedFilteredPath := filepath .Clean (filteredPath )
176- if cleanedFilteredPath == cleanedPath || strings .HasPrefix (cleanedPath , cleanedFilteredPath + "/" ) {
177- return true
178- }
179- }
180- return false
171+ return files .ShouldFilterSensitivePath (path )
181172}
182173
183174func (f * FileService ) buildFileTree (node * response.FileTree , items []* files.FileInfo , op request.FileOption , level int ) error {
@@ -1019,3 +1010,155 @@ func (f *FileService) ConvertLog(req dto.PageInfo) (total int64, data []response
10191010
10201011 return total , data , nil
10211012}
1013+
1014+ func (f * FileService ) AISearch (req request.FileAISearch ) (* response.FileAISearchResult , error ) {
1015+ root := filepath .Clean (strings .TrimSpace (req .Path ))
1016+ if root == "" {
1017+ return nil , buserr .WithDetail ("ErrInvalidParams" , "path is required" , nil )
1018+ }
1019+ query := strings .TrimSpace (req .Query )
1020+ if query == "" {
1021+ return nil , buserr .WithDetail ("ErrInvalidParams" , "query is required" , nil )
1022+ }
1023+ st , err := os .Stat (root )
1024+ if err != nil {
1025+ if os .IsNotExist (err ) {
1026+ return nil , buserr .New ("ErrPathNotFound" )
1027+ }
1028+ return nil , err
1029+ }
1030+ if ! st .IsDir () {
1031+ return nil , buserr .New ("ErrPathNotFound" )
1032+ }
1033+
1034+ maxItems := req .MaxItems
1035+ if maxItems <= 0 {
1036+ maxItems = files .DefaultFileAIMaxItems
1037+ }
1038+ if maxItems > 2000 {
1039+ maxItems = 2000
1040+ }
1041+
1042+ containSub := true
1043+ if req .ContainSub != nil {
1044+ containSub = * req .ContainSub
1045+ }
1046+
1047+ searchOpts , err := files .MergeContentSearchOptions (
1048+ req .MatchCase , req .WholeWord , req .UseRegex ,
1049+ req .Extensions ,
1050+ req .MinSize , req .MaxSize ,
1051+ req .ModifiedAfter , req .ModifiedBefore ,
1052+ req .MaxScanFiles ,
1053+ req .MaxFileBytes ,
1054+ req .MaxHitsPerFile , req .MaxTotalHits ,
1055+ req .ContentHitsPromptMaxBytes ,
1056+ req .LlmMaxOutputTokens ,
1057+ )
1058+ if err != nil {
1059+ return nil , buserr .WithDetail ("ErrInvalidParams" , err .Error (), nil )
1060+ }
1061+
1062+ matchFn , err := files .NewContentLineMatcher (query , searchOpts )
1063+ if err != nil {
1064+ return nil , buserr .WithDetail ("ErrFileAISearchBadPattern" , err .Error (), nil )
1065+ }
1066+
1067+ cfg , timeout , err := terminalai .LoadFileAIRuntimeConfig ()
1068+ aiEnabled := err == nil
1069+ if err != nil && ! errors .Is (err , os .ErrNotExist ) {
1070+ return nil , err
1071+ }
1072+
1073+ items , truncated , err := files .CollectDirInventory (root , containSub , maxItems )
1074+ if err != nil {
1075+ return nil , err
1076+ }
1077+
1078+ preFiltered := false
1079+ llmItems := items
1080+ qLower := strings .ToLower (query )
1081+ if len (llmItems ) > 0 && query != "" {
1082+ filtered := make ([]files.AISearchInventoryItem , 0 , len (llmItems ))
1083+ for _ , it := range llmItems {
1084+ rel := strings .TrimSpace (it .RelPath )
1085+ if rel == "" {
1086+ continue
1087+ }
1088+ if ! req .UseRegex && ! req .MatchCase && ! req .WholeWord && strings .Contains (strings .ToLower (rel ), qLower ) {
1089+ filtered = append (filtered , it )
1090+ }
1091+ }
1092+ if len (filtered ) >= 8 {
1093+ llmItems = filtered
1094+ preFiltered = true
1095+ }
1096+ }
1097+
1098+ start := time .Now ()
1099+ contentHits , scannedFiles , hitsTrunc := files .SearchFileAIContentHits (root , llmItems , searchOpts , matchFn )
1100+ hitsDTO := make ([]response.FileAIContentHit , 0 , len (contentHits ))
1101+ for _ , h := range contentHits {
1102+ hitsDTO = append (hitsDTO , response.FileAIContentHit {Path : h .Path , Line : h .Line , Text : h .Text })
1103+ }
1104+
1105+ matchDesc := searchOpts .ContentMatchDescription ()
1106+ result := & response.FileAISearchResult {
1107+ Hits : hitsDTO ,
1108+ ContentScannedFiles : scannedFiles ,
1109+ ContentHitsTruncated : hitsTrunc ,
1110+ Truncated : truncated ,
1111+ PreFiltered : preFiltered ,
1112+ ItemCount : len (llmItems ),
1113+ }
1114+
1115+ if len (llmItems ) == 0 {
1116+ if aiEnabled {
1117+ result .Mode = "ai"
1118+ result .Summary = i18n .GetMsgByKey ("FileAISearchEmptyDir" )
1119+ if result .Summary == "" || result .Summary == "FileAISearchEmptyDir" {
1120+ result .Summary = "No files or directories found under this path (or all entries were filtered)."
1121+ }
1122+ } else {
1123+ result .Mode = "grep"
1124+ result .Summary = ""
1125+ }
1126+ result .Duration = time .Since (start ).Round (time .Millisecond ).String ()
1127+ return result , nil
1128+ }
1129+
1130+ if ! aiEnabled {
1131+ result .Mode = "grep"
1132+ result .Summary = ""
1133+ result .Duration = time .Since (start ).Round (time .Millisecond ).String ()
1134+ return result , nil
1135+ }
1136+
1137+ result .Mode = "ai"
1138+
1139+ clientTimeout := timeout
1140+ if clientTimeout < 30 * time .Second {
1141+ clientTimeout = 90 * time .Second
1142+ }
1143+ if clientTimeout > 5 * time .Minute {
1144+ clientTimeout = 5 * time .Minute
1145+ }
1146+
1147+ runCtx , cancel := context .WithTimeout (context .Background (), timeout + time .Minute )
1148+ defer cancel ()
1149+
1150+ llmMaxOut := searchOpts .LlmMaxOutputTokens
1151+ summary , usage , err := files .RunFileAISearchLLM (runCtx , cfg , clientTimeout , root , query , llmItems , truncated , preFiltered , contentHits , scannedFiles , hitsTrunc , matchDesc , searchOpts .ContentHitsPromptMaxBytes , llmMaxOut )
1152+ if err != nil {
1153+ return nil , err
1154+ }
1155+ result .Summary = summary
1156+ result .PromptTokens = usage .PromptTokens
1157+ result .CompletionTokens = usage .CompletionTokens
1158+ result .TotalTokens = usage .TotalTokens
1159+ if result .TotalTokens == 0 {
1160+ result .TotalTokens = usage .PromptTokens + usage .CompletionTokens
1161+ }
1162+ result .Duration = time .Since (start ).Round (time .Millisecond ).String ()
1163+ return result , nil
1164+ }
0 commit comments