├── .DS_Store ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SETUP_GUIDE.MD ├── cookbook └── python │ ├── README.md │ ├── claude-mcp │ ├── README.md │ ├── image.png │ └── linkedin.py │ ├── knowledge-graph │ ├── .gitignore │ ├── README.md │ ├── app.py │ ├── helpers.py │ ├── lib │ │ ├── bindings │ │ │ └── utils.js │ │ ├── tom-select │ │ │ ├── tom-select.complete.min.js │ │ │ └── tom-select.css │ │ └── vis-9.1.2 │ │ │ ├── vis-network.css │ │ │ └── vis-network.min.js │ └── requirements.txt │ └── streamlit-chatbot │ ├── .gitignore │ ├── .streamlit │ └── secrets_template.toml │ ├── README.md │ ├── app.py │ ├── helpers.py │ └── requirements.txt ├── desktop ├── .editorconfig ├── .erb │ ├── configs │ │ ├── .eslintrc │ │ ├── webpack.config.base.ts │ │ ├── webpack.config.eslint.ts │ │ ├── webpack.config.main.prod.ts │ │ ├── webpack.config.preload.dev.ts │ │ ├── webpack.config.renderer.dev.dll.ts │ │ ├── webpack.config.renderer.dev.ts │ │ ├── webpack.config.renderer.prod.ts │ │ └── webpack.paths.ts │ ├── img │ │ ├── erb-banner.svg │ │ ├── erb-logo.png │ │ └── palette-sponsor-banner.svg │ ├── mocks │ │ └── fileMock.js │ ├── release │ │ └── app │ │ │ └── dist │ │ │ └── renderer │ │ │ └── index.html │ └── scripts │ │ ├── .eslintrc │ │ ├── check-build-exists.ts │ │ ├── check-native-dep.js │ │ ├── check-node-env.js │ │ ├── check-port-in-use.js │ │ ├── clean.js │ │ ├── delete-source-maps.js │ │ ├── electron-rebuild.js │ │ ├── link-modules.ts │ │ └── notarize.js ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .github │ ├── ISSUE_TEMPLATE │ │ ├── 1-Bug_report.md │ │ ├── 2-Question.md │ │ └── 3-Feature_request.md │ ├── config.yml │ ├── stale.yml │ └── workflows │ │ ├── codeql-analysis.yml │ │ ├── publish.yml │ │ └── test.yml ├── .gitignore ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── settings.json ├── ADD_PLATFORMS.md ├── HELPER_FUNCTIONS.md ├── README.md ├── assets │ ├── Surfer.png │ ├── SurferApp.png │ ├── SurferDemo.mp4 │ ├── SurferDiagram.png │ ├── assets.d.ts │ ├── current_db.py │ ├── entitlements.mac.plist │ ├── error.png │ ├── icon.icns │ ├── icon.png │ ├── icon.svg │ ├── imessage_mac.py │ ├── imessage_windows.py │ ├── password-prompt.html │ ├── requirements.txt │ ├── search_vector_db.py │ ├── vectorize.py │ └── windows_requirements.txt ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── release │ └── app │ │ ├── package-lock.json │ │ ├── package.json │ │ └── pnpm-lock.yaml ├── src │ ├── main │ │ ├── helpers │ │ │ ├── imessage.ts │ │ │ ├── menu.ts │ │ │ ├── network.ts │ │ │ ├── platforms.ts │ │ │ ├── python.ts │ │ │ └── util.ts │ │ ├── main.ts │ │ ├── platforms │ │ │ ├── Apple │ │ │ │ ├── imessage.js │ │ │ │ ├── imessage.json │ │ │ │ └── imessage.md │ │ │ ├── Google │ │ │ │ ├── gmail.js │ │ │ │ ├── gmail.json │ │ │ │ ├── gmail.md │ │ │ │ ├── youtube.js │ │ │ │ ├── youtube.json │ │ │ │ └── youtube.md │ │ │ ├── Microsoft │ │ │ │ ├── connections.js │ │ │ │ ├── connections.json │ │ │ │ ├── connections.md │ │ │ │ └── connections_with_profile.txt │ │ │ ├── Notion │ │ │ │ ├── notion.js │ │ │ │ ├── notion.json │ │ │ │ └── notion.md │ │ │ ├── OpenAI │ │ │ │ ├── chatgpt.js │ │ │ │ ├── chatgpt.json │ │ │ │ └── chatgpt.md │ │ │ └── X Corp │ │ │ │ ├── bookmarks.js │ │ │ │ ├── bookmarks.json │ │ │ │ ├── bookmarks.md │ │ │ │ ├── twitter.js │ │ │ │ ├── twitter.json │ │ │ │ └── twitter.md │ │ ├── preload.ts │ │ ├── preloadElectron.js │ │ ├── preloadFunctions.js │ │ └── preloadWebview.js │ └── renderer │ │ ├── App.tsx │ │ ├── Surfer.tsx │ │ ├── components │ │ ├── CodeBlock.jsx │ │ ├── Header.jsx │ │ ├── Layout.tsx │ │ ├── PlatformDashboard.jsx │ │ ├── RunDetails.jsx │ │ ├── Webview.tsx │ │ └── ui │ │ │ ├── accordion.tsx │ │ │ ├── alert-dialog.tsx │ │ │ ├── alert.tsx │ │ │ ├── aspect-ratio.tsx │ │ │ ├── avatar.tsx │ │ │ ├── badge.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── button.tsx │ │ │ ├── calendar.tsx │ │ │ ├── card.tsx │ │ │ ├── carousel.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── cn.js │ │ │ ├── collapsible.tsx │ │ │ ├── command.tsx │ │ │ ├── context-menu.tsx │ │ │ ├── dialog.tsx │ │ │ ├── drawer.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── form.tsx │ │ │ ├── hover-card.tsx │ │ │ ├── input-otp.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── menubar.tsx │ │ │ ├── navigation-menu.tsx │ │ │ ├── pagination.tsx │ │ │ ├── popover.tsx │ │ │ ├── progress.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── resizable.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── slider.tsx │ │ │ ├── sonner.tsx │ │ │ ├── switch.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── textarea.tsx │ │ │ ├── theme-provider.tsx │ │ │ ├── toast.tsx │ │ │ ├── toaster.tsx │ │ │ ├── toggle-group.tsx │ │ │ ├── toggle.tsx │ │ │ ├── toggleColor.tsx │ │ │ ├── tooltip.tsx │ │ │ └── use-toast.ts │ │ ├── helpers.ts │ │ ├── index.ejs │ │ ├── index.tsx │ │ ├── pages │ │ ├── Home.jsx │ │ └── Platform.jsx │ │ ├── preload.d.ts │ │ ├── state │ │ ├── actions.ts │ │ ├── initialStates.ts │ │ ├── reducers.ts │ │ └── store.js │ │ ├── styles │ │ └── globals.css │ │ └── types │ │ └── interfaces.ts ├── tailwind.config.js └── tsconfig.json ├── docs ├── .eslintrc.json ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── jsconfig.json ├── mdx-components.jsx ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.js ├── prettier.config.js ├── src │ ├── app │ │ ├── about │ │ │ └── page.mdx │ │ ├── claude │ │ │ └── page.mdx │ │ ├── community-projects │ │ │ └── page.mdx │ │ ├── contributing │ │ │ └── page.mdx │ │ ├── cookbook │ │ │ └── python │ │ │ │ └── page.mdx │ │ ├── desktop │ │ │ ├── installation │ │ │ │ └── page.mdx │ │ │ ├── page.mdx │ │ │ └── platforms │ │ │ │ ├── chatgpt │ │ │ │ └── page.mdx │ │ │ │ ├── gmail │ │ │ │ └── page.mdx │ │ │ │ ├── imessage │ │ │ │ └── page.mdx │ │ │ │ ├── linkedin │ │ │ │ └── page.mdx │ │ │ │ ├── notion │ │ │ │ └── page.mdx │ │ │ │ ├── page.mdx │ │ │ │ └── twitter │ │ │ │ └── page.mdx │ │ ├── faq │ │ │ └── page.mdx │ │ ├── favicon.ico │ │ ├── layout.jsx │ │ ├── not-found.jsx │ │ ├── page.mdx │ │ ├── providers.jsx │ │ └── sdk │ │ │ └── python │ │ │ └── page.mdx │ ├── components │ │ ├── Button.jsx │ │ ├── Code.jsx │ │ ├── Desktop.jsx │ │ ├── DownloadButtons.jsx │ │ ├── EditPage.jsx │ │ ├── Feedback.jsx │ │ ├── Footer.jsx │ │ ├── GridPattern.jsx │ │ ├── Header.jsx │ │ ├── Heading.jsx │ │ ├── HeroPattern.jsx │ │ ├── Image.jsx │ │ ├── Layout.jsx │ │ ├── Libraries.jsx │ │ ├── Logo.jsx │ │ ├── MobileNavigation.jsx │ │ ├── Navigation.jsx │ │ ├── Prose.jsx │ │ ├── Resources.jsx │ │ ├── Search.jsx │ │ ├── SectionProvider.jsx │ │ ├── Tag.jsx │ │ ├── ThemeToggle.jsx │ │ ├── icons │ │ │ ├── BellIcon.jsx │ │ │ ├── BoltIcon.jsx │ │ │ ├── BookIcon.jsx │ │ │ ├── CalendarIcon.jsx │ │ │ ├── CartIcon.jsx │ │ │ ├── ChatBubbleIcon.jsx │ │ │ ├── CheckIcon.jsx │ │ │ ├── ChevronRightLeftIcon.jsx │ │ │ ├── ClipboardIcon.jsx │ │ │ ├── CogIcon.jsx │ │ │ ├── CopyIcon.jsx │ │ │ ├── DocumentIcon.jsx │ │ │ ├── EnvelopeIcon.jsx │ │ │ ├── FaceSmileIcon.jsx │ │ │ ├── FolderIcon.jsx │ │ │ ├── LinkIcon.jsx │ │ │ ├── ListIcon.jsx │ │ │ ├── MagnifyingGlassIcon.jsx │ │ │ ├── MapPinIcon.jsx │ │ │ ├── PackageIcon.jsx │ │ │ ├── PaperAirplaneIcon.jsx │ │ │ ├── PaperClipIcon.jsx │ │ │ ├── ShapesIcon.jsx │ │ │ ├── ShirtIcon.jsx │ │ │ ├── SquaresPlusIcon.jsx │ │ │ ├── TagIcon.jsx │ │ │ ├── UserIcon.jsx │ │ │ └── UsersIcon.jsx │ │ └── mdx.jsx │ ├── helpers.js │ ├── images │ │ ├── claude-mcp.png │ │ ├── claude_finished.png │ │ ├── cover_image.png │ │ ├── knowledge-graph.png │ │ ├── logos │ │ │ ├── go.svg │ │ │ ├── node.svg │ │ │ ├── php.svg │ │ │ ├── python.svg │ │ │ └── ruby.svg │ │ └── streamlit-chatbot.png │ ├── lib │ │ └── remToPx.js │ ├── mdx │ │ ├── recma.mjs │ │ ├── rehype.mjs │ │ ├── remark.mjs │ │ └── search.mjs │ └── styles │ │ └── tailwind.css ├── tailwind.config.js └── typography.js ├── landing ├── .firebase │ └── hosting.ZGlzdA.cache ├── .firebaserc ├── .gitignore ├── README.md ├── eslint.config.js ├── firebase.json ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── src │ ├── App.jsx │ ├── Business.jsx │ ├── assets │ │ ├── cover_image.png │ │ ├── favicon.ico │ │ └── logo.svg │ ├── index.css │ └── main.jsx ├── tailwind.config.js └── vite.config.js ├── sdk └── python │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── pyproject.toml │ ├── requirements.txt │ ├── setup.py │ └── surfer_protocol │ ├── __init__.py │ └── client.py └── surfer-mcp ├── .python-version ├── README.md ├── pyproject.toml ├── src └── surfer_mcp │ ├── __init__.py │ ├── __pycache__ │ ├── __init__.cpython-313.pyc │ └── server.cpython-313.pyc │ └── server.py └── uv.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/.DS_Store -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to the Surfer Protocol! We welcome contributions from the community to help improve and expand this project. 4 | 5 | ## Types of Contributions 6 | 7 | - Adding new platforms in the desktop app and documentation (see [Guide to Adding New Platforms](ADD_PLATFORMS.md)) 8 | - Building applications off of Surfer Protocol (see [Python SDK](sdk/python/README.md) and [Cookbook](cookbook/README.md)) 9 | - Helping with the documentation website (see [Docs](docs/README.md)) 10 | - General Bug Fixes 11 | 12 | ## Setup 13 | 14 | Please see our [Setup Guide](SETUP_GUIDE.md) for more information on how to set up the development environment. 15 | 16 | ## Contribution Guidelines 17 | 18 | 1. **Reporting Bugs**: If you encounter any bugs or issues, please open a new issue in the [GitHub repository](https://github.com/Surfer-Org/Protocol/issues/new?labels=bug&template=bug-report---.md). Provide a clear and detailed description of the problem, along with any relevant steps to reproduce the issue. 19 | 20 | 2. **Suggesting Features**: If you have an idea for a new feature or improvement, please open a new issue in the [GitHub repository](https://github.com/Surfer-Org/Protocol/issues/new?labels=enhancement&template=feature-request---.md). Describe the feature in detail, explain its benefits, and provide any additional context that might be helpful. 21 | 22 | 3. **Submitting Pull Requests**: If you would like to contribute code changes to the project, follow these steps: 23 | - Fork the repository 24 | - Create a new branch for your feature or bug fix 25 | - Make your changes 26 | - Test your changes thoroughly 27 | - Submit a pull request to the main repository, providing a clear description of your changes and the problem they solve 28 | 29 | We appreciate all contributions, whether they are bug reports, feature suggestions, or code changes. By working together, we can make Surfer a better and more useful tool for everyone. 30 | 31 | ## Contact 32 | 33 | If you have any questions or need further assistance, please feel free to reach out to the project maintainers: 34 | 35 | [Surfer Discord Server](https://discord.gg/Tjg7pjcFNP) - [@SahilLalani0](https://x.com/SahilLalani0) - [@JackBlair87](https://x.com/JackBlair87) - [@T0M_3D](https://x.com/T0M_3D) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Surfer Protocol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SETUP_GUIDE.MD: -------------------------------------------------------------------------------- 1 | ## Surfer Protocol Setup Guide 2 | 3 | Before you can start contributing, please follow these steps to set up the development environment: 4 | 5 | 1. Fork the repository: 6 | 7 | 2. Clone the forked repository: 8 | ``` 9 | git clone https://github.com/YOUR_GITHUB_USERNAME/Protocol.git 10 | ``` 11 | 12 | ## Desktop App 13 | 14 | 1. Install the required dependencies: 15 | ``` 16 | cd Surfer-Protocol/desktop 17 | npm install 18 | ``` 19 | 20 | 2. Start the development server: 21 | ``` 22 | npm start 23 | ``` 24 | 25 | ## Python SDK 26 | 27 | 1. Navigate to the Python SDK directory: 28 | ```bash 29 | cd Surfer-Protocol/sdk/python 30 | ``` 31 | 32 | 2. Create and activate a virtual environment: 33 | ```bash 34 | python -m venv venv 35 | source venv/bin/activate # On Windows, use: venv\Scripts\activate 36 | ``` 37 | 38 | 3. Install development dependencies: 39 | ```bash 40 | pip install -r requirements.txt 41 | pip install -e . 42 | ``` 43 | 44 | 4. Test the package: 45 | ```bash 46 | python -m surfer_protocol 47 | ``` 48 | 49 | 5. You can now import and use the package in your Python code: 50 | ```python 51 | from surfer_protocol import SurferClient 52 | 53 | client = SurferClient() 54 | # Make sure the desktop app is running before testing 55 | data = client.get("bookmarks-001") 56 | ``` 57 | 58 | ## Docs 59 | 60 | 1. Install the required dependencies: 61 | ``` 62 | cd Surfer-Protocol/docs 63 | npm install 64 | ``` 65 | 66 | 2. Start the development server: 67 | ``` 68 | npm run dev 69 | ``` 70 | 71 | ## Landing Page 72 | 73 | 1. Install the required dependencies: 74 | ``` 75 | cd Surfer-Protocol/landing 76 | npm install 77 | ``` 78 | 79 | 2. Start the development server: 80 | ``` 81 | npm run dev 82 | ``` 83 | 84 | This will run the application in development mode, allowing you to test your changes and see the results in real-time. 85 | -------------------------------------------------------------------------------- /cookbook/python/README.md: -------------------------------------------------------------------------------- 1 | # Surfer Protocol Python Cookbook 2 | 3 | This cookbook contains examples of how to use the Surfer Protocol Python SDK to build applications. 4 | 5 | ## Current Recipes 6 | 7 | - [Streamlit Chatbot](streamlit-chatbot/app.py) 8 | - [Knowledge Graph Maker](knowledge-graph/app.py) 9 | 10 | ## How to Contribute 11 | 12 | We welcome contributions to the cookbook! Please see our [CONTRIBUTING.md](../../CONTRIBUTING.md) for more information on how to contribute to the Surfer Protocol. 13 | -------------------------------------------------------------------------------- /cookbook/python/claude-mcp/README.md: -------------------------------------------------------------------------------- 1 | # Claude MCP 2 | 3 | 1. Install the Claude Desktop App 4 | 5 | 3. Install uv. This is recommended for managing model context protocol servers. More details can be found here. 6 | 7 | 4. If not already there, create a `claude_desktop_config.json` file in the app data folder of the Claude desktop app. 8 | 9 | Mac Path: `~/Library/Application Support/Claude/claude_desktop_config.json` 10 | 11 | Windows Path: `C:/Users/[your-username]/AppData/Roaming/Claude/claude_desktop_config.json` 12 | 13 | 5. Add the following content to the file and save it: 14 | 15 | 16 | ```json 17 | { 18 | "mcpServers": { 19 | "surfer-mcp": { 20 | "command": "uvx", 21 | "args": [ 22 | "surfer-mcp" 23 | ] 24 | } 25 | } 26 | } 27 | ``` 28 | 29 | 6. Close the Claude Desktop app and run it again. If you see a plug and tool icon in the chat window, you should be good to go! 30 | -------------------------------------------------------------------------------- /cookbook/python/claude-mcp/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/cookbook/python/claude-mcp/image.png -------------------------------------------------------------------------------- /cookbook/python/claude-mcp/linkedin.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | from collections import defaultdict 4 | 5 | # Load the JSON data 6 | with open('your-file-here.json', 'r') as file: 7 | data = json.load(file) 8 | 9 | # Initialize a dictionary to store the counts 10 | connections_by_year_month = defaultdict(lambda: defaultdict(int)) 11 | 12 | # Iterate through the connections 13 | for connection in data['content']: 14 | created_at_timestamp = connection.get('created_at') 15 | if created_at_timestamp: 16 | # Convert timestamp to datetime 17 | created_date = datetime.fromtimestamp(created_at_timestamp / 1000) # Convert ms to seconds 18 | year = created_date.year 19 | month = created_date.strftime('%B') # Get month name 20 | connections_by_year_month[year][month] += 1 21 | 22 | # Print the results 23 | for year, months in sorted(connections_by_year_month.items()): 24 | print(f"\nYear: {year}") 25 | for month, count in sorted(months.items()): 26 | print(f" {month}: {count} connections") -------------------------------------------------------------------------------- /cookbook/python/knowledge-graph/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .env 3 | *.json 4 | *.png 5 | *.html -------------------------------------------------------------------------------- /cookbook/python/knowledge-graph/requirements.txt: -------------------------------------------------------------------------------- 1 | networkx 2 | matplotlib 3 | pyvis 4 | surfer-protocol -------------------------------------------------------------------------------- /cookbook/python/streamlit-chatbot/.gitignore: -------------------------------------------------------------------------------- 1 | .streamlit/secrets.toml 2 | __pycache__/ -------------------------------------------------------------------------------- /cookbook/python/streamlit-chatbot/.streamlit/secrets_template.toml: -------------------------------------------------------------------------------- 1 | **Rename this file to `secrets.toml` and add your API keys** 2 | 3 | WEAVIATE_URL = "add-your-weaviate-url-here" 4 | WEAVIATE_API_KEY = "add-your-weaviate-api-key-here" 5 | OPENAI_API_KEY = "add-your-openai-api-key-here" -------------------------------------------------------------------------------- /cookbook/python/streamlit-chatbot/helpers.py: -------------------------------------------------------------------------------- 1 | def chunk_object(obj, chunk_size=1000, overlap=200): 2 | """ 3 | Chunks the text property of an object into smaller pieces with overlap. 4 | 5 | Args: 6 | obj (dict): The input object containing properties including 'text' 7 | chunk_size (int): Maximum size of each chunk 8 | overlap (int): Number of characters to overlap between chunks 9 | 10 | Returns: 11 | list: List of objects with the same properties but chunked text 12 | """ 13 | # If there's no text property or text is shorter than chunk_size, return original 14 | if 'text' not in obj or len(obj['text']) <= chunk_size: 15 | return [obj] 16 | 17 | text = obj['text'] 18 | chunks = [] 19 | start = 0 20 | 21 | while start < len(text): 22 | # Get chunk of text 23 | end = start + chunk_size 24 | 25 | # If this isn't the first chunk, include the overlap from the previous chunk 26 | if start > 0: 27 | start = start - overlap 28 | 29 | # Get the chunk 30 | chunk = text[start:end] 31 | 32 | # Create new object with same properties but chunked text 33 | new_obj = obj.copy() 34 | new_obj['text'] = chunk 35 | 36 | 37 | chunks.append(new_obj) 38 | 39 | # Move start position for next chunk 40 | start = end 41 | 42 | return chunks -------------------------------------------------------------------------------- /cookbook/python/streamlit-chatbot/requirements.txt: -------------------------------------------------------------------------------- 1 | streamlit 2 | weaviate-client 3 | openai 4 | surfer-protocol -------------------------------------------------------------------------------- /desktop/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /desktop/.erb/configs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /desktop/.erb/configs/webpack.config.eslint.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | 3 | module.exports = require('./webpack.config.renderer.dev').default; 4 | -------------------------------------------------------------------------------- /desktop/.erb/configs/webpack.config.renderer.dev.dll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import { merge } from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import webpackPaths from './webpack.paths'; 10 | import { dependencies } from '../../package.json'; 11 | import checkNodeEnv from '../scripts/check-node-env'; 12 | 13 | checkNodeEnv('development'); 14 | 15 | const dist = webpackPaths.dllPath; 16 | 17 | const configuration: webpack.Configuration = { 18 | context: webpackPaths.rootPath, 19 | 20 | devtool: 'eval', 21 | 22 | mode: 'development', 23 | 24 | target: 'electron-renderer', 25 | 26 | externals: ['fsevents', 'crypto-browserify'], 27 | 28 | /** 29 | * Use `module` from `webpack.config.renderer.dev.js` 30 | */ 31 | module: require('./webpack.config.renderer.dev').default.module, 32 | 33 | entry: { 34 | renderer: Object.keys(dependencies || {}).filter((it) => it !== 'langchain').filter((it) => it !== '@langchain/community'), 35 | }, 36 | 37 | output: { 38 | path: dist, 39 | filename: '[name].dev.dll.js', 40 | library: { 41 | name: 'renderer', 42 | type: 'var', 43 | }, 44 | }, 45 | 46 | plugins: [ 47 | new webpack.DllPlugin({ 48 | path: path.join(dist, '[name].json'), 49 | name: '[name]', 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'development', 63 | }), 64 | 65 | new webpack.LoaderOptionsPlugin({ 66 | debug: true, 67 | options: { 68 | context: webpackPaths.srcPath, 69 | output: { 70 | path: webpackPaths.dllPath, 71 | }, 72 | }, 73 | }), 74 | ], 75 | }; 76 | 77 | export default merge(baseConfig, configuration); 78 | -------------------------------------------------------------------------------- /desktop/.erb/configs/webpack.paths.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const rootPath = path.join(__dirname, '../..'); 4 | 5 | const dllPath = path.join(__dirname, '../dll'); 6 | 7 | const srcPath = path.join(rootPath, 'src'); 8 | const srcMainPath = path.join(srcPath, 'main'); 9 | const srcRendererPath = path.join(srcPath, 'renderer'); 10 | 11 | const releasePath = path.join(rootPath, 'release'); 12 | const appPath = path.join(releasePath, 'app'); 13 | const appPackagePath = path.join(appPath, 'package.json'); 14 | const appNodeModulesPath = path.join(appPath, 'node_modules'); 15 | const srcNodeModulesPath = path.join(srcPath, 'node_modules'); 16 | 17 | const distPath = path.join(appPath, 'dist'); 18 | const distMainPath = path.join(distPath, 'main'); 19 | const distRendererPath = path.join(distPath, 'renderer'); 20 | 21 | const buildPath = path.join(releasePath, 'build'); 22 | 23 | export default { 24 | rootPath, 25 | dllPath, 26 | srcPath, 27 | srcMainPath, 28 | srcRendererPath, 29 | releasePath, 30 | appPath, 31 | appPackagePath, 32 | appNodeModulesPath, 33 | srcNodeModulesPath, 34 | distPath, 35 | distMainPath, 36 | distRendererPath, 37 | buildPath, 38 | }; 39 | -------------------------------------------------------------------------------- /desktop/.erb/img/erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/.erb/img/erb-logo.png -------------------------------------------------------------------------------- /desktop/.erb/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /desktop/.erb/release/app/dist/renderer/index.html: -------------------------------------------------------------------------------- 1 | Surfer
2 | -------------------------------------------------------------------------------- /desktop/.erb/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off", 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /desktop/.erb/scripts/check-build-exists.ts: -------------------------------------------------------------------------------- 1 | // Check if the renderer and main bundles are built 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import fs from 'fs'; 5 | import webpackPaths from '../configs/webpack.paths'; 6 | 7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.ts'); 8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js'); 9 | 10 | if (!fs.existsSync(mainPath)) { 11 | throw new Error( 12 | chalk.whiteBright.bgRed.bold( 13 | 'The main process is not built yet. Build it by running "npm run build:main"', 14 | ), 15 | ); 16 | } 17 | 18 | if (!fs.existsSync(rendererPath)) { 19 | throw new Error( 20 | chalk.whiteBright.bgRed.bold( 21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"', 22 | ), 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /desktop/.erb/scripts/check-native-dep.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import chalk from 'chalk'; 3 | import { execSync } from 'child_process'; 4 | import { dependencies } from '../../package.json'; 5 | 6 | if (dependencies) { 7 | const dependenciesKeys = Object.keys(dependencies); 8 | const nativeDeps = fs 9 | .readdirSync('node_modules') 10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 11 | if (nativeDeps.length === 0) { 12 | process.exit(0); 13 | } 14 | try { 15 | // Find the reason for why the dependency is installed. If it is installed 16 | // because of a devDependency then that is okay. Warn when it is installed 17 | // because of a dependency 18 | const { dependencies: dependenciesObject } = JSON.parse( 19 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString(), 20 | ); 21 | const rootDependencies = Object.keys(dependenciesObject); 22 | const filteredRootDependencies = rootDependencies.filter((rootDependency) => 23 | dependenciesKeys.includes(rootDependency), 24 | ); 25 | if (filteredRootDependencies.length > 0) { 26 | const plural = filteredRootDependencies.length > 1; 27 | console.log(` 28 | ${chalk.whiteBright.bgYellow.bold( 29 | 'Webpack does not work with native dependencies.', 30 | )} 31 | ${chalk.bold(filteredRootDependencies.join(', '))} ${ 32 | plural ? 'are native dependencies' : 'is a native dependency' 33 | } and should be installed inside of the "./release/app" folder. 34 | First, uninstall the packages from "./package.json": 35 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} 36 | ${chalk.bold( 37 | 'Then, instead of installing the package to the root "./package.json":', 38 | )} 39 | ${chalk.whiteBright.bgRed.bold('npm install your-package')} 40 | ${chalk.bold('Install the package to "./release/app/package.json"')} 41 | ${chalk.whiteBright.bgGreen.bold( 42 | 'cd ./release/app && npm install your-package', 43 | )} 44 | Read more about native dependencies at: 45 | ${chalk.bold( 46 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure', 47 | )} 48 | `); 49 | process.exit(1); 50 | } 51 | } catch (e) { 52 | console.log('Native dependencies could not be checked'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /desktop/.erb/scripts/check-node-env.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default function checkNodeEnv(expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set'); 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`, 12 | ), 13 | ); 14 | process.exit(2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /desktop/.erb/scripts/check-port-in-use.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import detectPort from 'detect-port'; 3 | 4 | const port = process.env.PORT || '1212'; 5 | 6 | detectPort(port, (_err, availablePort) => { 7 | if (port !== String(availablePort)) { 8 | throw new Error( 9 | chalk.whiteBright.bgRed.bold( 10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start`, 11 | ), 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /desktop/.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import { rimrafSync } from 'rimraf'; 2 | import fs from 'fs'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | const foldersToRemove = [ 6 | webpackPaths.distPath, 7 | webpackPaths.buildPath, 8 | webpackPaths.dllPath, 9 | ]; 10 | 11 | foldersToRemove.forEach((folder) => { 12 | if (fs.existsSync(folder)) rimrafSync(folder); 13 | }); 14 | -------------------------------------------------------------------------------- /desktop/.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { rimrafSync } from 'rimraf'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | export default function deleteSourceMaps() { 7 | if (fs.existsSync(webpackPaths.distMainPath)) 8 | rimrafSync(path.join(webpackPaths.distMainPath, '*.js.map'), { 9 | glob: true, 10 | }); 11 | if (fs.existsSync(webpackPaths.distRendererPath)) 12 | rimrafSync(path.join(webpackPaths.distRendererPath, '*.js.map'), { 13 | glob: true, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /desktop/.erb/scripts/electron-rebuild.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fs from 'fs'; 3 | import { dependencies } from '../../release/app/package.json'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | if ( 7 | Object.keys(dependencies || {}).length > 0 && 8 | fs.existsSync(webpackPaths.appNodeModulesPath) 9 | ) { 10 | const electronRebuildCmd = 11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .'; 12 | const cmd = 13 | process.platform === 'win32' 14 | ? electronRebuildCmd.replace(/\//g, '\\') 15 | : electronRebuildCmd; 16 | execSync(cmd, { 17 | cwd: webpackPaths.appPath, 18 | stdio: 'inherit', 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /desktop/.erb/scripts/link-modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const { srcNodeModulesPath } = webpackPaths; 5 | const { appNodeModulesPath } = webpackPaths; 6 | 7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) { 8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction'); 9 | } 10 | -------------------------------------------------------------------------------- /desktop/.erb/scripts/notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require('@electron/notarize'); 2 | require('dotenv').config(); 3 | const { build } = require('../../package.json'); 4 | 5 | exports.default = async function notarizeMacos(context) { 6 | const { electronPlatformName, appOutDir } = context; 7 | if (electronPlatformName !== 'darwin') { 8 | return; 9 | } 10 | 11 | if (process.env.CI !== 'true') { 12 | console.warn('Skipping notarizing step. Packaging is not running in CI'); 13 | return; 14 | } 15 | 16 | if ( 17 | !('APPLE_ID' in process.env && 'APPLE_APP_SPECIFIC_PASSWORD' in process.env) 18 | ) { 19 | console.warn( 20 | 'Skipping notarizing step. APPLE_ID and APPLE_APP_SPECIFIC_PASSWORD env variables must be set', 21 | ); 22 | return; 23 | } 24 | 25 | const appName = context.packager.appInfo.productFilename; 26 | 27 | console.log(`Notarizing ${appName}`); 28 | console.log(` appBundleId: ${build.appId}`); 29 | console.log(` appPath: ${appOutDir}/${appName}.app`); 30 | console.log(` appleId: ${process.env.APPLE_ID}`); 31 | console.log(` teamId: ${process.env.APPLE_TEAM_ID}`); 32 | console.log(` appleIdPassword: ${process.env.APPLE_APP_SPECIFIC_PASSWORD}`); 33 | console.log(); 34 | 35 | try { 36 | await notarize({ 37 | appBundleId: build.appId, 38 | appPath: `${appOutDir}/${appName}.app`, 39 | appleId: process.env.APPLE_ID, 40 | appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD, 41 | teamId: process.env.APPLE_TEAM_ID, 42 | }); 43 | } catch (error) { 44 | console.log(`Failed to notarize ${appName}`); 45 | console.log(` appBundleId: ${build.appId}`); 46 | console.error(`Failed to notarize ${appName}`, error); 47 | console.error(error) 48 | } 49 | 50 | console.log('Notarization complete'); 51 | }; 52 | -------------------------------------------------------------------------------- /desktop/.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | 31 | # eslint ignores hidden directories by default: 32 | # https://github.com/eslint/eslint/issues/8429 33 | !.erb 34 | -------------------------------------------------------------------------------- /desktop/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'erb', 3 | plugins: ['@typescript-eslint'], 4 | rules: { 5 | // A temporary hack related to IDE not resolving correct package.json 6 | 'import/no-extraneous-dependencies': 'off', 7 | 'react/react-in-jsx-scope': 'off', 8 | 'react/jsx-filename-extension': 'off', 9 | 'import/extensions': 'off', 10 | 'import/no-unresolved': 'off', 11 | 'import/no-import-module-exports': 'off', 12 | 'no-shadow': 'off', 13 | '@typescript-eslint/no-shadow': 'error', 14 | 'no-unused-vars': 'off', 15 | '@typescript-eslint/no-unused-vars': 'error', 16 | { 17 | test: /\.(sass|less|css)$/, 18 | use: ['style-loader', 'css-loader', 'less-loader'] 19 | } 20 | }, 21 | parserOptions: { 22 | ecmaVersion: 2022, 23 | sourceType: 'module', 24 | }, 25 | settings: { 26 | 'import/resolver': { 27 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 28 | node: {}, 29 | webpack: { 30 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'), 31 | }, 32 | typescript: {}, 33 | }, 34 | 'import/parsers': { 35 | '@typescript-eslint/parser': ['.ts', '.tsx'], 36 | }, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /desktop/.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.exe binary 3 | *.png binary 4 | *.jpg binary 5 | *.jpeg binary 6 | *.ico binary 7 | *.icns binary 8 | *.eot binary 9 | *.otf binary 10 | *.ttf binary 11 | *.woff binary 12 | *.woff2 binary 13 | -------------------------------------------------------------------------------- /desktop/.github/ISSUE_TEMPLATE/1-Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: You're having technical issues. 🐞 4 | labels: 'bug' 5 | --- 6 | 7 | 8 | 9 | ## Prerequisites 10 | 11 | 12 | 13 | - [ ] Using npm 14 | - [ ] Using an up-to-date [`main` branch](https://github.com/electron-react-boilerplate/electron-react-boilerplate/tree/main) 15 | - [ ] Using latest version of devtools. [Check the docs for how to update](https://electron-react-boilerplate.js.org/docs/dev-tools/) 16 | - [ ] Tried solutions mentioned in [#400](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/400) 17 | - [ ] For issue in production release, add devtools output of `DEBUG_PROD=true npm run build && npm start` 18 | 19 | ## Expected Behavior 20 | 21 | 22 | 23 | ## Current Behavior 24 | 25 | 26 | 27 | ## Steps to Reproduce 28 | 29 | 30 | 31 | 32 | 1. 33 | 34 | 2. 35 | 36 | 3. 37 | 38 | 4. 39 | 40 | ## Possible Solution (Not obligatory) 41 | 42 | 43 | 44 | ## Context 45 | 46 | 47 | 48 | 49 | 50 | ## Your Environment 51 | 52 | 53 | 54 | - Node version : 55 | - electron-react-boilerplate version or branch : 56 | - Operating System and version : 57 | - Link to your project : 58 | 59 | 68 | -------------------------------------------------------------------------------- /desktop/.github/ISSUE_TEMPLATE/2-Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question.❓ 4 | labels: 'question' 5 | --- 6 | 7 | ## Summary 8 | 9 | 10 | 11 | 20 | -------------------------------------------------------------------------------- /desktop/.github/ISSUE_TEMPLATE/3-Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: You want something added to the boilerplate. 🎉 4 | labels: 'enhancement' 5 | --- 6 | 7 | 16 | -------------------------------------------------------------------------------- /desktop/.github/config.yml: -------------------------------------------------------------------------------- 1 | requiredHeaders: 2 | - Prerequisites 3 | - Expected Behavior 4 | - Current Behavior 5 | - Possible Solution 6 | - Your Environment 7 | -------------------------------------------------------------------------------- /desktop/.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - discussion 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /desktop/.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish: 10 | # To enable auto publishing to github, update your electron publisher 11 | # config in package.json > "build" and remove the conditional below 12 | if: ${{ github.repository_owner == 'electron-react-boilerplate' }} 13 | 14 | runs-on: ${{ matrix.os }} 15 | 16 | strategy: 17 | matrix: 18 | os: [macos-latest] 19 | 20 | steps: 21 | - name: Checkout git repo 22 | uses: actions/checkout@v3 23 | 24 | - name: Install Node and NPM 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: 18 28 | cache: npm 29 | 30 | - name: Install and build 31 | run: | 32 | npm install 33 | npm run postinstall 34 | npm run build 35 | 36 | - name: Publish releases 37 | env: 38 | # These values are used for auto updates signing 39 | APPLE_ID: ${{ secrets.APPLE_ID }} 40 | APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASS }} 41 | CSC_LINK: ${{ secrets.CSC_LINK }} 42 | CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} 43 | # This is used for uploading release assets to github 44 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 45 | run: | 46 | npm exec electron-builder -- --publish always --win --mac --linux 47 | -------------------------------------------------------------------------------- /desktop/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | matrix: 11 | os: [macos-latest, windows-latest, ubuntu-latest] 12 | 13 | steps: 14 | - name: Check out Git repository 15 | uses: actions/checkout@v3 16 | 17 | - name: Install Node.js and NPM 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 18 21 | cache: npm 22 | 23 | - name: npm install 24 | run: | 25 | npm install 26 | 27 | - name: npm test 28 | env: 29 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | run: | 31 | npm run package 32 | npm run lint 33 | npm exec tsc 34 | npm test 35 | -------------------------------------------------------------------------------- /desktop/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # chrome data 6 | src/firebase.js 7 | config.json 8 | edge_data.json 9 | chrome_data.json 10 | inputNodes.json 11 | axTree.json 12 | flattenedDOM.json 13 | ChromeWebData.db 14 | screenshot.pdf 15 | screenshot.png 16 | before_interaction.png 17 | after_interaction.png 18 | 19 | # Runtime data 20 | pids 21 | *.pid 22 | *.seed 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | .eslintcache 27 | 28 | # Dependency directory 29 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 30 | node_modules 31 | languages 32 | 33 | # OSX 34 | .DS_Store 35 | 36 | release/app/dist 37 | release/build 38 | .erb/dll 39 | 40 | .idea 41 | npm-debug.log.* 42 | *.css.d.ts 43 | *.sass.d.ts 44 | *.scss.d.ts 45 | *.llamafile 46 | 47 | # Added src/models directory 48 | src/models 49 | 50 | # Added .env file 51 | .env 52 | *.p12 53 | 54 | 55 | languages/Mac/python 56 | languages/Windows/python 57 | [0-9a-f]{10}*.json 58 | 59 | unpacked-app 60 | unpacked_asar 61 | #Any .pyc files 62 | *.pyc 63 | *.cpython-311.pyc 64 | /lancedb 65 | assets/chrome_windows.py 66 | ignoredAXTree.json 67 | 68 | -------------------------------------------------------------------------------- /desktop/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"] 3 | } 4 | -------------------------------------------------------------------------------- /desktop/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Electron: Main", 6 | "type": "node", 7 | "request": "launch", 8 | "protocol": "inspector", 9 | "runtimeExecutable": "npm", 10 | "runtimeArgs": ["run", "start"], 11 | "env": { 12 | "MAIN_ARGS": "--inspect=5858 --remote-debugging-port=9223" 13 | } 14 | }, 15 | { 16 | "name": "Electron: Renderer", 17 | "type": "chrome", 18 | "request": "attach", 19 | "port": 9223, 20 | "webRoot": "${workspaceFolder}", 21 | "timeout": 15000 22 | } 23 | ], 24 | "compounds": [ 25 | { 26 | "name": "Electron: All", 27 | "configurations": ["Electron: Main", "Electron: Renderer"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /desktop/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | ".eslintrc": "jsonc", 4 | ".prettierrc": "jsonc", 5 | ".eslintignore": "ignore" 6 | }, 7 | 8 | "eslint.validate": [ 9 | "javascript", 10 | "javascriptreact", 11 | "html", 12 | "typescriptreact" 13 | ], 14 | 15 | "javascript.validate.enable": false, 16 | "javascript.format.enable": false, 17 | "typescript.format.enable": false, 18 | 19 | "search.exclude": { 20 | ".git": true, 21 | ".eslintcache": true, 22 | ".erb/dll": true, 23 | "release/{build,app/dist}": true, 24 | "node_modules": true, 25 | "npm-debug.log.*": true, 26 | "test/**/__snapshots__": true, 27 | "package-lock.json": true, 28 | "*.{css,sass,scss}.d.ts": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /desktop/HELPER_FUNCTIONS.md: -------------------------------------------------------------------------------- 1 | # Helper Functions Documentation 2 | 3 | Surfer uses various helper functions to streamline the scraping process. Here's an overview of key helpers defined in `preloadFunctions.js`: 4 | 5 | 1. `customConsoleLog(...args)`: 6 | - Sends console log messages to the renderer process via IPC. 7 | - Converts objects to strings to avoid cloning issues. 8 | 9 | 2. `waitForElement(id, selector, elementName, multipleElements = false, timeout = 10000)`: 10 | - Waits for an element to appear in the DOM. 11 | - Parameters: 12 | - `id`: ID of the run. 13 | - `selector`: CSS selector for the element. 14 | - `elementName`: Name of the element for logging. 15 | - `multipleElements`: Set to true if waiting for multiple elements. 16 | - `timeout`: Maximum wait time in milliseconds. 17 | - Returns a Promise that resolves with the element(s) or null if timed out. 18 | 19 | 3. `wait(seconds)`: 20 | - Creates a delay for the specified number of seconds. 21 | - Returns a Promise that resolves after the delay. 22 | 23 | These helper functions are designed to assist with common tasks in the scraping process, such as waiting for elements to load, cleaning HTML, and managing asynchronous operations. Use them in your platform-specific modules to maintain consistency and improve code readability. 24 | -------------------------------------------------------------------------------- /desktop/assets/Surfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/Surfer.png -------------------------------------------------------------------------------- /desktop/assets/SurferApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/SurferApp.png -------------------------------------------------------------------------------- /desktop/assets/SurferDemo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/SurferDemo.mp4 -------------------------------------------------------------------------------- /desktop/assets/SurferDiagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/SurferDiagram.png -------------------------------------------------------------------------------- /desktop/assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | type Styles = Record; 2 | 3 | declare module '*.svg' { 4 | import React = require('react'); 5 | 6 | export const ReactComponent: React.FC>; 7 | 8 | const content: string; 9 | export default content; 10 | } 11 | 12 | declare module '*.png' { 13 | const content: string; 14 | export default content; 15 | } 16 | 17 | declare module '*.jpg' { 18 | const content: string; 19 | export default content; 20 | } 21 | 22 | declare module '*.scss' { 23 | const content: Styles; 24 | export default content; 25 | } 26 | 27 | declare module '*.sass' { 28 | const content: Styles; 29 | export default content; 30 | } 31 | 32 | declare module '*.css' { 33 | const content: Styles; 34 | export default content; 35 | } 36 | -------------------------------------------------------------------------------- /desktop/assets/current_db.py: -------------------------------------------------------------------------------- 1 | import chromadb 2 | import os 3 | import sys 4 | import json 5 | 6 | user_data_path = sys.argv[1] 7 | 8 | 9 | # Initialize ChromaDB client with persistent storage 10 | persistent_dir = os.path.join(user_data_path, "vector_db") 11 | client = chromadb.PersistentClient(path=persistent_dir) 12 | 13 | # Get collection 14 | collection = client.get_collection("surfer_collection") 15 | 16 | existing_chunks = collection.get() 17 | names_array = list(set(chunk['name'] for chunk in existing_chunks['metadatas'])) 18 | print(names_array) -------------------------------------------------------------------------------- /desktop/assets/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.cs.disable-library-validation 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /desktop/assets/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/error.png -------------------------------------------------------------------------------- /desktop/assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/icon.icns -------------------------------------------------------------------------------- /desktop/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Surfer-Org/Protocol/5f70e1ad5c0103b3c4704dd99b248df72e933873/desktop/assets/icon.png -------------------------------------------------------------------------------- /desktop/assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /desktop/assets/password-prompt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Enter password 5 | 53 | 54 | 55 |
56 |

Enter the password for your iPhone backup:

57 | 58 | 59 |
60 | 68 | 69 | -------------------------------------------------------------------------------- /desktop/assets/requirements.txt: -------------------------------------------------------------------------------- 1 | chromadb 2 | -------------------------------------------------------------------------------- /desktop/assets/search_vector_db.py: -------------------------------------------------------------------------------- 1 | import chromadb 2 | import os 3 | import sys 4 | import json 5 | 6 | user_data_path = sys.argv[1] 7 | query = sys.argv[2] 8 | platform = sys.argv[3] 9 | 10 | try: 11 | # Initialize ChromaDB client with persistent storage 12 | persistent_dir = os.path.join(user_data_path, "vector_db") 13 | client = chromadb.PersistentClient(path=persistent_dir) 14 | 15 | # Get collection 16 | collection = client.get_collection("surfer_collection") 17 | 18 | # Perform search 19 | regular_results = collection.query( 20 | query_texts=[query], 21 | n_results=5, 22 | where={"name": platform} 23 | ) 24 | 25 | # full_text_results = collection.query( 26 | # query_texts=[query], 27 | # n_results=5, 28 | # where={"name": platform}, 29 | # where_document={"$contains":query} 30 | # ) 31 | 32 | # full_results = { 33 | # "regular_results": regular_results, 34 | # "full_text_results": full_text_results 35 | # } 36 | 37 | # Format results for output 38 | formatted_results = { 39 | "documents": regular_results["documents"][0], 40 | "distances": regular_results["distances"][0], 41 | "ids": regular_results["ids"][0], 42 | "metadata": regular_results["metadatas"][0] 43 | } 44 | 45 | # Print as JSON for easy parsing 46 | print(json.dumps(formatted_results)) 47 | 48 | except Exception as e: 49 | print(json.dumps({"error": str(e)}), file=sys.stderr) 50 | sys.exit(1) 51 | 52 | -------------------------------------------------------------------------------- /desktop/assets/windows_requirements.txt: -------------------------------------------------------------------------------- 1 | iphone_backup_decrypt 2 | fastpbkdf2 3 | chromadb -------------------------------------------------------------------------------- /desktop/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('tailwindcss'), require('autoprefixer')], 3 | }; 4 | -------------------------------------------------------------------------------- /desktop/release/app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "surfer", 3 | "version": "1.0.4", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "surfer", 9 | "version": "1.0.4", 10 | "hasInstallScript": true 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /desktop/release/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Surfer is a desktop app that allows you to export your personal data.", 3 | "productName": "Surfer", 4 | "name": "surfer", 5 | "version": "1.0.4", 6 | "keywords": [ 7 | "electron", 8 | "ai", 9 | "chat", 10 | "interface", 11 | "local", 12 | "personalized", 13 | "llms", 14 | "chatGPT", 15 | "gpt3", 16 | "gpt4", 17 | "chatbot" 18 | ], 19 | "repository": "https://github.com/Surfer-Org/Protocol/tree/main/desktop", 20 | "publish": { 21 | "provider": "github", 22 | "releaseType": "release" 23 | }, 24 | "author": { 25 | "name": "Sahil Lalani, Jack Blair, Thomas Stahura", 26 | "email": "lihas1002@gmail.com", 27 | "url": "https://surferprotocol.org" 28 | }, 29 | "main": "./dist/main/main.js", 30 | "scripts": { 31 | "rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js", 32 | "postinstall": "npm run rebuild && npm run link-modules", 33 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /desktop/release/app/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: {} 10 | -------------------------------------------------------------------------------- /desktop/src/main/platforms/Apple/imessage.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require('electron'); 2 | const { customConsoleLog } = require('../../preloadFunctions'); 3 | 4 | async function exportiMessage(id, platformId, filename, company, name) { 5 | customConsoleLog(id, 'Exporting iMessages'); 6 | const messageData = await ipcRenderer.invoke( 7 | 'get-imessage-data', 8 | company, 9 | name, 10 | id, 11 | ); 12 | return 'NOTHING'; 13 | } 14 | 15 | module.exports = exportiMessage; 16 | -------------------------------------------------------------------------------- /desktop/src/main/platforms/Apple/imessage.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iMessage", 3 | "description": "Exports all your iMessages.", 4 | "needsConnection": false, 5 | "vectorize_config": { 6 | "documents": "text" 7 | } 8 | } -------------------------------------------------------------------------------- /desktop/src/main/platforms/Apple/imessage.md: -------------------------------------------------------------------------------- 1 | https://github.com/saubury/paranoid_text_LLM/ 2 | 3 | https://simon-aubury.medium.com/my-data-your-llm-paranoid-analysis-of-imessage-chats-with-openai-llamaindex-duckdb-60e5eb9e23e3#:~:text=INSTALL%20sqlite_scanner;,Load%20messages%20into%20table 4 | -------------------------------------------------------------------------------- /desktop/src/main/platforms/Google/gmail.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Gmail", 3 | "description": "Exports all emails.", 4 | "isUpdated": true, 5 | "connectURL": "https://takeout.google.com/u/0/settings/takeout/custom/gmail", 6 | "connectSelector": "button[aria-label='Next step']", 7 | "exportFrequency": "hourly", 8 | "vectorize_config": { 9 | "documents": "body" 10 | } 11 | } -------------------------------------------------------------------------------- /desktop/src/main/platforms/Google/gmail.md: -------------------------------------------------------------------------------- 1 | # Gmail 2 | 3 | This module is responsible for scraping email data from a user's Gmail account. 4 | 5 | ## Functionality 6 | 7 | The `exportGmail()` function in the `gmail.js` file performs the following tasks: 8 | 9 | 1. Checks if this is the first export or a subsequent one. 10 | 2. For the first export: 11 | - Initiates a Google Takeout process to download all emails. 12 | - Navigates to Gmail to wait for the Takeout email. 13 | - Downloads the Takeout data. 14 | 3. For subsequent exports: 15 | - Checks if the user is connected to their Gmail account. 16 | - Collects new email content and stores it in an array. 17 | - Navigates through emails and checks for duplicates. 18 | - Updates the local storage with new emails. 19 | 20 | ## Implementation 21 | 22 | The Gmail export functionality is implemented in the `gmail.js` file. Here's an overview of the implementation: 23 | 24 | 1. The `exportGmail()` function is the main entry point. 25 | 2. It uses helper functions like `checkIfEmailExists()` to avoid duplicates. 26 | 3. For first-time exports, it uses Google Takeout. 27 | 4. For subsequent exports, it scrapes emails directly from the Gmail interface. 28 | 5. The process uses IPC messages to communicate with the main process for various operations. 29 | 30 | ## Platform-specific Considerations 31 | 32 | 1. Navigation: The module uses specific selectors to navigate the Gmail interface, which may need updating if the UI changes. 33 | 2. Google Takeout: The first export uses Google Takeout, which may have rate limits or change its interface. 34 | 3. Email Content Extraction: The module extracts email content by targeting specific HTML elements, which may require updates if Gmail's structure changes. 35 | 36 | ## Future Improvements 37 | 38 | 1. **Handling Multiple Email Threads**: Incorporate thread-level data for a more comprehensive view of the user's email history. 39 | 2. **Optimizing Incremental Data Collection**: Further refine the process of collecting only new emails to improve efficiency. 40 | 3. **Enhanced Error Handling**: Add more robust error handling and recovery mechanisms. 41 | 4. **User Settings**: Implement options for users to customize the export process (e.g., date ranges, specific folders). 42 | 5. **Performance Optimization**: Improve the speed of data collection, especially for accounts with large numbers of emails. -------------------------------------------------------------------------------- /desktop/src/main/platforms/Google/youtube.js: -------------------------------------------------------------------------------- 1 | const { 2 | customConsoleLog , 3 | wait, 4 | waitForElement, 5 | } = require('../../preloadFunctions'); 6 | const { ipcRenderer } = require('electron'); 7 | 8 | async function exportYoutube(id, platformId, filename, company, name) { 9 | if (!window.location.href.includes('youtube.com')) { 10 | customConsoleLog(id, 'Navigating to YouTube'); 11 | window.location.assign('https://www.youtube.com/'); 12 | } 13 | 14 | await wait(10); 15 | 16 | if (document.querySelector('a[aria-label="Sign in"]')) { 17 | customConsoleLog(id, 'YOU NEED TO SIGN IN (click the eye in the top right)!'); 18 | ipcRenderer.send('connect-website', id); 19 | return 'CONNECT_WEBSITE'; 20 | } 21 | const videoData = []; 22 | 23 | // Extract video information 24 | customConsoleLog(id, 'Waiting for Video elements'); 25 | const videoElements = await waitForElement( 26 | id, 27 | 'ytd-rich-grid-media', 28 | 'Video elements', 29 | true, 30 | ); 31 | 32 | if (videoElements && videoElements.length > 0) { 33 | customConsoleLog(id, 'Got Video elements'); 34 | for (const videoElement of videoElements) { 35 | const titleElement = videoElement.querySelector( 36 | 'yt-formatted-string#video-title', 37 | ); 38 | 39 | const linkElement = videoElement.querySelector( 40 | '#video-title-link' 41 | ) 42 | const channelElement = videoElement.querySelector( 43 | 'a.yt-simple-endpoint.style-scope.yt-formatted-string[href^="/@"]', 44 | ); 45 | const viewCountElement = videoElement.querySelector( 46 | 'span.inline-metadata-item', 47 | ); 48 | 49 | if (titleElement && channelElement && viewCountElement) { 50 | videoData.push({ 51 | title: titleElement.textContent.trim(), 52 | url: linkElement.href, 53 | channel: channelElement.textContent.trim(), 54 | viewCount: viewCountElement.textContent.trim(), 55 | }); 56 | } 57 | } 58 | } else { 59 | customConsoleLog(id, 'No video elements found'); 60 | } 61 | 62 | customConsoleLog(id, 'Video data collected:', videoData.length); 63 | 64 | return videoData; 65 | } 66 | 67 | module.exports = exportYoutube; 68 | 69 | 70 | -------------------------------------------------------------------------------- /desktop/src/main/platforms/Google/youtube.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "YouTube", 3 | "description": "Exports the videos in your recommended feed.", 4 | "connectURL": "https://www.youtube.com", 5 | "connectSelector": "img[alt='Avatar image']" 6 | } -------------------------------------------------------------------------------- /desktop/src/main/platforms/Google/youtube.md: -------------------------------------------------------------------------------- 1 | # YouTube 2 | 3 | This module is responsible for scraping video data from the YouTube home page. 4 | 5 | ## Functionality 6 | 7 | The `exportYoutube()` function in the `youtube.js` file performs the following tasks: 8 | 9 | 1. Navigates to the YouTube home page if not already there. 10 | 2. Checks if the user is signed in and handles the sign-in process if needed. 11 | 3. Waits for video elements to load on the page. 12 | 4. Collects video information (title, URL, channel name, and view count) from the home page. 13 | 5. Stores the collected data in an array. 14 | 6. Returns the collected video data. 15 | 16 | ## Implementation 17 | 18 | The YouTube export functionality is implemented in the `youtube.js` file. Here's an overview of the implementation: 19 | 20 | 1. The `exportYoutube()` function is defined and exported as a module. 21 | 2. It uses helper functions like `customConsoleLog`, `wait`, `waitForElement` or other helper functions for various tasks. 22 | 3. The function handles navigation, sign-in checks, and data extraction. 23 | 4. It uses Electron's `ipcRenderer` to communicate with the main process. 24 | 25 | ## Platform-specific Considerations 26 | 27 | 1. Element Selection: The module uses specific CSS selectors to target video information elements, which may need updating if YouTube's HTML structure changes. 28 | 2. Home Page Focus: The current implementation only extracts data from the home page. 29 | 3. Sign-in Handling: The module checks for a sign-in button and handles the sign-in process if needed. 30 | 31 | ## Future Improvements 32 | 33 | 1. **Expand Data Collection**: Include additional metadata such as video duration and upload date. 34 | 2. **User-specific Data**: Implement functionality to extract data from user's playlists, subscriptions, or watch history. 35 | 3. **Pagination Handling**: Add the ability to navigate through multiple pages of video results. 36 | 4. **Enhanced Error Handling**: Implement more robust error handling and recovery mechanisms. 37 | 5. **Rate Limiting**: Implement rate limiting to avoid potential issues with YouTube's anti-scraping measures. 38 | 6. **Configurable Export**: Allow users to specify the number of videos to export or set other export parameters. 39 | -------------------------------------------------------------------------------- /desktop/src/main/platforms/Microsoft/connections.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LinkedIn Connections", 3 | "id": "connections-001", 4 | "description": "Exports your connections.", 5 | "logoURL": "https://logo.clearbit.com/linkedin.com", 6 | "connectURL": "https://linkedin.com/login", 7 | "connectSelector": "img[alt*='Photo of']", 8 | "exportFrequency": "daily", 9 | "vectorize_config": { 10 | "documents": "headline" 11 | } 12 | } -------------------------------------------------------------------------------- /desktop/src/main/platforms/Microsoft/connections.md: -------------------------------------------------------------------------------- 1 | # LinkedIn 2 | 3 | This module provides functionality to export a user's LinkedIn profile data. 4 | 5 | ## Functionality 6 | 7 | The module consists of one main function: 8 | 9 | 1. `exportLinkedin()`: Navigates to the user's profile page and exports specific profile data. 10 | 11 | ## Implementation 12 | 13 | The export process follows these steps: 14 | 15 | 1. Navigate to LinkedIn if not already there. 16 | 2. Check for user authentication and prompt for login if necessary. 17 | 3. Click on the profile button and then the contact info button. 18 | 4. Extract specific profile data including: 19 | - Name 20 | - Subheading 21 | - About section 22 | - Profile URL 23 | - Experience 24 | - Email address 25 | 5. Structure the extracted data as an object. 26 | 6. Return the structured data for further processing. 27 | 28 | ## Platform-specific Considerations 29 | 30 | 1. DOM Manipulation: The module relies on specific selectors to find and interact with page elements. These may need updates if LinkedIn's HTML structure changes. 31 | 2. Timing: The module uses wait functions to account for page load times, which may need adjustment based on network conditions. 32 | 3. Authentication: The script checks for authentication status and can prompt for login if needed. 33 | 34 | ## Future Improvements 35 | 36 | 1. Data Enrichment: Expand data extraction to include additional profile sections like education, skills, and recommendations. 37 | 2. Error Handling: Implement more robust error handling and recovery mechanisms. 38 | 3. Progressive Loading: Handle LinkedIn's progressive loading behavior to ensure all profile data is captured, especially for profiles with extensive content. 39 | 4. Rate Limiting: Implement respect for LinkedIn's rate limits to prevent potential blocking. 40 | 5. API Integration: Consider using LinkedIn's API for data retrieval instead of web scraping, which could be faster and more reliable. 41 | -------------------------------------------------------------------------------- /desktop/src/main/platforms/Notion/notion.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Notion", 3 | "description": "Exports your entire workspace.", 4 | "connectURL": "https://www.notion.so/login", 5 | "connectSelector": ".notion-sidebar-switcher", 6 | "exportFrequency": "daily", 7 | "vectorize_config": { 8 | "documents": "text" 9 | } 10 | } -------------------------------------------------------------------------------- /desktop/src/main/platforms/Notion/notion.md: -------------------------------------------------------------------------------- 1 | # Notion 2 | 3 | This module exports all workspace content from a user's Notion account. 4 | 5 | ## Functionality 6 | 7 | The `exportNotion()` function performs these tasks: 8 | 1. Checks if the user is connected to Notion. 9 | 2. Navigates through the Notion interface to reach the export dialog. 10 | 3. Initiates the export of all workspace content. 11 | 12 | ## Implementation 13 | 14 | The Notion export process is implemented as follows: 15 | 1. The function checks if the current page is Notion, navigating to it if not. 16 | 2. It verifies user authentication, prompting for sign-in if needed. 17 | 3. The function navigates through the Notion interface by: 18 | - Clicking the workspace dropdown 19 | - Accessing Settings through two separate button clicks 20 | - Selecting "Export all workspace content" 21 | - Confirming the export 22 | 4. The process uses `waitForElement()` to ensure UI elements are present before interacting. 23 | 5. Error handling is implemented throughout the process. 24 | 25 | ## Platform-specific Considerations 26 | 27 | 1. DOM Manipulation: The module relies on specific class names, roles, and text content to navigate the Notion interface. 28 | 2. Timing: The `wait()` function is used to account for page load and animation times. 29 | 3. Scrolling: Elements are scrolled into view before clicking to ensure visibility. 30 | 4. IPC Communication: The module uses Electron's IPC to communicate with the main process for actions like connecting to the website. 31 | 32 | ## Future Improvements 33 | 34 | 1. Error Handling: While error handling is implemented, it could be further enhanced for each step of the process. 35 | 2. Dynamic Waiting: Replace fixed waits with more dynamic waiting for elements to appear or change. 36 | 3. Selective Export: Add options to export specific pages or sections instead of the entire workspace. 37 | 4. Progress Tracking: Implement a way to track and report the export progress beyond the current step logging. 38 | 39 | -------------------------------------------------------------------------------- /desktop/src/main/platforms/OpenAI/chatgpt.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ChatGPT", 3 | "description": "Exports your entire ChatGPT history.", 4 | "connectURL": "https://chat.openai.com", 5 | "connectSelector": "img[alt='User']", 6 | "exportFrequency": "daily", 7 | "vectorize_config": { 8 | "documents": "title" 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /desktop/src/main/platforms/X Corp/bookmarks.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Twitter Bookmarks", 3 | "description": "Exports your Twitter bookmarks", 4 | "logoURL": "https://logo.clearbit.com/twitter.com", 5 | "isUpdated": true, 6 | "connectURL": "https://twitter.com", 7 | "connectSelector": "img.css-9pa8cd", 8 | "exportFrequency": "daily", 9 | "vectorize_config": { 10 | "documents": "text" 11 | } 12 | } -------------------------------------------------------------------------------- /desktop/src/main/platforms/X Corp/twitter.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Twitter Posts", 3 | "description": "Exports all your tweets.", 4 | "isUpdated": true, 5 | "logoURL": "https://logo.clearbit.com/twitter.com", 6 | "connectURL": "https://twitter.com", 7 | "connectSelector": "img.css-9pa8cd" 8 | } -------------------------------------------------------------------------------- /desktop/src/main/preload.ts: -------------------------------------------------------------------------------- 1 | const { 2 | contextBridge, 3 | ipcRenderer, 4 | IpcRendererEvent, 5 | app, 6 | } = require('electron'); 7 | 8 | const state = { 9 | workspaceID: '', 10 | }; 11 | 12 | const electronHandler = { 13 | ipcRenderer: { 14 | send(channel, ...args) { 15 | ipcRenderer.send(channel, ...args); 16 | }, 17 | on(channel, func) { 18 | const subscription = (_event, ...args) => func(...args); 19 | ipcRenderer.on(channel, subscription); 20 | 21 | return () => { 22 | ipcRenderer.removeListener(channel, subscription); 23 | }; 24 | }, 25 | once(channel, func) { 26 | ipcRenderer.once(channel, (_event, ...args) => func(...args)); 27 | }, 28 | removeAllListeners(channel) { 29 | ipcRenderer.removeAllListeners(channel); 30 | }, 31 | removeListener(channel, listener) { 32 | ipcRenderer.removeListener(channel, listener); 33 | }, 34 | off(channel, func) { 35 | const subscription = (_event, ...args) => func(...args); 36 | ipcRenderer.removeListener(channel, subscription); 37 | }, 38 | invoke(channel, ...args) { 39 | return ipcRenderer.invoke(channel, ...args); 40 | }, 41 | }, 42 | }; 43 | 44 | contextBridge.exposeInMainWorld('electron', electronHandler); 45 | -------------------------------------------------------------------------------- /desktop/src/main/preloadElectron.js: -------------------------------------------------------------------------------- 1 | const electronHandler = { 2 | ipcRenderer: { 3 | send(channel, ...args) { 4 | ipcRenderer.send(channel, ...args); 5 | }, 6 | on(channel, func) { 7 | const subscription = (_event, ...args) => func(...args); 8 | ipcRenderer.on(channel, subscription); 9 | 10 | return () => { 11 | ipcRenderer.removeListener(channel, subscription); 12 | }; 13 | }, 14 | once(channel, func) { 15 | ipcRenderer.once(channel, (_event, ...args) => func(...args)); 16 | }, 17 | removeAllListeners(channel) { 18 | ipcRenderer.removeAllListeners(channel); 19 | }, 20 | off(channel, func) { 21 | const subscription = (_event, ...args) => func(...args); 22 | ipcRenderer.removeListener(channel, subscription); 23 | }, 24 | invoke(channel, ...args) { 25 | return ipcRenderer.invoke(channel, ...args); 26 | }, 27 | }, 28 | }; 29 | 30 | module.exports = electronHandler -------------------------------------------------------------------------------- /desktop/src/main/preloadFunctions.js: -------------------------------------------------------------------------------- 1 | const { ipcRenderer } = require('electron') 2 | 3 | function customConsoleLog(id, ...args) { 4 | // Convert arguments to strings to avoid cloning issues 5 | const stringArgs = args.map((arg) => 6 | typeof arg === 'object' ? JSON.stringify(arg) : arg, 7 | ); 8 | ipcRenderer.sendToHost('console-log', id, ...stringArgs); 9 | }; 10 | 11 | function waitForElement( 12 | id, 13 | selector, 14 | elementName, 15 | multipleElements = false, 16 | timeout = 10000, 17 | ) { 18 | 19 | if (!multipleElements){ 20 | customConsoleLog(id, `Waiting for ${elementName}`); 21 | } 22 | 23 | return new Promise((resolve) => { 24 | const startTime = Date.now(); 25 | 26 | const checkElement = () => { 27 | const element = multipleElements 28 | ? document.querySelectorAll(selector) 29 | : document.querySelector(selector); 30 | if (element) { 31 | if (!multipleElements){ 32 | customConsoleLog(id, `Found ${elementName}`); 33 | } 34 | resolve(element); 35 | } else if (Date.now() - startTime >= timeout) { 36 | customConsoleLog(id, `Timeout waiting for ${elementName}`); 37 | resolve(null); 38 | } else { 39 | setTimeout(checkElement, 100); 40 | } 41 | }; 42 | 43 | checkElement(); 44 | }); 45 | } 46 | 47 | async function wait(seconds) { 48 | return new Promise((resolve) => { 49 | setTimeout(resolve, seconds * 1000); 50 | }); 51 | } 52 | 53 | async function waitForContentToStabilize() { 54 | return new Promise((resolve) => { 55 | let timeout; 56 | const observer = new MutationObserver(() => { 57 | clearTimeout(timeout); 58 | timeout = setTimeout(() => { 59 | observer.disconnect(); 60 | console.log('Content has stabilized'); 61 | resolve(); 62 | }, 100); // Adjust this delay as needed 63 | }); 64 | 65 | observer.observe(document.body, { 66 | childList: true, 67 | subtree: true, 68 | attributes: true, 69 | }); 70 | 71 | // Fallback in case the page never stabilizes 72 | setTimeout(() => { 73 | observer.disconnect(); 74 | console.log('Timed out waiting for content to stabilize'); 75 | resolve(); 76 | }, 5000); // Adjust this timeout as needed 77 | }); 78 | } 79 | 80 | 81 | 82 | module.exports = { customConsoleLog,waitForElement, wait, waitForContentToStabilize } -------------------------------------------------------------------------------- /desktop/src/main/preloadWebview.js: -------------------------------------------------------------------------------- 1 | if (window.trustedTypes && window.trustedTypes.createPolicy) { 2 | window.trustedTypes.createPolicy('default', { 3 | createHTML: (string, sink) => string, 4 | }); 5 | } 6 | 7 | const { ipcRenderer } = require('electron'); 8 | const { customConsoleLog } = require('./preloadFunctions'); 9 | 10 | 11 | ipcRenderer.on('export-platform', async (event, runID, platformId, filename, company, name, isUpdated) => { 12 | const platform = require(`./platforms/${company}/${filename}.js`); 13 | 14 | const data = await platform(runID, platformId, filename, company, name); 15 | console.log('data', data); 16 | 17 | if (data === 'CONNECT_WEBSITE') { 18 | console.log('CONNECT_WEBSITE'); 19 | } 20 | 21 | else if (data === 'NOTHING') { 22 | console.log('NOTHING') 23 | } 24 | 25 | else if (data === 'DOWNLOADING') { 26 | customConsoleLog(runID, 'Downloading export (will take some time!'); 27 | } 28 | 29 | else if (data === 'HANDLE_UPDATE_COMPLETE') { 30 | customConsoleLog(runID, 'Finishing updating data!'); 31 | } 32 | 33 | else if (data) { 34 | ipcRenderer.send( 35 | 'handle-export', 36 | runID, 37 | platformId, 38 | filename, 39 | company, 40 | name, 41 | data, 42 | isUpdated, 43 | ); 44 | customConsoleLog(runID, 'Got data, need to export now'); 45 | } 46 | 47 | else { 48 | console.log( 49 | runID, 50 | "Something might've gone wrong (click the Eye icon to view the run)", 51 | ); 52 | } 53 | }); -------------------------------------------------------------------------------- /desktop/src/renderer/App.tsx: -------------------------------------------------------------------------------- 1 | import { Provider } from 'react-redux'; 2 | import store from './state/store'; 3 | import { ThemeProvider } from './components/ui/theme-provider'; 4 | import './styles/globals.css'; 5 | import Surfer from './Surfer'; 6 | 7 | export default function App() { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/CodeBlock.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Button } from "./ui/button"; 3 | import { Copy, Check } from 'lucide-react'; 4 | import MonacoEditor from '@monaco-editor/react'; 5 | import { getLanguageFromFilename } from '../helpers'; 6 | 7 | const CodeBlock = ({ run, code, filename }) => { 8 | const [showCheck, setShowCheck] = useState(false); 9 | 10 | const detectedLanguage = filename 11 | ? getLanguageFromFilename(filename) 12 | : 'plaintext'; 13 | 14 | const handleCopyCode = () => { 15 | navigator.clipboard.writeText(code); 16 | setShowCheck(true); 17 | setTimeout(() => setShowCheck(false), 500); 18 | }; 19 | 20 | let formattedCode; 21 | 22 | if (detectedLanguage === 'python') { 23 | formattedCode = code.replace('platform-001', run.platformId); 24 | } 25 | else if (detectedLanguage === 'markdown') { 26 | formattedCode = code.replace('[insert-folder-path-of-your-choice-here]', run.exportPath); 27 | } 28 | else { 29 | formattedCode = code; 30 | } 31 | 32 | return ( 33 |
34 |
35 |
36 | 48 | 60 |
61 |
62 |
63 | ); 64 | }; 65 | 66 | export default CodeBlock; 67 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Outlet } from 'react-router-dom'; 3 | import { Header } from './Header'; 4 | import Webview from './Webview'; 5 | 6 | interface LayoutProps { 7 | webviewRefs: { [key: string]: React.RefObject }; 8 | getWebviewRef: (runId: string) => React.RefObject; 9 | contentScale: number; 10 | onHomeClick: () => void; 11 | children: React.ReactNode; 12 | } 13 | 14 | const Layout: React.FC = ({ 15 | webviewRefs, 16 | getWebviewRef, 17 | contentScale, 18 | onHomeClick, 19 | children 20 | }) => { 21 | return ( 22 |
23 |
24 |
25 |
34 |
35 | {children} 36 | 40 |
41 |
42 |
43 |
44 | ); 45 | }; 46 | 47 | export default Layout; 48 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AccordionPrimitive from "@radix-ui/react-accordion" 5 | import { ChevronDown } from "lucide-react" 6 | 7 | import { cn } from "./cn" 8 | 9 | const Accordion = AccordionPrimitive.Root 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )) 21 | AccordionItem.displayName = "AccordionItem" 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, children, ...props }, ref) => ( 27 | 28 | svg]:rotate-180", 32 | className 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | 38 | 39 | 40 | )) 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, children, ...props }, ref) => ( 47 | 52 |
{children}
53 |
54 | )) 55 | 56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 57 | 58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } 59 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "./cn" 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-background text-foreground", 12 | destructive: 13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", 14 | moreDestructive: 15 | "bg-red-700 text-white border-red-500 dark:border-red-500 [&>svg]:text-white", 16 | }, 17 | }, 18 | defaultVariants: { 19 | variant: "default", 20 | }, 21 | } 22 | ) 23 | 24 | const Alert = React.forwardRef< 25 | HTMLDivElement, 26 | React.HTMLAttributes & VariantProps 27 | >(({ className, variant, ...props }, ref) => ( 28 |
34 | )) 35 | Alert.displayName = "Alert" 36 | 37 | const AlertTitle = React.forwardRef< 38 | HTMLParagraphElement, 39 | React.HTMLAttributes 40 | >(({ className, ...props }, ref) => ( 41 |
46 | )) 47 | AlertTitle.displayName = "AlertTitle" 48 | 49 | const AlertDescription = React.forwardRef< 50 | HTMLParagraphElement, 51 | React.HTMLAttributes 52 | >(({ className, ...props }, ref) => ( 53 |
58 | )) 59 | AlertDescription.displayName = "AlertDescription" 60 | 61 | export { Alert, AlertTitle, AlertDescription } 62 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root 6 | 7 | export { AspectRatio } 8 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "./cn" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } 51 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "./cn" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "./cn" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "./cn" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

44 | )) 45 | CardTitle.displayName = "CardTitle" 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

56 | )) 57 | CardDescription.displayName = "CardDescription" 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

64 | )) 65 | CardContent.displayName = "CardContent" 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )) 77 | CardFooter.displayName = "CardFooter" 78 | 79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 80 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox" 5 | import { Check } from "lucide-react" 6 | 7 | import { cn } from "./cn" 8 | 9 | const Checkbox = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 21 | 24 | 25 | 26 | 27 | )) 28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName 29 | 30 | export { Checkbox } 31 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/cn.js: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/hover-card.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card" 5 | 6 | import { cn } from "./cn" 7 | 8 | const HoverCard = HoverCardPrimitive.Root 9 | 10 | const HoverCardTrigger = HoverCardPrimitive.Trigger 11 | 12 | const HoverCardContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 16 | 26 | )) 27 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName 28 | 29 | export { HoverCard, HoverCardTrigger, HoverCardContent } 30 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "./cn" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, onChange, ...props }, ref) => { 10 | const handleKeyDown = (event: React.KeyboardEvent) => { 11 | if (event.key === 'Escape') { 12 | const target = event.currentTarget; 13 | target.value = ''; 14 | 15 | // Create a custom event 16 | const customEvent = new CustomEvent('input', { 17 | bubbles: true, 18 | cancelable: true, 19 | detail: { value: '' } 20 | }); 21 | 22 | // Dispatch the custom event 23 | target.dispatchEvent(customEvent); 24 | 25 | // Call the onChange handler with a synthesized React event 26 | onChange && onChange({ 27 | target, 28 | currentTarget: target, 29 | type: 'input', 30 | } as React.ChangeEvent); 31 | } 32 | // Call the original onKeyDown if it exists 33 | props.onKeyDown?.(event); 34 | }; 35 | 36 | return ( 37 | 48 | ) 49 | } 50 | ) 51 | Input.displayName = "Input" 52 | 53 | export { Input } 54 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "./cn" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as PopoverPrimitive from "@radix-ui/react-popover" 5 | 6 | import { cn } from "./cn" 7 | 8 | const Popover = PopoverPrimitive.Root 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger 11 | 12 | const PopoverContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 16 | 17 | 27 | 28 | )) 29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName 30 | 31 | export { Popover, PopoverTrigger, PopoverContent } 32 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ProgressPrimitive from "@radix-ui/react-progress" 5 | 6 | import { cn } from "./cn" 7 | 8 | const Progress = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, value, ...props }, ref) => ( 12 | 20 | 24 | 25 | )) 26 | Progress.displayName = ProgressPrimitive.Root.displayName 27 | 28 | export { Progress } 29 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/radio-group.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" 5 | import { Circle } from "lucide-react" 6 | 7 | import { cn } from "./cn" 8 | 9 | const RadioGroup = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => { 13 | return ( 14 | 19 | ) 20 | }) 21 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName 22 | 23 | const RadioGroupItem = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => { 27 | return ( 28 | 36 | 37 | 38 | 39 | 40 | ) 41 | }) 42 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName 43 | 44 | export { RadioGroup, RadioGroupItem } 45 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/resizable.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { GripVertical } from "lucide-react" 4 | import * as ResizablePrimitive from "react-resizable-panels" 5 | 6 | import { cn } from "./cn" 7 | 8 | const ResizablePanelGroup = ({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) => ( 12 | 19 | ) 20 | 21 | const ResizablePanel = ResizablePrimitive.Panel 22 | 23 | const ResizableHandle = ({ 24 | withHandle, 25 | className, 26 | ...props 27 | }: React.ComponentProps & { 28 | withHandle?: boolean 29 | }) => ( 30 | div]:rotate-90", 33 | className 34 | )} 35 | {...props} 36 | > 37 | {withHandle && ( 38 |
39 | 40 |
41 | )} 42 |
43 | ) 44 | 45 | export { ResizablePanelGroup, ResizablePanel, ResizableHandle } 46 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" 5 | 6 | import { cn } from "./cn" 7 | 8 | const ScrollArea = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 17 | 18 | {children} 19 | 20 | 21 | 22 | 23 | )) 24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName 25 | 26 | const ScrollBar = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, orientation = "vertical", ...props }, ref) => ( 30 | 43 | 44 | 45 | )) 46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName 47 | 48 | export { ScrollArea, ScrollBar } 49 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 5 | 6 | import { cn } from "./cn" 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >( 12 | ( 13 | { className, orientation = "horizontal", decorative = true, ...props }, 14 | ref 15 | ) => ( 16 | 27 | ) 28 | ) 29 | Separator.displayName = SeparatorPrimitive.Root.displayName 30 | 31 | export { Separator } 32 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "./cn" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/slider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SliderPrimitive from "@radix-ui/react-slider" 5 | 6 | import { cn } from "./cn" 7 | 8 | const Slider = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 21 | 22 | 23 | 24 | 25 | )) 26 | Slider.displayName = SliderPrimitive.Root.displayName 27 | 28 | export { Slider } 29 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useTheme } from "next-themes" 4 | import { Toaster as Sonner } from "sonner" 5 | 6 | type ToasterProps = React.ComponentProps 7 | 8 | const Toaster = ({ ...props }: ToasterProps) => { 9 | const { theme = "system" } = useTheme() 10 | 11 | return ( 12 | 28 | ) 29 | } 30 | 31 | export { Toaster } 32 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SwitchPrimitives from "@radix-ui/react-switch" 5 | 6 | import { cn } from "./cn" 7 | 8 | const Switch = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 25 | 26 | )) 27 | Switch.displayName = SwitchPrimitives.Root.displayName 28 | 29 | export { Switch } 30 | -------------------------------------------------------------------------------- /desktop/src/renderer/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "./cn" 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |