diff --git a/Dockerfile b/Dockerfile index e0ff00c..6102adb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,6 +28,20 @@ COPY web . RUN npm run build FROM requirements +RUN apt-get update +RUN apt-get install -y wget gnupg +RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - +RUN echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list +RUN apt-get update +RUN apt-get install -y google-chrome-stable +RUN rm -rf /var/lib/apt/lists/* + +COPY ./tools/itb/requirements.txt /root/sia/tools/itb/requirements.txt +RUN cd /root/sia/tools/itb/ && python3 -m pip install -r requirements.txt + +COPY ./tools/ /root/sia/tools/ +RUN cd /root/sia/tools/itb/ && python3 -m pip install -e ".[dev]" + COPY ./ /root/sia/ COPY --from=web-build /app/dist /root/sia/static/ WORKDIR /root/sia diff --git a/container.sh b/container.sh new file mode 100755 index 0000000..273376d --- /dev/null +++ b/container.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +container_id=$(docker ps -q) +docker exec -it $container_id /bin/bash \ No newline at end of file diff --git a/selenium/dom.py b/selenium/dom.py deleted file mode 100644 index e062265..0000000 --- a/selenium/dom.py +++ /dev/null @@ -1,38 +0,0 @@ -from selenium import webdriver -from selenium.webdriver.chrome.options import Options -from selenium.webdriver.common.by import By -import json - -def connect_to_chrome(): - """ - Connect to an existing Chrome browser instance and print the DOM - """ - # Chrome options for connecting to existing browser - chrome_options = Options() - chrome_options.add_experimental_option("debuggerAddress", "127.0.0.1:1234") - - try: - # Connect to the existing browser - driver = webdriver.Chrome(options=chrome_options) - - # Get the current page's DOM - dom = driver.find_element(By.TAG_NAME, 'html').get_attribute('outerHTML') - - # Pretty print the DOM - parsed_dom = json.loads(json.dumps(dom)) - print(json.dumps(parsed_dom, indent=2)) - - return driver - - except Exception as e: - print(f"Error connecting to Chrome: {str(e)}") - print("\nMake sure Chrome is running with remote debugging enabled!") - print("Start Chrome with: chrome.exe --remote-debugging-port=9222") - return None - -if __name__ == "__main__": - driver = connect_to_chrome() - if driver: - # Keep the connection open until user input - input("Press Enter to close the connection...") - driver.quit() diff --git a/selenium/install.sh b/selenium/install.sh deleted file mode 100644 index a663daf..0000000 --- a/selenium/install.sh +++ /dev/null @@ -1,6 +0,0 @@ -apt-get update && apt-get install gnupg wget -y && \ - wget --quiet --output-document=- https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor > /etc/apt/trusted.gpg.d/google-archive.gpg && \ - sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' && \ - apt-get update && \ - apt-get install google-chrome-stable -y --no-install-recommends && \ - rm -rf /var/lib/apt/lists/* \ No newline at end of file diff --git a/selenium/start.sh b/selenium/start.sh deleted file mode 100644 index 735c264..0000000 --- a/selenium/start.sh +++ /dev/null @@ -1,5 +0,0 @@ -/opt/google/chrome/chrome \ ---no-sandbox \ ---headless=new \ ---remote-debugging-port=1234 \ -https://google.be & \ No newline at end of file diff --git a/sia/__main__.py b/sia/__main__.py index 84dfb50..db159ca 100644 --- a/sia/__main__.py +++ b/sia/__main__.py @@ -73,13 +73,13 @@ class Main: metrics=SystemMetrics(), llms=self._llms, validator=XMLValidator(self._action_schema), - parser=ResponseParser(self._io_buffer), + parser=ResponseParser(config.work_dir, self._io_buffer), iteration_logger=IterationLogger(self._config.iterations_dir, self._system_prompt, self._action_schema), ) self._auto_approver = AutoApprover(self._agent) self._app = web.Application() - self._api = Api(self._app, self._agent, self._io_buffer, self._working_memory, self._auto_approver) + self._api = Api(config.work_dir, self._app, self._agent, self._io_buffer, self._working_memory, self._auto_approver) self._websockets = Websockets(self._app, self._agent, self._io_buffer, self._auto_approver, self._working_memory) self._static = Static(self._app, self._config) diff --git a/sia/config.py b/sia/config.py index b70bc6d..45be752 100644 --- a/sia/config.py +++ b/sia/config.py @@ -30,6 +30,12 @@ class Config: default=os.getenv('SIA_ITERATIONS_DIR', 'iterations'), help='Path to the directory for storing iterations (default: iterations, env: SIA_ITERATIONS_DIR)' ) + parser.add_argument( + '--work-dir', + type=Path, + default=os.getenv('SIA_WORK_DIR', '/root'), + help='Path to the working directory (default: /root, env: SIA_WORK_DIR)' + ) # Web server configuration parser.add_argument( @@ -205,6 +211,10 @@ class Config: @property def iterations_dir(self) -> Path: return self.args.iterations_dir + + @property + def work_dir(self) -> Path: + return self.args.work_dir # Server properties @property diff --git a/sia/entry/background_entry.py b/sia/entry/background_entry.py index a25b1c0..e5ddaa5 100644 --- a/sia/entry/background_entry.py +++ b/sia/entry/background_entry.py @@ -19,6 +19,7 @@ class BackgroundEntry(Entry): def __init__( self, id: str, + work_dir: str, script: str, ): """ @@ -26,9 +27,11 @@ class BackgroundEntry(Entry): Args: id: Unique identifier for this entry + work_dir: Working directory for the process script: The script/command to execute """ super().__init__(id) + self.work_dir = work_dir self.script = script self._process: Optional[subprocess.Popen] = None self.stdout = "" @@ -81,6 +84,7 @@ class BackgroundEntry(Entry): if self._process is None and self.exit_code is None: self._process = subprocess.Popen( self.script, + cwd=self.work_dir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -155,6 +159,7 @@ class BackgroundEntry(Entry): return { "type": "background", "id": self.id, + "work_dir": str(self.work_dir), "script": self.script, "stdout": self.stdout, "stderr": self.stderr, diff --git a/sia/entry/entry_factory.py b/sia/entry/entry_factory.py index 1b045e9..7b19841 100644 --- a/sia/entry/entry_factory.py +++ b/sia/entry/entry_factory.py @@ -1,4 +1,4 @@ -from typing import Optional +from pathlib import Path from . import Entry from ..io_buffer import IOBuffer @@ -11,12 +11,12 @@ from .single_entry import SingleEntry from .write_entry import WriteEntry class EntryFactory: - def create_entry(data: dict, io_buffer: IOBuffer) -> Entry: + def create_entry(data: dict, work_dir: Path, io_buffer: IOBuffer) -> Entry: entry_type = data.get("type") entry_id = data.get("id") if entry_type == "background": - return BackgroundEntry(entry_id, data["script"]) + return BackgroundEntry(entry_id, work_dir, data["script"]) elif entry_type == "read_stdin": return ReadEntry(entry_id, io_buffer) @@ -27,6 +27,7 @@ class EntryFactory: elif entry_type == "repeat": return RepeatEntry( entry_id, + work_dir, data["script"], data.get("timeout"), data.get("limit") @@ -35,6 +36,7 @@ class EntryFactory: elif entry_type == "single": return SingleEntry( entry_id, + work_dir, data["script"], data.get("timeout"), data.get("limit") @@ -51,6 +53,8 @@ class EntryFactory: entry.id = data["id"] if isinstance(entry, SingleEntry): + if "work_dir" in data: + entry.work_dir = Path(data["work_dir"]) if "script" in data: entry.script = data["script"] if "timeout" in data: @@ -69,6 +73,8 @@ class EntryFactory: entry.timed_out = bool(data["timed_out"]) if isinstance(entry, RepeatEntry): + if "work_dir" in data: + entry.work_dir = Path(data["work_dir"]) if "script" in data: entry.script = data["script"] if "timeout" in data: @@ -87,6 +93,8 @@ class EntryFactory: entry.timed_out = bool(data["timed_out"]) elif isinstance(entry, BackgroundEntry): + if "work_dir" in data: + entry.work_dir = Path(data["work_dir"]) if "script" in data: entry.script = data["script"] if "stdout" in data: diff --git a/sia/entry/repeat_entry.py b/sia/entry/repeat_entry.py index ecd1cde..8f9fcb0 100644 --- a/sia/entry/repeat_entry.py +++ b/sia/entry/repeat_entry.py @@ -1,3 +1,4 @@ +from pathlib import Path import subprocess import xml.etree.ElementTree as ET from typing import Optional @@ -15,6 +16,7 @@ class RepeatEntry(Entry): def __init__( self, id: str, + work_dir: Path, script: str, timeout: Optional[float] = None, limit: Optional[int] = None, @@ -29,6 +31,7 @@ class RepeatEntry(Entry): limit: Maximum number of characters to capture from stdout/stderr """ super().__init__(id) + self.work_dir = work_dir self.script = script self.timeout = timeout self.limit = limit @@ -45,6 +48,7 @@ class RepeatEntry(Entry): try: process = subprocess.run( self.script, + cwd=self.work_dir, timeout=(self.timeout or self.default_timeout), shell=True, capture_output=True, @@ -95,6 +99,7 @@ class RepeatEntry(Entry): return { "type": "repeat", "id": self.id, + "work_dir": str(self.work_dir), "script": self.script, "timeout": self.timeout, "limit": self.limit, diff --git a/sia/entry/single_entry.py b/sia/entry/single_entry.py index eb965c6..ee1e3ae 100644 --- a/sia/entry/single_entry.py +++ b/sia/entry/single_entry.py @@ -1,3 +1,4 @@ +from pathlib import Path import subprocess import xml.etree.ElementTree as ET from typing import Optional @@ -15,6 +16,7 @@ class SingleEntry(Entry): def __init__( self, id: str, + work_dir: Path, script: str, timeout: Optional[float] = None, limit: Optional[int] = None, @@ -29,6 +31,7 @@ class SingleEntry(Entry): limit: Maximum number of characters to capture from stdout/stderr """ super().__init__(id) + self.work_dir = work_dir self.script = script self.timeout = timeout self.limit = limit @@ -58,6 +61,7 @@ class SingleEntry(Entry): try: process = subprocess.run( self.script, + cwd=self.work_dir, timeout=(self.timeout or self.default_timeout), shell=True, capture_output=True, @@ -106,6 +110,7 @@ class SingleEntry(Entry): return { "type": "single", "id": self.id, + "work_dir": str(self.work_dir), "script": self.script, "timeout": self.timeout, "limit": self.limit, diff --git a/sia/iteration_parser.py b/sia/iteration_parser.py index ed37f21..1aeff23 100644 --- a/sia/iteration_parser.py +++ b/sia/iteration_parser.py @@ -1,6 +1,6 @@ -from datetime import datetime -import xml.etree.ElementTree as ET +from pathlib import Path from typing import List +import xml.etree.ElementTree as ET from .entry import Entry from .entry.background_entry import BackgroundEntry @@ -15,7 +15,7 @@ class IterationParser: """Parses iteration XML files into entries""" @staticmethod - def parse_iteration(content: str, io_buffer) -> tuple[str, str, List[Entry]]: + def parse_iteration(content: str, work_dir: Path, io_buffer) -> tuple[str, str, List[Entry]]: """ Parse iteration XML content into context, response and entries @@ -37,7 +37,7 @@ class IterationParser: entries = [] for elem in context: if elem.tag == "background": - entries.append(IterationParser._parse_background(elem)) + entries.append(IterationParser._parse_background(elem, work_dir)) elif elem.tag == "parse_error": entries.append(IterationParser._parse_parse_error(elem)) elif elem.tag == "read_stdin": @@ -45,19 +45,20 @@ class IterationParser: elif elem.tag == "reasoning": entries.append(IterationParser._parse_reasoning(elem)) elif elem.tag == "repeat": - entries.append(IterationParser._parse_repeat(elem)) + entries.append(IterationParser._parse_repeat(elem, work_dir)) elif elem.tag == "single": - entries.append(IterationParser._parse_single(elem)) + entries.append(IterationParser._parse_single(elem, work_dir)) elif elem.tag == "write_stdout": entries.append(IterationParser._parse_write(elem, io_buffer)) return entries @staticmethod - def _parse_background(elem: ET.Element) -> BackgroundEntry: + def _parse_background(elem: ET.Element, work_dir: Path) -> BackgroundEntry: entry = BackgroundEntry( id=elem.get("id"), - script=elem.text + script=elem.text, + work_dir=work_dir ) if elem.get("exit_code"): entry.exit_code = int(elem.get("exit_code")) @@ -97,9 +98,10 @@ class IterationParser: ) @staticmethod - def _parse_repeat(elem: ET.Element) -> RepeatEntry: + def _parse_repeat(elem: ET.Element, work_dir: Path) -> RepeatEntry: entry = RepeatEntry( id=elem.get("id"), + work_dir=work_dir, script=elem.text, timeout=float(elem.get("timeout")) if elem.get("timeout") else None, limit=int(elem.get("limit")) if elem.get("limit") else None @@ -116,9 +118,10 @@ class IterationParser: return entry @staticmethod - def _parse_single(elem: ET.Element) -> SingleEntry: + def _parse_single(elem: ET.Element, work_dir: Path) -> SingleEntry: entry = SingleEntry( id=elem.get("id"), + work_dir=work_dir, script=elem.text, timeout=float(elem.get("timeout")) if elem.get("timeout") else None, limit=int(elem.get("limit")) if elem.get("limit") else None diff --git a/sia/response_parser.py b/sia/response_parser.py index 017d7da..ef4991b 100644 --- a/sia/response_parser.py +++ b/sia/response_parser.py @@ -1,4 +1,5 @@ from datetime import datetime +from pathlib import Path import xml.etree.ElementTree as ET from typing import Union @@ -30,13 +31,15 @@ class ResponseParser: io_buffer: Buffer to use for IO operations """ - def __init__(self, io_buffer: IOBuffer): + def __init__(self, work_dir: Path, io_buffer: IOBuffer): """ Initialize parser with IO buffer. Args: + work_dir: Workdir for the scripts io_buffer: Buffer to use for IO operations """ + self._work_dir = work_dir self._io_buffer = io_buffer @property @@ -87,7 +90,7 @@ class ResponseParser: elif root.tag == 'background': if len(root) != 0 or root.attrib or root.text is None or root.text.strip() == '': return ParseErrorEntry(entry_id, xml, "Background entry requires (only) script content") - return BackgroundEntry(entry_id, root.text) + return BackgroundEntry(entry_id, self._work_dir, root.text) elif root.tag == 'repeat': if len(root) != 0 or root.text is None or root.text.strip() == '': @@ -98,7 +101,7 @@ class ResponseParser: limit = root.get('limit') timeout = float(timeout) if timeout is not None else None limit = int(limit) if limit is not None else None - return RepeatEntry(entry_id, root.text, timeout, limit) + return RepeatEntry(entry_id, self._work_dir, root.text, timeout, limit) elif root.tag == 'single': if len(root) != 0 or root.text is None or root.text.strip() == '': @@ -109,7 +112,7 @@ class ResponseParser: limit = root.get('limit') timeout = float(timeout) if timeout is not None else None limit = int(limit) if limit is not None else None - return SingleEntry(entry_id, root.text, timeout, limit) + return SingleEntry(entry_id, self._work_dir, root.text, timeout, limit) elif root.tag == 'reasoning': if len(root) != 0 or root.attrib or root.text is None or root.text.strip() == '': diff --git a/sia/web/api.py b/sia/web/api.py index e0523ce..41b7173 100644 --- a/sia/web/api.py +++ b/sia/web/api.py @@ -1,3 +1,4 @@ +from pathlib import Path from aiohttp import web import json import asyncio @@ -13,12 +14,14 @@ from ..working_memory import WorkingMemory class Api: def __init__( self, + work_dir: Path, app: web.Application, agent: WebAgent, io_buffer: WebIOBuffer, working_memory: WorkingMemory, auto_approver: AutoApprover ): + self._work_dir = work_dir self._app = app self._agent = agent self._working_memory = working_memory @@ -275,7 +278,7 @@ class Api: if not content: return web.Response(status=400, text="Missing content in request body") - entries = IterationParser.parse_iteration(content, self._io_buffer) + entries = IterationParser.parse_iteration(content, self._work_dir, self._io_buffer) for entry in entries: self._working_memory.add_entry(entry) diff --git a/tools/ITB/README.md b/tools/itb/README.md similarity index 100% rename from tools/ITB/README.md rename to tools/itb/README.md diff --git a/tools/itb/bin/itb_click b/tools/itb/bin/itb_click new file mode 100755 index 0000000..e69de29 diff --git a/tools/itb/bin/itb_forms b/tools/itb/bin/itb_forms new file mode 100755 index 0000000..e69de29 diff --git a/tools/itb/bin/itb_input b/tools/itb/bin/itb_input new file mode 100755 index 0000000..e69de29 diff --git a/tools/itb/bin/itb_links b/tools/itb/bin/itb_links new file mode 100755 index 0000000..e69de29 diff --git a/tools/itb/bin/itb_navigate b/tools/itb/bin/itb_navigate new file mode 100755 index 0000000..e69de29 diff --git a/tools/itb/bin/itb_refresh b/tools/itb/bin/itb_refresh new file mode 100755 index 0000000..e69de29 diff --git a/tools/itb/bin/itb_screenshot b/tools/itb/bin/itb_screenshot new file mode 100755 index 0000000..44d8bf6 --- /dev/null +++ b/tools/itb/bin/itb_screenshot @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +import sys +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.remote.webelement import WebElement +import xml.etree.ElementTree as ET +from typing import Dict, List, Set, Optional +from dataclasses import dataclass + +@dataclass +class DOMNode: + element: WebElement + children: List['DOMNode'] + own_text: str + total_text: str + tag: str + location: Dict[str, int] + size: Dict[str, int] + is_interactive: bool + attributes: Dict[str, str] + +class DOMTree: + def __init__(self, root_element: WebElement): + self.root = self._build_tree(root_element) + + def _get_element_own_text(self, element: WebElement) -> str: + script = """ + let element = arguments[0]; + let walker = document.createTreeWalker( + element, + NodeFilter.SHOW_TEXT, + { + acceptNode: function(node) { + return node.parentElement === element ? + NodeFilter.FILTER_ACCEPT : + NodeFilter.FILTER_REJECT; + } + } + ); + let text = ''; + let node; + while (node = walker.nextNode()) { + text += node.textContent; + } + return text.trim(); + """ + return element.parent.execute_script(script, element) + + def _is_interactive(self, element: WebElement) -> bool: + return ( + element.tag_name in {"button", "a", "input", "select", "textarea"} or + element.get_attribute("role") in {"button", "link", "textbox"} or + element.get_attribute("onclick") is not None or + element.get_attribute("contenteditable") == "true" + ) + + def _get_attributes(self, element: WebElement) -> Dict[str, str]: + attrs = {} + for attr in ['placeholder', 'aria-label', 'alt', 'title', 'role']: + if value := element.get_attribute(attr): + attrs[attr] = value + return attrs + + def _build_tree(self, element: WebElement) -> DOMNode: + children = [ + self._build_tree(child) + for child in element.find_elements(By.XPATH, "./*") + ] + + own_text = self._get_element_own_text(element) + total_text = own_text + " " + " ".join( + child.total_text for child in children + ) + total_text = " ".join(total_text.split()) + + return DOMNode( + element=element, + children=children, + own_text=own_text, + total_text=total_text, + tag=element.tag_name, + location=element.location, + size=element.size, + is_interactive=self._is_interactive(element), + attributes=self._get_attributes(element) + ) + +class ScreenshotGenerator: + IGNORED_TAGS = {'script', 'style', 'meta', 'link', 'noscript'} + + def __init__(self, debug_port: int): + options = webdriver.ChromeOptions() + options.add_experimental_option("debuggerAddress", f"127.0.0.1:{debug_port}") + self.driver = webdriver.Chrome(options=options) + + def get_viewport_metrics(self) -> Dict[str, int]: + return { + "scroll_x": self.driver.execute_script("return window.pageXOffset;"), + "scroll_y": self.driver.execute_script("return window.pageYOffset;"), + "width": self.driver.execute_script("return document.documentElement.clientWidth;"), + "height": self.driver.execute_script("return document.documentElement.clientHeight;"), + "max_scroll_y": self.driver.execute_script( + "return Math.max(document.documentElement.scrollHeight - window.innerHeight, 0);" + ) + } + + def should_process_node(self, node: DOMNode) -> bool: + # Skip invisible elements + if not node.element.is_displayed(): + return False + + # Skip script and style elements + if node.tag in self.IGNORED_TAGS: + return False + + return True + + def create_xml_element(self, node: DOMNode, parent_element: Optional[ET.Element] = None) -> Optional[ET.Element]: + attrs = { + "x": str(node.location["x"]), + "y": str(node.location["y"]), + "width": str(node.size["width"]), + "height": str(node.size["height"]) + } + + if node.is_interactive: + element = ET.Element("interactive", attrs) + child = ET.SubElement(element, node.tag) + for attr, value in node.attributes.items(): + child.set(attr, value) + if node.own_text: + child.text = node.own_text + return element + + element = ET.Element(node.tag, attrs) + if node.own_text: + element.text = node.own_text + return element + + def process_tree(self, node: DOMNode, parent_element: Optional[ET.Element] = None) -> List[ET.Element]: + if not self.should_process_node(node): + return [] + + current_element = self.create_xml_element(node, parent_element) + if not current_element: + return [] + + for child in node.children: + child_elements = self.process_tree(child, current_element) + for child_element in child_elements: + current_element.append(child_element) + + return [current_element] + + def generate_output(self) -> ET.Element: + metrics = self.get_viewport_metrics() + viewport = ET.Element("viewport", { + "scroll_x": str(metrics["scroll_x"]), + "scroll_y": str(metrics["scroll_y"]), + "width": str(metrics["width"]), + "height": str(metrics["height"]), + "max_scroll_y": str(metrics["max_scroll_y"]) + }) + + dom_tree = DOMTree(self.driver.find_element(By.TAG_NAME, "body")) + for element in self.process_tree(dom_tree.root): + viewport.append(element) + + return viewport + +def main(debug_port: int): + generator = ScreenshotGenerator(debug_port) + output = generator.generate_output() + print(ET.tostring(output, encoding="unicode", method="xml")) + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: itb_screenshot ") + sys.exit(1) + main(int(sys.argv[1])) diff --git a/tools/itb/bin/itb_scroll b/tools/itb/bin/itb_scroll new file mode 100755 index 0000000..e69de29 diff --git a/tools/itb/bin/itb_start b/tools/itb/bin/itb_start new file mode 100755 index 0000000..405560b --- /dev/null +++ b/tools/itb/bin/itb_start @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +import random +import subprocess +import sys + +def start(url): + debug_port = random.randint(9000, 9999) + chrome_cmd = [ + '/usr/bin/google-chrome', + '--headless=new', + '--no-sandbox', + '--remote-debugging-port=' + str(debug_port), + url + ] + + process = subprocess.Popen( + chrome_cmd, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + start_new_session=True + ) + + print({ + "pid": process.pid, + "debug_port": debug_port + }) + + +if __name__ == '__main__': + if len(sys.argv) != 2: + print("Usage: itb_start ") + sys.exit(1) + start(sys.argv[1]) \ No newline at end of file diff --git a/tools/itb/itb.egg-info/PKG-INFO b/tools/itb/itb.egg-info/PKG-INFO new file mode 100644 index 0000000..5b833a2 --- /dev/null +++ b/tools/itb/itb.egg-info/PKG-INFO @@ -0,0 +1,11 @@ +Metadata-Version: 2.1 +Name: itb +Version: 0.1.0 +Summary: UNKNOWN +Home-page: UNKNOWN +License: UNKNOWN +Platform: UNKNOWN +Provides-Extra: dev + +UNKNOWN + diff --git a/tools/itb/itb.egg-info/SOURCES.txt b/tools/itb/itb.egg-info/SOURCES.txt new file mode 100644 index 0000000..873bf32 --- /dev/null +++ b/tools/itb/itb.egg-info/SOURCES.txt @@ -0,0 +1,16 @@ +README.md +setup.py +bin/itb_click +bin/itb_forms +bin/itb_input +bin/itb_links +bin/itb_navigate +bin/itb_refresh +bin/itb_screenshot +bin/itb_scroll +bin/itb_start +itb.egg-info/PKG-INFO +itb.egg-info/SOURCES.txt +itb.egg-info/dependency_links.txt +itb.egg-info/requires.txt +itb.egg-info/top_level.txt \ No newline at end of file diff --git a/tools/itb/itb.egg-info/dependency_links.txt b/tools/itb/itb.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tools/itb/itb.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/tools/itb/itb.egg-info/requires.txt b/tools/itb/itb.egg-info/requires.txt new file mode 100644 index 0000000..8058206 --- /dev/null +++ b/tools/itb/itb.egg-info/requires.txt @@ -0,0 +1,10 @@ +beautifulsoup4>=4.9.0 +click>=8.0.0 +selenium>=4.0.0 +webdriver-manager>=3.8.0 + +[dev] +black>=22.0.0 +flake8>=4.0.0 +pytest-cov>=4.0.0 +pytest>=7.0.0 diff --git a/tools/itb/itb.egg-info/top_level.txt b/tools/itb/itb.egg-info/top_level.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tools/itb/itb.egg-info/top_level.txt @@ -0,0 +1 @@ + diff --git a/tools/itb/requirements.txt b/tools/itb/requirements.txt new file mode 100644 index 0000000..140cd3c --- /dev/null +++ b/tools/itb/requirements.txt @@ -0,0 +1,8 @@ +selenium>=4.0.0 +webdriver-manager>=3.8.0 +click>=8.0.0 +beautifulsoup4>=4.9.0 +pytest>=7.0.0 +pytest-cov>=4.0.0 +black>=22.0.0 +flake8>=4.0.0 diff --git a/tools/itb/result.social.html b/tools/itb/result.social.html new file mode 100644 index 0000000..e1bdf4b --- /dev/null +++ b/tools/itb/result.social.html @@ -0,0 +1,38 @@ + + + +
+
Alice Johnson
+
2 hours ago
+ +
+ Just finished my morning hike! The sunrise was absolutely breathtaking today. 🌄 #MorningHike #Nature #Wellness +
+ + Sunrise view from mountain top + +
+ + + + + + + + + +
+ +
+
Bob Smith
+
+ Beautiful view! Which trail is this? +
+
+
+
diff --git a/tools/itb/result.test.html b/tools/itb/result.test.html new file mode 100644 index 0000000..42381b4 --- /dev/null +++ b/tools/itb/result.test.html @@ -0,0 +1,32 @@ + +
+ +
+ +
+ Scrollable Section + Lorem ipsum dolor sit amet, consectetur adipiscing elit... [first paragraph] +
+ + + + + + + JavaScript Link + + + + + + + + Cell with mixed formatting + Cell with list: +- Item 1 +- Item 2 +
+
diff --git a/tools/itb/setup.py b/tools/itb/setup.py new file mode 100644 index 0000000..96b0496 --- /dev/null +++ b/tools/itb/setup.py @@ -0,0 +1,32 @@ +from setuptools import setup, find_packages + +setup( + name="itb", + version="0.1.0", + packages=find_packages(), + scripts=[ + 'bin/itb_start', + 'bin/itb_screenshot', + 'bin/itb_scroll', + 'bin/itb_click', + 'bin/itb_input', + 'bin/itb_links', + 'bin/itb_forms', + 'bin/itb_navigate', + 'bin/itb_refresh' + ], + install_requires=[ + 'selenium>=4.0.0', + 'webdriver-manager>=3.8.0', + 'click>=8.0.0', + 'beautifulsoup4>=4.9.0' + ], + extras_require={ + 'dev': [ + 'pytest>=7.0.0', + 'pytest-cov>=4.0.0', + 'black>=22.0.0', + 'flake8>=4.0.0' + ] + } +) diff --git a/tools/itb/social.html b/tools/itb/social.html new file mode 100644 index 0000000..6c1be48 --- /dev/null +++ b/tools/itb/social.html @@ -0,0 +1,178 @@ + + + + SocialConnect + + + + + +
+ +
+
+
+ +
+
+ Just finished my morning hike! The sunrise was absolutely breathtaking today. 🌄 + #MorningHike #Nature #Wellness +
+ Sunrise view from mountain top +
+ + + +
+
+
+
+
+
Bob Smith
+ Beautiful view! Which trail is this? +
+
+
+
+ + + +
+
+
+ +
+
+ Just launched my new portfolio website! 🚀 Excited to share my latest projects with everyone. + Check it out and let me know what you think! #WebDevelopment #Portfolio #Coding +
+
+ + + +
+
+ + + + +
+ + diff --git a/tools/itb/test.html b/tools/itb/test.html new file mode 100644 index 0000000..c2a75ff --- /dev/null +++ b/tools/itb/test.html @@ -0,0 +1,90 @@ + + + + + + + + + + +
+

Scrollable Section

+

+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam a bibendum purus, id accumsan lorem. Ut quis diam semper, rutrum justo in, ornare urna. Proin vel consectetur odio. Donec quis odio ut erat lobortis aliquam. Vestibulum vitae tellus interdum, hendrerit odio id, varius urna. Sed nec est tempor, placerat lectus sit amet, aliquam mauris. Duis elementum risus vitae nunc mollis ullamcorper. Donec sodales ac neque at aliquam. Duis eu risus ex. Fusce neque erat, egestas nec placerat in, dapibus quis magna. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Ut faucibus nunc tellus, vel consequat dolor suscipit vel. Sed sapien erat, rutrum non dignissim nec, egestas at augue. Nunc interdum lacinia urna a euismod. Integer ullamcorper sit amet sapien non auctor. +

+

+Nam vitae bibendum nulla, ut tempus enim. Aenean eu iaculis metus, vel rhoncus justo. Sed vel interdum sem. Praesent congue erat sit amet ultrices molestie. Pellentesque pellentesque odio a ullamcorper facilisis. Sed at viverra magna. Aliquam posuere dolor tortor, non aliquam orci bibendum eu. Sed efficitur feugiat congue. Vivamus elementum in metus lacinia gravida. +

+

+Fusce aliquam, erat ac consequat eleifend, massa neque posuere urna, vel sagittis tortor neque hendrerit augue. Nulla mattis felis non efficitur hendrerit. Pellentesque viverra elementum enim ornare pellentesque. Donec mollis eget leo non congue. Aliquam vel iaculis odio. Mauris eget neque suscipit, tempus mi quis, porta quam. Integer non tempor leo. Pellentesque magna nisl, fermentum sit amet orci ut, posuere dictum erat. Vivamus a lacinia lacus, eleifend ultrices velit. Etiam est arcu, dictum at sapien efficitur, fringilla tincidunt sem. In nec lacus quis diam placerat sollicitudin. Vivamus scelerisque commodo velit, id lacinia orci volutpat quis. Nulla facilisi. Nam nec quam quis lorem vulputate auctor quis a dui. +

+

+Curabitur a erat nisi. Fusce lacinia, enim quis mollis placerat, eros nulla blandit turpis, id feugiat purus enim ut diam. Etiam et tincidunt turpis, in scelerisque lectus. Nunc vulputate at velit sit amet tincidunt. Cras maximus ultrices enim, sit amet placerat nisi tristique eget. Sed ac neque sed quam laoreet pellentesque nec vel lorem. Nunc sollicitudin arcu ac quam tempor bibendum. Phasellus sollicitudin tortor at porta lobortis. Aenean venenatis ultrices neque ut scelerisque. Nullam aliquam fringilla varius. Etiam eget nunc leo. Proin ornare tempus libero eget molestie. Praesent id ex sit amet ante semper dictum. Mauris ac libero at ipsum dapibus pretium quis at sapien. Praesent ac luctus lacus. Praesent id varius nisl, et luctus ex. +

+ +
+Curabitur a erat nisi. Fusce lacinia, enim quis mollis placerat, eros nulla blandit turpis, id feugiat purus enim ut diam. Etiam et tincidunt turpis, in scelerisque lectus. Nunc vulputate at velit sit amet tincidunt. Cras maximus ultrices enim, sit amet placerat nisi tristique eget. Sed ac neque sed quam laoreet pellentesque nec vel lorem. Nunc sollicitudin arcu ac quam tempor bibendum. Phasellus sollicitudin tortor at porta lobortis. Aenean venenatis ultrices neque ut scelerisque. Nullam aliquam fringilla varius. Etiam eget nunc leo. Proin ornare tempus libero eget molestie. Praesent id ex sit amet ante semper dictum. Mauris ac libero at ipsum dapibus pretium quis at sapien. Praesent ac luctus lacus. Praesent id varius nisl, et luctus ex. +
+
+ + +
+ + JavaScript Link +
ARIA Button
+
+ + + + + + + + + + +
Cell with mixed formattingCell with list: +
    +
  • Item 1
  • +
  • Item 2
  • +
+
+ + diff --git a/web/src/components/App.jsx b/web/src/components/App.jsx index 3572f73..ed4fb82 100644 --- a/web/src/components/App.jsx +++ b/web/src/components/App.jsx @@ -251,6 +251,8 @@ const App = () => { if (activeTab === Tabs.MEMORY) return; const state = llms[activeLlm]; switch (state) { + case null: + break; case LlmState.NO_OUTPUT: setActiveTab(Tabs.CONTEXT); setShowDiff(false);