Spaces:
Running
Running
Upload 2 files
Browse files
app.py
CHANGED
@@ -11,7 +11,7 @@ from smolagents.agent_types import AgentAudio, AgentImage, AgentText
|
|
11 |
from smolagents.memory import ActionStep, FinalAnswerStep, MemoryStep
|
12 |
from smolagents.models import ChatMessageStreamDelta
|
13 |
from smolagents.utils import _is_package_available
|
14 |
-
from tools import search, scraper, FileReader, FileWriter, CommitChanges, get_repository_structure
|
15 |
|
16 |
def get_step_footnote_content(step_log: MemoryStep, step_name: str) -> str:
|
17 |
"""Get a footnote string for a step log with duration and token information"""
|
@@ -305,13 +305,17 @@ class GradioUI:
|
|
305 |
read_file = FileReader(space_id=space_id, folder_path=temp_dir)
|
306 |
write_file = FileWriter(space_id=space_id, folder_path=temp_dir)
|
307 |
commit_changes = CommitChanges(space_id=space_id, folder_path=temp_dir)
|
|
|
|
|
|
|
|
|
308 |
|
309 |
# Initialize SWE Agent with enhanced capabilities and improved description
|
310 |
swe_agent = CodeAgent(
|
311 |
model=model,
|
312 |
prompt_templates=swe_agent_prompt_templates,
|
313 |
verbosity_level=1,
|
314 |
-
tools=[search, scraper, read_file, write_file],
|
315 |
name="swe_agent",
|
316 |
description="An expert Software Engineer capable of designing, developing, and debugging code. This agent can read and write files, search the web, and scrape web content to assist in coding tasks. It excels at implementing detailed technical specifications provided by the Planning Agent."
|
317 |
)
|
@@ -456,7 +460,7 @@ class GradioUI:
|
|
456 |
)
|
457 |
|
458 |
def launch(self, share: bool = True, **kwargs):
|
459 |
-
self.create_app().launch(debug=True, share=share, **kwargs)
|
460 |
|
461 |
def create_app(self):
|
462 |
import gradio as gr
|
@@ -556,8 +560,8 @@ def run_gradio_agent():
|
|
556 |
"""Runs the Gradio UI for the agent."""
|
557 |
# The GradioUI class now handles space_id input and agent initialization internally
|
558 |
GradioUI(
|
559 |
-
agent_name="
|
560 |
-
agent_description="Interact with a
|
561 |
).launch()
|
562 |
|
563 |
if __name__ == "__main__":
|
|
|
11 |
from smolagents.memory import ActionStep, FinalAnswerStep, MemoryStep
|
12 |
from smolagents.models import ChatMessageStreamDelta
|
13 |
from smolagents.utils import _is_package_available
|
14 |
+
from tools import search, scraper, FileReader, FileWriter, CommitChanges, get_repository_structure, FileCopier, FileMover, FileDeleter, FileSearcher
|
15 |
|
16 |
def get_step_footnote_content(step_log: MemoryStep, step_name: str) -> str:
|
17 |
"""Get a footnote string for a step log with duration and token information"""
|
|
|
305 |
read_file = FileReader(space_id=space_id, folder_path=temp_dir)
|
306 |
write_file = FileWriter(space_id=space_id, folder_path=temp_dir)
|
307 |
commit_changes = CommitChanges(space_id=space_id, folder_path=temp_dir)
|
308 |
+
file_copier = FileCopier(space_id=space_id, folder_path=temp_dir)
|
309 |
+
file_mover = FileMover(space_id=space_id, folder_path=temp_dir)
|
310 |
+
file_deleter = FileDeleter(space_id=space_id, folder_path=temp_dir)
|
311 |
+
file_searcher = FileSearcher(space_id=space_id, folder_path=temp_dir)
|
312 |
|
313 |
# Initialize SWE Agent with enhanced capabilities and improved description
|
314 |
swe_agent = CodeAgent(
|
315 |
model=model,
|
316 |
prompt_templates=swe_agent_prompt_templates,
|
317 |
verbosity_level=1,
|
318 |
+
tools=[search, scraper, read_file, write_file, file_copier, file_mover, file_deleter, file_searcher],
|
319 |
name="swe_agent",
|
320 |
description="An expert Software Engineer capable of designing, developing, and debugging code. This agent can read and write files, search the web, and scrape web content to assist in coding tasks. It excels at implementing detailed technical specifications provided by the Planning Agent."
|
321 |
)
|
|
|
460 |
)
|
461 |
|
462 |
def launch(self, share: bool = True, **kwargs):
|
463 |
+
self.create_app().launch(debug=True, share=share, create_mcp_server=True, **kwargs)
|
464 |
|
465 |
def create_app(self):
|
466 |
import gradio as gr
|
|
|
560 |
"""Runs the Gradio UI for the agent."""
|
561 |
# The GradioUI class now handles space_id input and agent initialization internally
|
562 |
GradioUI(
|
563 |
+
agent_name="SmolSWE Gradio Interface",
|
564 |
+
agent_description="Interact with a SmolSWE an AI assistant for software development. Currently in beta. It can do tasks like refactoring, debugging, adding any features, and more."
|
565 |
).launch()
|
566 |
|
567 |
if __name__ == "__main__":
|
tools.py
CHANGED
@@ -1,154 +1,316 @@
|
|
1 |
-
from googlesearch import search as google_search
|
2 |
-
from smolagents import tool, Tool
|
3 |
-
from curl_scraper import BasicScraper
|
4 |
-
from huggingface_hub import hf_hub_download, upload_folder
|
5 |
-
import os
|
6 |
-
|
7 |
-
class FileReader(Tool):
|
8 |
-
name = "read_file"
|
9 |
-
description = "Reads a file and returns the contents as a string."
|
10 |
-
inputs = {"file": {"type": "string", "description": "The absolute path to the file to be read."}}
|
11 |
-
output_type = "string"
|
12 |
-
|
13 |
-
def __init__(self, space_id, folder_path, **kwargs):
|
14 |
-
super().__init__()
|
15 |
-
self.space_id = space_id
|
16 |
-
self.folder_path = folder_path
|
17 |
-
|
18 |
-
def forward(self, file: str) -> str:
|
19 |
-
file_path = os.path.join(self.folder_path, file)
|
20 |
-
if not os.path.exists(file_path):
|
21 |
-
hf_hub_download(repo_id=self.space_id, filename=file, repo_type="space", local_dir=self.folder_path, local_dir_use_symlinks=False)
|
22 |
-
with open(file_path, 'r', encoding='utf-8') as f:
|
23 |
-
content = f.read()
|
24 |
-
|
25 |
-
return content
|
26 |
-
|
27 |
-
class FileWriter(Tool):
|
28 |
-
name = "write_file"
|
29 |
-
description = "Overwrite or append content to a file. Use for creating new files, appending content, or modifying existing files."
|
30 |
-
inputs = {
|
31 |
-
"file": {"type": "string", "description": "The absolute path to the file to be written."},
|
32 |
-
"content": {"type": "string", "description": "The content to be written to the file."},
|
33 |
-
"append": {"type": "boolean", "description": "Whether to append to the file or overwrite it.", "nullable": True},
|
34 |
-
}
|
35 |
-
output_type = "string"
|
36 |
-
|
37 |
-
def __init__(self, space_id, folder_path, **kwargs):
|
38 |
-
super().__init__()
|
39 |
-
self.space_id = space_id
|
40 |
-
self.folder_path = folder_path
|
41 |
-
|
42 |
-
def forward(self, file: str, content: str, append: bool = False) -> str:
|
43 |
-
file_path = os.path.join(self.folder_path, file)
|
44 |
-
mode = 'a' if append else 'w'
|
45 |
-
with open(file_path, mode, encoding='utf-8') as f:
|
46 |
-
f.write(content if append else content.strip())
|
47 |
-
return f"Successfully {'appended to' if append else 'wrote to'} file at {file_path}"
|
48 |
-
|
49 |
-
@tool
|
50 |
-
def search(query: str) -> list:
|
51 |
-
"""
|
52 |
-
Search for web pages and scrape their content.
|
53 |
-
|
54 |
-
Args:
|
55 |
-
query (str): The search query.
|
56 |
-
|
57 |
-
Returns:
|
58 |
-
list: List of top web results including url, title, description from the search results.
|
59 |
-
"""
|
60 |
-
# Get search results
|
61 |
-
results = []
|
62 |
-
for result in google_search(query, advanced=True, unique=True):
|
63 |
-
results.append(result)
|
64 |
-
return results
|
65 |
-
|
66 |
-
@tool
|
67 |
-
def scraper(url: str, response_type: str = 'plain_text') -> str:
|
68 |
-
"""
|
69 |
-
Scrapes the content of a web page. Important: Don't use regex to extract information from the output of a this scraper tool, Think more and find answer yourself.
|
70 |
-
|
71 |
-
Args:
|
72 |
-
url (str): The URL of the web page to scrape.
|
73 |
-
response_type (str): The type of response to return. Valid options are 'markdown' and 'plain_text'. Default is 'plain_text'.
|
74 |
-
|
75 |
-
Returns:
|
76 |
-
str: The content of the web page in the specified format.
|
77 |
-
"""
|
78 |
-
response = BasicScraper(timeout=5).get(url)
|
79 |
-
if response_type == 'markdown':
|
80 |
-
return response.markdown
|
81 |
-
elif response_type == 'plain_text':
|
82 |
-
return response.plain_text
|
83 |
-
else:
|
84 |
-
raise ValueError("Invalid response_type. Use 'markdown' or 'plain_text'.")
|
85 |
-
|
86 |
-
class CommitChanges(Tool):
|
87 |
-
name = "commit_changes"
|
88 |
-
description = "Commits changes to a repository."
|
89 |
-
inputs = {
|
90 |
-
"commit_message": {"type": "string", "description": "The commit message."},
|
91 |
-
"commit_description": {"type": "string", "description": "The commit description."},
|
92 |
-
}
|
93 |
-
output_type = "string"
|
94 |
-
|
95 |
-
def __init__(self, space_id, folder_path, **kwargs):
|
96 |
-
super().__init__()
|
97 |
-
self.space_id = space_id
|
98 |
-
self.folder_path = folder_path
|
99 |
-
|
100 |
-
def forward(self, commit_message: str, commit_description: str) -> str:
|
101 |
-
try:
|
102 |
-
upload_folder(
|
103 |
-
folder_path=self.folder_path,
|
104 |
-
repo_id=self.space_id,
|
105 |
-
repo_type="space",
|
106 |
-
create_pr=True,
|
107 |
-
commit_message=commit_message,
|
108 |
-
commit_description=commit_description,
|
109 |
-
)
|
110 |
-
return "Changes committed successfully."
|
111 |
-
except Exception as e:
|
112 |
-
return f"Error committing changes: {str(e)}"
|
113 |
-
|
114 |
-
from huggingface_hub import HfApi
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
154 |
return '\n'.join(output_lines)
|
|
|
1 |
+
from googlesearch import search as google_search
|
2 |
+
from smolagents import tool, Tool
|
3 |
+
from curl_scraper import BasicScraper
|
4 |
+
from huggingface_hub import hf_hub_download, upload_folder
|
5 |
+
import os
|
6 |
+
|
7 |
+
class FileReader(Tool):
|
8 |
+
name = "read_file"
|
9 |
+
description = "Reads a file and returns the contents as a string."
|
10 |
+
inputs = {"file": {"type": "string", "description": "The absolute path to the file to be read."}}
|
11 |
+
output_type = "string"
|
12 |
+
|
13 |
+
def __init__(self, space_id, folder_path, **kwargs):
|
14 |
+
super().__init__()
|
15 |
+
self.space_id = space_id
|
16 |
+
self.folder_path = folder_path
|
17 |
+
|
18 |
+
def forward(self, file: str) -> str:
|
19 |
+
file_path = os.path.join(self.folder_path, file)
|
20 |
+
if not os.path.exists(file_path):
|
21 |
+
hf_hub_download(repo_id=self.space_id, filename=file, repo_type="space", local_dir=self.folder_path, local_dir_use_symlinks=False)
|
22 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
23 |
+
content = f.read()
|
24 |
+
|
25 |
+
return content
|
26 |
+
|
27 |
+
class FileWriter(Tool):
|
28 |
+
name = "write_file"
|
29 |
+
description = "Overwrite or append content to a file. Use for creating new files, appending content, or modifying existing files."
|
30 |
+
inputs = {
|
31 |
+
"file": {"type": "string", "description": "The absolute path to the file to be written."},
|
32 |
+
"content": {"type": "string", "description": "The content to be written to the file."},
|
33 |
+
"append": {"type": "boolean", "description": "Whether to append to the file or overwrite it.", "nullable": True},
|
34 |
+
}
|
35 |
+
output_type = "string"
|
36 |
+
|
37 |
+
def __init__(self, space_id, folder_path, **kwargs):
|
38 |
+
super().__init__()
|
39 |
+
self.space_id = space_id
|
40 |
+
self.folder_path = folder_path
|
41 |
+
|
42 |
+
def forward(self, file: str, content: str, append: bool = False) -> str:
|
43 |
+
file_path = os.path.join(self.folder_path, file)
|
44 |
+
mode = 'a' if append else 'w'
|
45 |
+
with open(file_path, mode, encoding='utf-8') as f:
|
46 |
+
f.write(content if append else content.strip())
|
47 |
+
return f"Successfully {'appended to' if append else 'wrote to'} file at {file_path}"
|
48 |
+
|
49 |
+
@tool
|
50 |
+
def search(query: str) -> list:
|
51 |
+
"""
|
52 |
+
Search for web pages and scrape their content.
|
53 |
+
|
54 |
+
Args:
|
55 |
+
query (str): The search query.
|
56 |
+
|
57 |
+
Returns:
|
58 |
+
list: List of top web results including url, title, description from the search results.
|
59 |
+
"""
|
60 |
+
# Get search results
|
61 |
+
results = []
|
62 |
+
for result in google_search(query, advanced=True, unique=True):
|
63 |
+
results.append(result)
|
64 |
+
return results
|
65 |
+
|
66 |
+
@tool
|
67 |
+
def scraper(url: str, response_type: str = 'plain_text') -> str:
|
68 |
+
"""
|
69 |
+
Scrapes the content of a web page. Important: Don't use regex to extract information from the output of a this scraper tool, Think more and find answer yourself.
|
70 |
+
|
71 |
+
Args:
|
72 |
+
url (str): The URL of the web page to scrape.
|
73 |
+
response_type (str): The type of response to return. Valid options are 'markdown' and 'plain_text'. Default is 'plain_text'.
|
74 |
+
|
75 |
+
Returns:
|
76 |
+
str: The content of the web page in the specified format.
|
77 |
+
"""
|
78 |
+
response = BasicScraper(timeout=5).get(url)
|
79 |
+
if response_type == 'markdown':
|
80 |
+
return response.markdown
|
81 |
+
elif response_type == 'plain_text':
|
82 |
+
return response.plain_text
|
83 |
+
else:
|
84 |
+
raise ValueError("Invalid response_type. Use 'markdown' or 'plain_text'.")
|
85 |
+
|
86 |
+
class CommitChanges(Tool):
|
87 |
+
name = "commit_changes"
|
88 |
+
description = "Commits changes to a repository."
|
89 |
+
inputs = {
|
90 |
+
"commit_message": {"type": "string", "description": "The commit message."},
|
91 |
+
"commit_description": {"type": "string", "description": "The commit description."},
|
92 |
+
}
|
93 |
+
output_type = "string"
|
94 |
+
|
95 |
+
def __init__(self, space_id, folder_path, **kwargs):
|
96 |
+
super().__init__()
|
97 |
+
self.space_id = space_id
|
98 |
+
self.folder_path = folder_path
|
99 |
+
|
100 |
+
def forward(self, commit_message: str, commit_description: str) -> str:
|
101 |
+
try:
|
102 |
+
upload_folder(
|
103 |
+
folder_path=self.folder_path,
|
104 |
+
repo_id=self.space_id,
|
105 |
+
repo_type="space",
|
106 |
+
create_pr=True,
|
107 |
+
commit_message=commit_message,
|
108 |
+
commit_description=commit_description,
|
109 |
+
)
|
110 |
+
return "Changes committed successfully."
|
111 |
+
except Exception as e:
|
112 |
+
return f"Error committing changes: {str(e)}"
|
113 |
+
|
114 |
+
from huggingface_hub import HfApi
|
115 |
+
import shutil
|
116 |
+
import os
|
117 |
+
import fnmatch
|
118 |
+
from typing import List, Dict, Any, Optional, Union
|
119 |
+
from pathlib import Path
|
120 |
+
|
121 |
+
|
122 |
+
class FileMover(Tool):
|
123 |
+
name = "move_file"
|
124 |
+
description = "Move or rename a file or directory."
|
125 |
+
inputs = {
|
126 |
+
"source": {"type": "string", "description": "Source file or directory path"},
|
127 |
+
"destination": {"type": "string", "description": "Destination path"}
|
128 |
+
}
|
129 |
+
output_type = "string"
|
130 |
+
|
131 |
+
def __init__(self, space_id, folder_path, **kwargs):
|
132 |
+
super().__init__()
|
133 |
+
self.space_id = space_id
|
134 |
+
self.folder_path = folder_path
|
135 |
+
|
136 |
+
def forward(self, source: str, destination: str) -> str:
|
137 |
+
src_path = os.path.join(self.folder_path, source)
|
138 |
+
dst_path = os.path.join(self.folder_path, destination)
|
139 |
+
|
140 |
+
if not os.path.exists(src_path):
|
141 |
+
return f"Error: Source path does not exist: {src_path}"
|
142 |
+
|
143 |
+
try:
|
144 |
+
shutil.move(src_path, dst_path)
|
145 |
+
return f"Successfully moved {src_path} to {dst_path}"
|
146 |
+
except Exception as e:
|
147 |
+
return f"Error moving {src_path}: {str(e)}"
|
148 |
+
|
149 |
+
|
150 |
+
class FileCopier(Tool):
|
151 |
+
name = "copy_file"
|
152 |
+
description = "Copy a file or directory."
|
153 |
+
inputs = {
|
154 |
+
"source": {"type": "string", "description": "Source file or directory path"},
|
155 |
+
"destination": {"type": "string", "description": "Destination path"}
|
156 |
+
}
|
157 |
+
output_type = "string"
|
158 |
+
|
159 |
+
def __init__(self, space_id, folder_path, **kwargs):
|
160 |
+
super().__init__()
|
161 |
+
self.space_id = space_id
|
162 |
+
self.folder_path = folder_path
|
163 |
+
|
164 |
+
def forward(self, source: str, destination: str) -> str:
|
165 |
+
src_path = os.path.join(self.folder_path, source)
|
166 |
+
dst_path = os.path.join(self.folder_path, destination)
|
167 |
+
|
168 |
+
if not os.path.exists(src_path):
|
169 |
+
return f"Error: Source path does not exist: {src_path}"
|
170 |
+
|
171 |
+
try:
|
172 |
+
if os.path.isdir(src_path):
|
173 |
+
shutil.copytree(src_path, dst_path)
|
174 |
+
else:
|
175 |
+
shutil.copy2(src_path, dst_path)
|
176 |
+
return f"Successfully copied {src_path} to {dst_path}"
|
177 |
+
except Exception as e:
|
178 |
+
return f"Error copying {src_path}: {str(e)}"
|
179 |
+
|
180 |
+
|
181 |
+
class FileDeleter(Tool):
|
182 |
+
name = "delete_file"
|
183 |
+
description = "Delete a file or directory."
|
184 |
+
inputs = {
|
185 |
+
"path": {"type": "string", "description": "Path to file or directory to delete"}
|
186 |
+
}
|
187 |
+
output_type = "string"
|
188 |
+
|
189 |
+
def __init__(self, space_id, folder_path, **kwargs):
|
190 |
+
super().__init__()
|
191 |
+
self.space_id = space_id
|
192 |
+
self.folder_path = folder_path
|
193 |
+
|
194 |
+
def forward(self, path: str) -> str:
|
195 |
+
full_path = os.path.join(self.folder_path, path)
|
196 |
+
|
197 |
+
if not os.path.exists(full_path):
|
198 |
+
return f"Error: Path does not exist: {full_path}"
|
199 |
+
|
200 |
+
try:
|
201 |
+
if os.path.isdir(full_path):
|
202 |
+
shutil.rmtree(full_path)
|
203 |
+
else:
|
204 |
+
os.remove(full_path)
|
205 |
+
return f"Successfully deleted: {full_path}"
|
206 |
+
except Exception as e:
|
207 |
+
return f"Error deleting {full_path}: {str(e)}"
|
208 |
+
|
209 |
+
|
210 |
+
class FileSearcher(Tool):
|
211 |
+
name = "search_files"
|
212 |
+
description = "Search for text within files in the repository."
|
213 |
+
inputs = {
|
214 |
+
"search_term": {"type": "string", "description": "Text to search for"},
|
215 |
+
"file_pattern": {"type": "string", "description": "File pattern to search in (e.g., '*.txt' for all .txt files)", "default": "*"},
|
216 |
+
"case_sensitive": {"type": "boolean", "description": "Whether the search should be case sensitive", "default": False}
|
217 |
+
}
|
218 |
+
output_type = "list"
|
219 |
+
|
220 |
+
def __init__(self, space_id, folder_path, **kwargs):
|
221 |
+
super().__init__()
|
222 |
+
self.space_id = space_id
|
223 |
+
self.folder_path = folder_path
|
224 |
+
|
225 |
+
def _find_files(self, pattern: str) -> List[str]:
|
226 |
+
"""Find all files matching the pattern in the repository."""
|
227 |
+
matches = []
|
228 |
+
for root, _, filenames in os.walk(self.folder_path):
|
229 |
+
for filename in fnmatch.filter(filenames, pattern):
|
230 |
+
matches.append(os.path.join(root, filename))
|
231 |
+
return matches
|
232 |
+
|
233 |
+
def _search_in_file(self, file_path: str, search_term: str, case_sensitive: bool) -> List[Dict[str, Any]]:
|
234 |
+
"""Search for the term in a single file."""
|
235 |
+
matches = []
|
236 |
+
try:
|
237 |
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
238 |
+
for line_num, line in enumerate(f, 1):
|
239 |
+
if not case_sensitive:
|
240 |
+
if search_term.lower() in line.lower():
|
241 |
+
matches.append({
|
242 |
+
'file': os.path.relpath(file_path, self.folder_path),
|
243 |
+
'line': line_num,
|
244 |
+
'content': line.strip()
|
245 |
+
})
|
246 |
+
else:
|
247 |
+
if search_term in line:
|
248 |
+
matches.append({
|
249 |
+
'file': os.path.relpath(file_path, self.folder_path),
|
250 |
+
'line': line_num,
|
251 |
+
'content': line.strip()
|
252 |
+
})
|
253 |
+
except Exception as e:
|
254 |
+
return [{'file': file_path, 'error': f"Error reading file: {str(e)}"}]
|
255 |
+
return matches
|
256 |
+
|
257 |
+
def forward(self, search_term: str, file_pattern: str = "*", case_sensitive: bool = False) -> List[Dict[str, Any]]:
|
258 |
+
if not search_term.strip():
|
259 |
+
return [{"error": "Search term cannot be empty"}]
|
260 |
+
|
261 |
+
try:
|
262 |
+
matching_files = self._find_files(file_pattern)
|
263 |
+
if not matching_files:
|
264 |
+
return [{"message": f"No files found matching pattern: {file_pattern}"}]
|
265 |
+
|
266 |
+
results = []
|
267 |
+
for file_path in matching_files:
|
268 |
+
file_matches = self._search_in_file(file_path, search_term, case_sensitive)
|
269 |
+
if file_matches:
|
270 |
+
results.extend(file_matches)
|
271 |
+
|
272 |
+
return results if results else [{"message": "No matches found"}]
|
273 |
+
|
274 |
+
except Exception as e:
|
275 |
+
return [{"error": f"Error during search: {str(e)}"}]
|
276 |
+
|
277 |
+
|
278 |
+
def get_repository_structure(space_id: str) -> str:
|
279 |
+
api = HfApi()
|
280 |
+
space_info = api.space_info(space_id)
|
281 |
+
|
282 |
+
printed_dirs = set()
|
283 |
+
output_lines = []
|
284 |
+
sorted_siblings = sorted(space_info.siblings, key=lambda x: x.rfilename)
|
285 |
+
|
286 |
+
for sibling in sorted_siblings:
|
287 |
+
rfilename = sibling.rfilename
|
288 |
+
path_parts = rfilename.split('/')
|
289 |
+
|
290 |
+
current_dir_path_parts = []
|
291 |
+
for i in range(len(path_parts) - 1):
|
292 |
+
dir_name = path_parts[i]
|
293 |
+
current_dir_path_parts.append(dir_name)
|
294 |
+
dir_path = '/'.join(current_dir_path_parts)
|
295 |
+
|
296 |
+
if dir_path not in printed_dirs:
|
297 |
+
depth = i
|
298 |
+
indent = ' ' * depth
|
299 |
+
output_lines.append(f"{indent}{dir_name}")
|
300 |
+
printed_dirs.add(dir_path)
|
301 |
+
|
302 |
+
file_name = path_parts[-1]
|
303 |
+
file_indent_depth = len(path_parts) - 1
|
304 |
+
file_indent = ' ' * file_indent_depth
|
305 |
+
|
306 |
+
if file_indent_depth == 0:
|
307 |
+
output_lines.append(file_name)
|
308 |
+
else:
|
309 |
+
is_last_file = sibling == sorted_siblings[-1] or not any(
|
310 |
+
s.rfilename.startswith('/'.join(path_parts[:-1]) + '/')
|
311 |
+
for s in sorted_siblings[sorted_siblings.index(sibling)+1:]
|
312 |
+
)
|
313 |
+
file_prefix = '└─ ' if is_last_file else '├─ '
|
314 |
+
output_lines.append(f"{file_indent}{file_prefix}{file_name}")
|
315 |
+
|
316 |
return '\n'.join(output_lines)
|