├── .env.example ├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── rowboat-build.yml ├── .gitignore ├── Dockerfile.qdrant ├── LICENSE ├── README.md ├── apps ├── .gitignore ├── copilot │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── app.py │ ├── client.py │ ├── copilot.py │ ├── copilot_edit_agent.md │ ├── copilot_multi_agent.md │ ├── current_workflow.md │ ├── example_multi_agent_1.md │ ├── lib.py │ ├── requirements.txt │ └── streaming.py ├── docs │ ├── .gitignore │ ├── Dockerfile │ ├── docs │ │ ├── CNAME │ │ ├── add_tools.md │ │ ├── agents.md │ │ ├── create_agents.md │ │ ├── data_sources.md │ │ ├── graph.md │ │ ├── hosted_setup.md │ │ ├── img │ │ │ ├── add-mcp-server.png │ │ │ ├── add-tool.png │ │ │ ├── agent-config.png │ │ │ ├── agent-instruction.png │ │ │ ├── chat-delivery.png │ │ │ ├── copilot-clarifications.png │ │ │ ├── copilot-create.png │ │ │ ├── create-agents-delivery.png │ │ │ ├── debug-tool-calls.png │ │ │ ├── dev-config.png │ │ │ ├── edit-agent-manually.png │ │ │ ├── example-tool.png │ │ │ ├── favicon.ico │ │ │ ├── hub-config.png │ │ │ ├── import-mcp-tools.png │ │ │ ├── inspect-agent-tool-connections.png │ │ │ ├── inspect-agent-tools.png │ │ │ ├── mock-response.png │ │ │ ├── mock-tool-responses.png │ │ │ ├── mock-tool.png │ │ │ ├── prod-deploy.png │ │ │ ├── project-page.png │ │ │ ├── re-test-chat.png │ │ │ ├── scenarios.png │ │ │ ├── simulate.png │ │ │ ├── start-agent.png │ │ │ ├── sys-msg.png │ │ │ ├── test-updated-agent.png │ │ │ ├── testing-chat.png │ │ │ ├── try-chat.png │ │ │ ├── update-agent-copilot.png │ │ │ ├── update-agent-generate.png │ │ │ ├── update-agent-manual.png │ │ │ ├── update-agent-with-copilot.png │ │ │ ├── update-agent.png │ │ │ └── use-copilot.png │ │ ├── index.md │ │ ├── license.md │ │ ├── oss_installation.md │ │ ├── playground.md │ │ ├── prompts.md │ │ ├── setup.md │ │ ├── simulate.md │ │ ├── studio_overview.md │ │ ├── testing.md │ │ ├── tools.md │ │ ├── update_agents.md │ │ ├── using_rag.md │ │ ├── using_the_api.md │ │ └── using_the_sdk.md │ ├── mkdocs.yml │ ├── readme.md │ └── requirements.txt ├── experimental │ ├── chat_widget │ │ ├── .dockerignore │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── app │ │ │ ├── api │ │ │ │ └── bootstrap.js │ │ │ │ │ └── route.ts │ │ │ ├── app.tsx │ │ │ ├── favicon.ico │ │ │ ├── fonts │ │ │ │ ├── GeistMonoVF.woff │ │ │ │ └── GeistVF.woff │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ ├── markdown-content.tsx │ │ │ ├── page.tsx │ │ │ └── providers.tsx │ │ ├── next.config.mjs │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── postcss.config.mjs │ │ ├── public │ │ │ ├── bootstrap.template.js │ │ │ ├── file.svg │ │ │ ├── globe.svg │ │ │ ├── next.svg │ │ │ ├── vercel.svg │ │ │ └── window.svg │ │ ├── tailwind.config.ts │ │ └── tsconfig.json │ ├── simulation_runner │ │ ├── Dockerfile │ │ ├── __init__.py │ │ ├── db.py │ │ ├── requirements.txt │ │ ├── scenario_types.py │ │ ├── service.py │ │ └── simulation.py │ ├── tools_webhook │ │ ├── Dockerfile │ │ ├── __init__.py │ │ ├── app.py │ │ ├── function_map.py │ │ ├── requirements.txt │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_app.py │ │ │ └── test_tool_caller.py │ │ └── tool_caller.py │ └── twilio_handler │ │ ├── .dockerignore │ │ ├── .env.example │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── app.py │ │ ├── load_env.py │ │ ├── requirements.txt │ │ ├── twilio_api.py │ │ └── util.py ├── python-sdk │ ├── .gitignore │ ├── README.md │ ├── pyproject.toml │ ├── requirements.txt │ └── src │ │ └── rowboat │ │ ├── __init__.py │ │ ├── client.py │ │ └── schema.py ├── rowboat │ ├── .dockerignore │ ├── .eslintrc.json │ ├── .gitignore │ ├── Dockerfile │ ├── README.md │ ├── app │ │ ├── actions │ │ │ ├── actions.ts │ │ │ ├── copilot_actions.ts │ │ │ ├── datasource_actions.ts │ │ │ ├── mcp_actions.ts │ │ │ ├── project_actions.ts │ │ │ ├── testing_actions.ts │ │ │ ├── voice_actions.ts │ │ │ └── workflow_actions.ts │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── api │ │ │ ├── auth │ │ │ │ └── [auth0] │ │ │ │ │ └── route.ts │ │ │ ├── copilot-stream-response │ │ │ │ └── [streamId] │ │ │ │ │ └── route.ts │ │ │ ├── stream-response │ │ │ │ └── [streamId] │ │ │ │ │ └── route.ts │ │ │ ├── uploads │ │ │ │ └── [fileId] │ │ │ │ │ └── route.ts │ │ │ ├── v1 │ │ │ │ ├── [projectId] │ │ │ │ │ └── chat │ │ │ │ │ │ └── route.ts │ │ │ │ └── utils.ts │ │ │ └── widget │ │ │ │ └── v1 │ │ │ │ ├── chats │ │ │ │ ├── [chatId] │ │ │ │ │ ├── close │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── messages │ │ │ │ │ │ └── route.ts │ │ │ │ │ ├── route.ts │ │ │ │ │ └── turn │ │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ │ ├── session │ │ │ │ ├── guest │ │ │ │ │ └── route.ts │ │ │ │ └── user │ │ │ │ │ └── route.ts │ │ │ │ └── utils.ts │ │ ├── app.tsx │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.tsx │ │ ├── lib │ │ │ ├── client_utils.ts │ │ │ ├── components │ │ │ │ ├── atmentions.ts │ │ │ │ ├── datasource-icon.tsx │ │ │ │ ├── dropdown.tsx │ │ │ │ ├── editable-field-with-immediate-save.tsx │ │ │ │ ├── editable-field.tsx │ │ │ │ ├── form-section.tsx │ │ │ │ ├── form-status-button-old.tsx │ │ │ │ ├── form-status-button.tsx │ │ │ │ ├── icons.tsx │ │ │ │ ├── label.tsx │ │ │ │ ├── markdown-content.tsx │ │ │ │ ├── mentions-editor.css │ │ │ │ ├── mentions_editor.tsx │ │ │ │ ├── menu-item.tsx │ │ │ │ ├── page-section.tsx │ │ │ │ ├── pagination.tsx │ │ │ │ ├── structured-list.tsx │ │ │ │ ├── structured-panel.tsx │ │ │ │ ├── typewriter.tsx │ │ │ │ └── user_button.tsx │ │ │ ├── embedding.ts │ │ │ ├── feature_flags.ts │ │ │ ├── loadenv.ts │ │ │ ├── mongodb.ts │ │ │ ├── project_templates.ts │ │ │ ├── qdrant.ts │ │ │ ├── rate_limiting.ts │ │ │ ├── redis.ts │ │ │ ├── types │ │ │ │ ├── agents_api_types.ts │ │ │ │ ├── copilot_types.ts │ │ │ │ ├── datasource_types.ts │ │ │ │ ├── project_types.ts │ │ │ │ ├── testing_types.ts │ │ │ │ ├── tool_types.ts │ │ │ │ ├── types.ts │ │ │ │ ├── voice_types.ts │ │ │ │ └── workflow_types.ts │ │ │ ├── uploads_s3_client.ts │ │ │ └── utils.ts │ │ ├── loading.tsx │ │ ├── new-chat-link.tsx │ │ ├── page.tsx │ │ ├── projects │ │ │ ├── [projectId] │ │ │ │ ├── config │ │ │ │ │ ├── app.tsx │ │ │ │ │ ├── components │ │ │ │ │ │ ├── project.tsx │ │ │ │ │ │ ├── shared-styles.ts │ │ │ │ │ │ ├── tools.tsx │ │ │ │ │ │ └── voice.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── copilot │ │ │ │ │ ├── app.tsx │ │ │ │ │ ├── components │ │ │ │ │ │ ├── actions.tsx │ │ │ │ │ │ └── messages.tsx │ │ │ │ │ ├── example.md │ │ │ │ │ ├── use-copilot.tsx │ │ │ │ │ └── use-parsed-blocks.tsx │ │ │ │ ├── entities │ │ │ │ │ ├── agent_config.tsx │ │ │ │ │ ├── prompt_config.tsx │ │ │ │ │ └── tool_config.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── playground │ │ │ │ │ ├── app.tsx │ │ │ │ │ └── components │ │ │ │ │ │ ├── chat.tsx │ │ │ │ │ │ ├── messages.tsx │ │ │ │ │ │ └── profile-context-box.tsx │ │ │ │ ├── sources │ │ │ │ │ ├── [sourceId] │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ └── source-page.tsx │ │ │ │ │ ├── components │ │ │ │ │ │ ├── delete.tsx │ │ │ │ │ │ ├── files-source.tsx │ │ │ │ │ │ ├── scrape-source.tsx │ │ │ │ │ │ ├── section.tsx │ │ │ │ │ │ ├── self-updating-source-status.tsx │ │ │ │ │ │ ├── shared.tsx │ │ │ │ │ │ ├── source-status.tsx │ │ │ │ │ │ ├── sources-list.tsx │ │ │ │ │ │ ├── text-source.tsx │ │ │ │ │ │ ├── toggle-source.tsx │ │ │ │ │ │ └── web-recrawl.tsx │ │ │ │ │ ├── new │ │ │ │ │ │ ├── form.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── test │ │ │ │ │ └── [[...slug]] │ │ │ │ │ │ ├── app.tsx │ │ │ │ │ │ ├── components │ │ │ │ │ │ ├── item-view.tsx │ │ │ │ │ │ ├── profile-form.tsx │ │ │ │ │ │ ├── scenario-form.tsx │ │ │ │ │ │ ├── selectors │ │ │ │ │ │ │ ├── profile-selector.tsx │ │ │ │ │ │ │ ├── scenario-selector.tsx │ │ │ │ │ │ │ ├── simulation-selector.tsx │ │ │ │ │ │ │ └── workflow-selector.tsx │ │ │ │ │ │ ├── simulation-form.tsx │ │ │ │ │ │ └── table.tsx │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ ├── profiles_app.tsx │ │ │ │ │ │ ├── runs_app.tsx │ │ │ │ │ │ ├── scenarios_app.tsx │ │ │ │ │ │ ├── simulations_app.tsx │ │ │ │ │ │ ├── testing_menu.tsx │ │ │ │ │ │ └── utils │ │ │ │ │ │ └── date.ts │ │ │ │ └── workflow │ │ │ │ │ ├── app.tsx │ │ │ │ │ ├── config_list.tsx │ │ │ │ │ ├── entity_list.tsx │ │ │ │ │ ├── mcp_imports.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ ├── pane.tsx │ │ │ │ │ ├── preview-modal.tsx │ │ │ │ │ ├── published_badge.tsx │ │ │ │ │ ├── workflow_editor.tsx │ │ │ │ │ └── workflow_selector.tsx │ │ │ ├── layout.tsx │ │ │ ├── layout │ │ │ │ ├── components │ │ │ │ │ ├── app-layout.tsx │ │ │ │ │ ├── menu-item.tsx │ │ │ │ │ └── sidebar.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── menu.tsx │ │ │ │ └── nav.tsx │ │ │ ├── page.tsx │ │ │ └── select │ │ │ │ ├── app.tsx │ │ │ │ ├── components │ │ │ │ ├── create-project.tsx │ │ │ │ ├── custom-prompt-card.tsx │ │ │ │ ├── project-card.tsx │ │ │ │ ├── project-list.tsx │ │ │ │ ├── search-input.tsx │ │ │ │ ├── search-projects.tsx │ │ │ │ └── submit-button.tsx │ │ │ │ └── page.tsx │ │ ├── providers.tsx │ │ ├── providers │ │ │ ├── help-modal-provider.tsx │ │ │ └── theme-provider.tsx │ │ ├── safari-pinned-tab.svg │ │ ├── scripts │ │ │ ├── delete_qdrant.ts │ │ │ ├── rag_files_worker.ts │ │ │ ├── rag_text_worker.ts │ │ │ ├── rag_urls_worker.ts │ │ │ └── setup_qdrant.ts │ │ ├── site.webmanifest │ │ └── styles │ │ │ ├── design-tokens.ts │ │ │ ├── pane-effects.ts │ │ │ └── quill-mentions.css │ ├── components.json │ ├── components │ │ ├── common │ │ │ ├── compose-box-copilot.tsx │ │ │ ├── compose-box-playground.tsx │ │ │ ├── compose-box.tsx │ │ │ ├── copy-as-json-button.tsx │ │ │ ├── copy-button.tsx │ │ │ ├── help-modal.tsx │ │ │ ├── panel-common.tsx │ │ │ └── product-tour.tsx │ │ └── ui │ │ │ ├── button.tsx │ │ │ ├── dropdown.tsx │ │ │ ├── horizontal-divider.tsx │ │ │ ├── input.tsx │ │ │ ├── page-heading.tsx │ │ │ ├── resizable.tsx │ │ │ ├── search-bar.tsx │ │ │ ├── section-heading.tsx │ │ │ └── textarea.tsx │ ├── hooks │ │ └── use-click-away.ts │ ├── lib │ │ ├── utils.ts │ │ └── utils │ │ │ └── date.ts │ ├── middleware.ts │ ├── next.config.mjs │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.mjs │ ├── public │ │ ├── dark-logo-only.png │ │ ├── dark-logo.png │ │ ├── landing-bg.jpg │ │ ├── logo-only.png │ │ └── logo.png │ ├── scripts.Dockerfile │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── types │ │ └── project_types.ts └── rowboat_agents │ ├── .dockerignore │ ├── .env.example │ ├── .gitignore │ ├── Dockerfile │ ├── NOTICE.md │ ├── README.md │ ├── __init__.py │ ├── configs │ └── default_config.json │ ├── poetry.lock │ ├── pyproject.toml │ ├── requirements.txt │ ├── src │ ├── __init__.py │ ├── app │ │ ├── __init__.py │ │ └── main.py │ ├── graph │ │ ├── __init__.py │ │ ├── core.py │ │ ├── execute_turn.py │ │ ├── guardrails.py │ │ ├── helpers │ │ │ ├── access.py │ │ │ ├── control.py │ │ │ ├── instructions.py │ │ │ ├── library_tools.py │ │ │ ├── state.py │ │ │ └── transfer.py │ │ ├── instructions.py │ │ ├── tool_calling.py │ │ ├── tools.py │ │ ├── tracing.py │ │ └── types.py │ └── utils │ │ ├── __init__.py │ │ ├── client.py │ │ └── common.py │ └── tests │ ├── __init__.py │ ├── app_client.py │ ├── app_client_streaming.py │ ├── interactive.py │ ├── sample_requests │ ├── default_example.json │ ├── example1.json │ ├── example2.json │ ├── example3.json │ ├── tmp1.json │ ├── tmp2.json │ ├── tmp3.json │ └── tmp4.json │ └── sample_responses │ └── default_example.json ├── assets ├── banner.png ├── mcp-import.png ├── rb-logo.png └── ui_revamp_screenshot.png ├── docker-compose.yml └── start.sh /.env.example: -------------------------------------------------------------------------------- 1 | # Basic configuration 2 | # ------------------------------------------------------------ 3 | OPENAI_API_KEY= 4 | 5 | 6 | # Uncomment to enable auth using Auth0 7 | # ------------------------------------------------------------ 8 | # USE_AUTH=true 9 | 10 | # Even though auth is disabled by default, these test values are needed for the auth0 imports 11 | # -------------------------------------------------------------------------------------------- 12 | AUTH0_SECRET=test_secret 13 | AUTH0_BASE_URL=http://localhost:3000 14 | AUTH0_ISSUER_BASE_URL=https://test.com 15 | AUTH0_CLIENT_ID=test 16 | AUTH0_CLIENT_SECRET=test 17 | 18 | # Uncomment to enable RAG: 19 | # ------------------------------------------------------------ 20 | # USE_RAG=true 21 | # QDRANT_URL= 22 | # QDRANT_API_KEY= 23 | 24 | # Uncomment to enable RAG: File uploads 25 | # ------------------------------------------------------------ 26 | # USE_RAG_UPLOADS=true 27 | # AWS_ACCESS_KEY_ID= 28 | # AWS_SECRET_ACCESS_KEY= 29 | # RAG_UPLOADS_S3_BUCKET= 30 | # RAG_UPLOADS_S3_REGION= 31 | 32 | # Uncomment to enable RAG: Scraping URLs 33 | # ------------------------------------------------------------ 34 | # USE_RAG_SCRAPING=true 35 | # FIRECRAWL_API_KEY= 36 | 37 | # Uncomment to enable chat widget 38 | # ------------------------------------------------------------ 39 | # USE_CHAT_WIDGET=true 40 | # CHAT_WIDGET_SESSION_JWT_SECRET= -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/.gitattributes -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: [main, master] 5 | 6 | permissions: 7 | contents: write 8 | pages: write 9 | 10 | # Prevent parallel deployments 11 | concurrency: 12 | group: pages-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | deploy: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: actions/setup-python@v5 22 | with: 23 | python-version: '3.x' 24 | cache: 'pip' 25 | cache-dependency-path: 'apps/docs/requirements.txt' 26 | 27 | - name: Install and Deploy 28 | working-directory: apps/docs 29 | run: | 30 | pip install -r requirements.txt 31 | git config user.name github-actions[bot] 32 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 33 | mkdocs gh-deploy --force -------------------------------------------------------------------------------- /.github/workflows/rowboat-build.yml: -------------------------------------------------------------------------------- 1 | name: Rowboat Next.js Build 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | build-rowboat-nextjs: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Setup Node.js 14 | uses: actions/setup-node@v4 15 | with: 16 | cache-dependency-path: 'apps/rowboat/package-lock.json' 17 | node-version: '20' 18 | cache: 'npm' 19 | 20 | - name: Install dependencies 21 | run: npm ci 22 | working-directory: apps/rowboat 23 | 24 | - name: Build Rowboat 25 | run: npm run build 26 | working-directory: apps/rowboat -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | .vscode/ 4 | data/ 5 | .venv/ 6 | -------------------------------------------------------------------------------- /Dockerfile.qdrant: -------------------------------------------------------------------------------- 1 | FROM qdrant/qdrant:latest 2 | 3 | RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /apps/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ -------------------------------------------------------------------------------- /apps/copilot/.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | venv/ -------------------------------------------------------------------------------- /apps/copilot/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | venv/ -------------------------------------------------------------------------------- /apps/copilot/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use official Python runtime as base image 2 | FROM python:3.11-slim 3 | 4 | # Set working directory in container 5 | WORKDIR /app 6 | 7 | # Copy requirements file 8 | COPY requirements.txt . 9 | 10 | # Install dependencies 11 | RUN pip install --no-cache-dir -r requirements.txt 12 | 13 | # Copy project files 14 | COPY . . 15 | 16 | # Expose port if your app needs it (adjust as needed) 17 | ENV FLASK_APP=app 18 | ENV PYTHONUNBUFFERED=1 19 | 20 | # Command to run Flask development server 21 | CMD ["flask", "run", "--host=0.0.0.0", "--port=3002"] 22 | -------------------------------------------------------------------------------- /apps/copilot/README.md: -------------------------------------------------------------------------------- 1 | # AI Workflow Copilot 2 | 3 | A Flask-based application that helps design and manage multi-agent AI systems for customer support. 4 | 5 | ## Prerequisites 6 | 7 | - Python 3.8+ 8 | - OpenAI API key 9 | 10 | ## Installation 11 | 12 | 1. Clone the repository: 13 | 2. Create and activate a virtual environment: 14 | ```bash 15 | python -m venv venv 16 | source venv/bin/activate # On Windows, use: venv\Scripts\activate 17 | ``` 18 | 19 | 3. Install required dependencies: 20 | ```bash 21 | pip install -r requirements.txt 22 | ``` 23 | 24 | 4. Set up your OpenAI API key: 25 | ```bash 26 | export OPENAI_API_KEY='your-api-key-here' # On Windows, use: set OPENAI_API_KEY=your-api-key-here 27 | export API_KEY='test-api-key' # set a shared API key for the application 28 | ``` 29 | 30 | ## Running the Application 31 | 32 | 1. Start the Flask server: 33 | ```bash 34 | python app.py 35 | ``` 36 | 37 | The server will start on `http://localhost:3002` 38 | 39 | ## API Usage 40 | 41 | The application exposes a single endpoint at `/chat` that accepts POST requests. 42 | 43 | ### Example Request: 44 | ```bash 45 | curl -X POST http://localhost:3002/chat \ 46 | -H "Content-Type: application/json" \ 47 | -H "Authorization: Bearer test-api-key" \ 48 | -d '{ 49 | "messages": [ 50 | { 51 | "role": "user", 52 | "content": "Your message here" 53 | } 54 | ], 55 | "workflow_schema": "Your workflow schema here", 56 | "current_workflow_config": "Your current workflow configuration here" 57 | }' 58 | ``` 59 | 60 | ### Example Response: 61 | ```json 62 | { 63 | "response": "Assistant's response here" 64 | } 65 | ``` 66 | 67 | ## Error Handling 68 | 69 | The API returns appropriate HTTP status codes: 70 | - 400: Invalid request format or data 71 | - 500: Internal server error 72 | 73 | ## Development 74 | 75 | To run the server in debug mode, ensure `debug=True` is set in `app.py` (already included). 76 | 77 | ## License 78 | 79 | [Add your license information here] -------------------------------------------------------------------------------- /apps/copilot/client.py: -------------------------------------------------------------------------------- 1 | import os 2 | from openai import OpenAI 3 | import dotenv 4 | dotenv.load_dotenv() 5 | 6 | PROVIDER_BASE_URL = os.getenv('PROVIDER_BASE_URL', '') 7 | PROVIDER_API_KEY = os.getenv('PROVIDER_API_KEY') 8 | PROVIDER_DEFAULT_MODEL = os.getenv('PROVIDER_DEFAULT_MODEL') 9 | PROVIDER_COPILOT_MODEL = os.getenv('PROVIDER_COPILOT_MODEL') 10 | 11 | if not PROVIDER_COPILOT_MODEL: 12 | PROVIDER_COPILOT_MODEL = 'gpt-4.1' 13 | 14 | if not PROVIDER_API_KEY: 15 | PROVIDER_API_KEY = os.getenv('OPENAI_API_KEY') 16 | 17 | if not PROVIDER_API_KEY: 18 | raise(ValueError("No LLM Provider API key found")) 19 | 20 | if not PROVIDER_DEFAULT_MODEL: 21 | PROVIDER_DEFAULT_MODEL = 'gpt-4.1' 22 | 23 | completions_client = None 24 | if PROVIDER_BASE_URL: 25 | print(f"Using provider {PROVIDER_BASE_URL}, for completions") 26 | completions_client = OpenAI( 27 | base_url=PROVIDER_BASE_URL, 28 | api_key=PROVIDER_API_KEY 29 | ) 30 | else: 31 | print(f"Using OpenAI directly for completions") 32 | completions_client = OpenAI( 33 | api_key=PROVIDER_API_KEY 34 | ) -------------------------------------------------------------------------------- /apps/copilot/copilot_edit_agent.md: -------------------------------------------------------------------------------- 1 | ## Role: 2 | You are a copilot that helps the user create edit agent instructions. 3 | 4 | ## Section 1 : Editing an Existing Agent 5 | 6 | When the user asks you to edit an existing agent, you should follow the steps below: 7 | 8 | 1. Understand the user's request. 9 | 3. Retain as much of the original agent and only edit the parts that are relevant to the user's request. 10 | 3. If needed, ask clarifying questions to the user. Keep that to one turn and keep it minimal. 11 | 4. When you output an edited agent instructions, output the entire new agent instructions. 12 | 13 | ## Section 8 : Creating New Agents 14 | 15 | When creating a new agent, strictly follow the format of this example agent. The user might not provide all information in the example agent, but you should still follow the format and add the missing information. 16 | 17 | example agent: 18 | ``` 19 | ## 🧑‍💼 Role: 20 | 21 | You are responsible for providing delivery information to the user. 22 | 23 | --- 24 | 25 | ## ⚙️ Steps to Follow: 26 | 27 | 1. Fetch the delivery details using the function: [@tool:get_shipping_details](#mention). 28 | 2. Answer the user's question based on the fetched delivery details. 29 | 3. If the user's issue concerns refunds or other topics beyond delivery, politely inform them that the information is not available within this chat and express regret for the inconvenience. 30 | 31 | --- 32 | ## 🎯 Scope: 33 | 34 | ✅ In Scope: 35 | - Questions about delivery status, shipping timelines, and delivery processes. 36 | - Generic delivery/shipping-related questions where answers can be sourced from articles. 37 | 38 | ❌ Out of Scope: 39 | - Questions unrelated to delivery or shipping. 40 | - Questions about products features, returns, subscriptions, or promotions. 41 | - If a question is out of scope, politely inform the user and avoid providing an answer. 42 | 43 | --- 44 | 45 | ## 📋 Guidelines: 46 | 47 | ✔️ Dos: 48 | - Use [@tool:get_shipping_details](#mention) to fetch accurate delivery information. 49 | - Provide complete and clear answers based on the delivery details. 50 | - For generic delivery questions, refer to relevant articles if necessary. 51 | - Stick to factual information when answering. 52 | 53 | 🚫 Don'ts: 54 | - Do not provide answers without fetching delivery details when required. 55 | - Do not leave the user with partial information. Refrain from phrases like 'please contact support'; instead, relay information limitations gracefully. 56 | ``` 57 | 58 | output format: 59 | ```json 60 | { 61 | "agent_instructions": "" 62 | } 63 | ``` 64 | """ -------------------------------------------------------------------------------- /apps/copilot/current_workflow.md: -------------------------------------------------------------------------------- 1 | ## Section: State of the Current Multi-Agent System 2 | 3 | The design of the multi-agent system is represented by the following JSON schema: 4 | 5 | ``` 6 | {workflow_schema} 7 | ``` 8 | 9 | If the workflow has an 'Example Agent' as the main agent, it means the user is yet to create the main agent. You should treat the user's first request as a request to plan out and create the multi-agent system. 10 | 11 | --- -------------------------------------------------------------------------------- /apps/copilot/lib.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from typing import Literal, List, Any 3 | 4 | class AgentContext(BaseModel): 5 | type: Literal['agent'] 6 | agentName: str 7 | 8 | class PromptContext(BaseModel): 9 | type: Literal['prompt'] 10 | promptName: str 11 | 12 | class ToolContext(BaseModel): 13 | type: Literal['tool'] 14 | toolName: str 15 | 16 | class ChatContext(BaseModel): 17 | type: Literal['chat'] 18 | messages: List[Any] -------------------------------------------------------------------------------- /apps/copilot/requirements.txt: -------------------------------------------------------------------------------- 1 | annotated-types==0.7.0 2 | anyio==4.7.0 3 | blinker==1.9.0 4 | certifi==2024.8.30 5 | click==8.1.7 6 | distro==1.9.0 7 | Flask==3.1.0 8 | gunicorn==23.0.0 9 | h11==0.14.0 10 | httpcore==1.0.7 11 | httpx==0.28.0 12 | idna==3.10 13 | itsdangerous==2.2.0 14 | Jinja2==3.1.4 15 | jiter==0.8.0 16 | MarkupSafe==3.0.2 17 | openai==1.61.0 18 | packaging==24.2 19 | pydantic==2.10.3 20 | pydantic_core==2.27.1 21 | python-dotenv 22 | sniffio==1.3.1 23 | tqdm==4.67.1 24 | typing_extensions==4.12.2 25 | Werkzeug==3.1.3 26 | -------------------------------------------------------------------------------- /apps/docs/.gitignore: -------------------------------------------------------------------------------- 1 | site/ 2 | -------------------------------------------------------------------------------- /apps/docs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt . 6 | 7 | RUN pip install -r requirements.txt 8 | 9 | COPY . . 10 | 11 | CMD ["mkdocs", "serve", "--dev-addr", "0.0.0.0:8000"] -------------------------------------------------------------------------------- /apps/docs/docs/CNAME: -------------------------------------------------------------------------------- 1 | docs.rowboatlabs.com -------------------------------------------------------------------------------- /apps/docs/docs/add_tools.md: -------------------------------------------------------------------------------- 1 | ## Add tools to agents 2 | Copilot can help you add tools to agents. You can (a) add a mock tool, (b) add a tool from an MCP server, (c) integrate with you own tools using a webhook. 3 | 4 | 5 | ### Adding mock tools 6 | You can mock any tool you have created by checking the 'Mock tool responses' option. 7 | 8 | 9 | ![Example Tool](img/mock-tool.png) 10 | 11 | ### Adding MCP tools 12 | 13 | You can add a running MCP server in Settings -> Tools. 14 | 15 | ![Example Tool](img/add-mcp-server.png) 16 | 17 | You can use [supergateway](https://github.com/supercorp-ai/supergateway) to expose any MCP stdio server as an SSE server. 18 | 19 | Now, you can import the tools from the MCP server in the Build view. 20 | 21 | ![Example Tool](img/import-mcp-tools.png) 22 | 23 | 24 | ### Debug tool calls in the playground 25 | When agents call tools during a chat in the playground, the tool call parameters and response are available for debugging real-time. For testing purposes, the platform can produce mock tool responses in the playground, without integrating actual tools. 26 | 27 | ![Mock Tool Responses](img/mock-response.png) -------------------------------------------------------------------------------- /apps/docs/docs/agents.md: -------------------------------------------------------------------------------- 1 | # Agents 2 | 3 | ## Overview 4 | - Agents carry out a specific part of the conversation and / or perform tasks like orchestrating between other agents, triggering internal processes and fetching information. 5 | - Agents carry out tasks through tools provided to them. 6 | - Agents can be connected to other agents through a mention in the agent's instruction. 7 | 8 | ## Agent Configurations 9 | 10 | ### Description 11 | The description conveys the agent's role in the multi-agent system. Writing a good description is important for other agents to know when to pass control of the conversation to an agent. 12 | 13 | ### Instructions 14 | Agent instructions are the backbone of an agent, defining its behavior. RowBoat Studio's copilot produces a good framework for agent instructions, involving Role, Steps to Follow, Scope and Guidelines. Since agents are powered by LLMs, general best practices while writing prompts apply. 15 | 16 | ### Examples 17 | The agent uses examples as a reference for behavior in different scenarios. While there are no prescribed formats to provide examples in, examples should include what the user might say, what the agent should respond with as well as indications of any tool calls to be made. 18 | 19 | ### Prompts 20 | Prompts attached to an agent will be used by the agent in addition to instructions. 21 | 22 | ### Tools 23 | Tools attached to an agent will be put out as tool calls. The behavior of when to invoke tools can be fine-tuned by specifying corresponding instructions or prompts. Adding examples to agents can also be useful in controlling tool call behavior. 24 | 25 | ### Connected Agents 26 | In the agent instructions, the connected agents are shown with an '@mention'. If the agent mentioned in an instruction (connected agent) does not actually exist, the connected agent's name would show up with an '!' to call to attention. 27 | 28 | ### Model 29 | RowBoat currently supports OpenAI LLMs. Agents can be configured to use GPT-4o or GPT-4o-mini. -------------------------------------------------------------------------------- /apps/docs/docs/create_agents.md: -------------------------------------------------------------------------------- 1 | ## Create the set of initial agents 2 | Copilot can set up agents for you from scratch. 3 | 4 | ### Instruct copilot 5 | First, tell it about the initial set of agents that make up your assistant. 6 | 7 | ![Agent Config](img/create-agents-delivery.png) 8 | 9 | Using copilot to create your initial set of agents helps you leverage best practices in formatting agent instructions and connecting agents to each other as a graph, all of which have been baked into copilot. 10 | 11 | ### Inspect the agents 12 | Once you apply changes, inspect the agents to see how copilot has built them. Specifically, note the Instructions, and Examples in each agent. 13 | 14 | ![Agent Config](img/agent-instruction.png) 15 | -------------------------------------------------------------------------------- /apps/docs/docs/data_sources.md: -------------------------------------------------------------------------------- 1 | Coming soon. -------------------------------------------------------------------------------- /apps/docs/docs/graph.md: -------------------------------------------------------------------------------- 1 | # Graph-based Framework 2 | 3 | ## Overview 4 | 5 | - Multi-agent systems are popularly represented as graphs, where each agent is a node in the graph. 6 | - In RowBoat, agents are connected to each other as Directed Acyclic Graphs (DAG). 7 | - The graph is also called a workflow, which defines agents, tools, and their connections. 8 | - Since the graph is directed, the control of conversation flows from "parent" agents to their "children" agents 9 | - Every agent is responsible for carrying out a specific part of the workflow, which can involve conversing with the user and / or carrying out tasks such as directing the conversation to other agents. 10 | - [Langgraph](https://www.langchain.com/langgraph) and [Swarm](https://github.com/openai/swarm) are examples of open-source frameworks used to define multi-agent graphs. RowBoat currently supports a Swarm implementation and will extend to Langgraph too in the future. 11 | 12 | ## Control Passing 13 | 14 | - While defining the workflow, an agent is designated as the Start agent, to which the first turn of chat will be directed. Typically the Start agent is responsible for triaging the user's query at a high-level and passing control to relevant specific agents which can address the user's needs. 15 | - In any turn of chat, the agent currently in control of the chat has one of 3 options: a) respond to the user (or put out tool calls), b) transfer the chat to any of its children agents or c) transfer the chat back to its parent agent. 16 | - Agents use internal tool calls to transfer the chat to other agents. 17 | - Thus, control passing is achieved by allowing agents to decide flow of control autonomously. 18 | - To the user, the assistant will appear as a unified system, while agents work under the hood. 19 | 20 | ## Pipelines 21 | 22 | - RowBoat also has the concept of pipelines - specialized agents invoked sequentially after an agent in the graph has produced a user-facing response. 23 | - E.g. a pipeline with a post processing agent and a guardrail agent will ensure that every response is post processed and guardrailed for appropriateness before presenting it to the user. 24 | -------------------------------------------------------------------------------- /apps/docs/docs/img/add-mcp-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/add-mcp-server.png -------------------------------------------------------------------------------- /apps/docs/docs/img/add-tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/add-tool.png -------------------------------------------------------------------------------- /apps/docs/docs/img/agent-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/agent-config.png -------------------------------------------------------------------------------- /apps/docs/docs/img/agent-instruction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/agent-instruction.png -------------------------------------------------------------------------------- /apps/docs/docs/img/chat-delivery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/chat-delivery.png -------------------------------------------------------------------------------- /apps/docs/docs/img/copilot-clarifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/copilot-clarifications.png -------------------------------------------------------------------------------- /apps/docs/docs/img/copilot-create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/copilot-create.png -------------------------------------------------------------------------------- /apps/docs/docs/img/create-agents-delivery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/create-agents-delivery.png -------------------------------------------------------------------------------- /apps/docs/docs/img/debug-tool-calls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/debug-tool-calls.png -------------------------------------------------------------------------------- /apps/docs/docs/img/dev-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/dev-config.png -------------------------------------------------------------------------------- /apps/docs/docs/img/edit-agent-manually.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/edit-agent-manually.png -------------------------------------------------------------------------------- /apps/docs/docs/img/example-tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/example-tool.png -------------------------------------------------------------------------------- /apps/docs/docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/favicon.ico -------------------------------------------------------------------------------- /apps/docs/docs/img/hub-config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/hub-config.png -------------------------------------------------------------------------------- /apps/docs/docs/img/import-mcp-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/import-mcp-tools.png -------------------------------------------------------------------------------- /apps/docs/docs/img/inspect-agent-tool-connections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/inspect-agent-tool-connections.png -------------------------------------------------------------------------------- /apps/docs/docs/img/inspect-agent-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/inspect-agent-tools.png -------------------------------------------------------------------------------- /apps/docs/docs/img/mock-response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/mock-response.png -------------------------------------------------------------------------------- /apps/docs/docs/img/mock-tool-responses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/mock-tool-responses.png -------------------------------------------------------------------------------- /apps/docs/docs/img/mock-tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/mock-tool.png -------------------------------------------------------------------------------- /apps/docs/docs/img/prod-deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/prod-deploy.png -------------------------------------------------------------------------------- /apps/docs/docs/img/project-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/project-page.png -------------------------------------------------------------------------------- /apps/docs/docs/img/re-test-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/re-test-chat.png -------------------------------------------------------------------------------- /apps/docs/docs/img/scenarios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/scenarios.png -------------------------------------------------------------------------------- /apps/docs/docs/img/simulate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/simulate.png -------------------------------------------------------------------------------- /apps/docs/docs/img/start-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/start-agent.png -------------------------------------------------------------------------------- /apps/docs/docs/img/sys-msg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/sys-msg.png -------------------------------------------------------------------------------- /apps/docs/docs/img/test-updated-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/test-updated-agent.png -------------------------------------------------------------------------------- /apps/docs/docs/img/testing-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/testing-chat.png -------------------------------------------------------------------------------- /apps/docs/docs/img/try-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/try-chat.png -------------------------------------------------------------------------------- /apps/docs/docs/img/update-agent-copilot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/update-agent-copilot.png -------------------------------------------------------------------------------- /apps/docs/docs/img/update-agent-generate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/update-agent-generate.png -------------------------------------------------------------------------------- /apps/docs/docs/img/update-agent-manual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/update-agent-manual.png -------------------------------------------------------------------------------- /apps/docs/docs/img/update-agent-with-copilot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/update-agent-with-copilot.png -------------------------------------------------------------------------------- /apps/docs/docs/img/update-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/update-agent.png -------------------------------------------------------------------------------- /apps/docs/docs/img/use-copilot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/docs/docs/img/use-copilot.png -------------------------------------------------------------------------------- /apps/docs/docs/oss_installation.md: -------------------------------------------------------------------------------- 1 | # Open Source Installation 2 | 3 | - This is the developers guide to self-hosting the open-source version of RowBoat. To get started with the hosted app, please see [Using the Hosted App](/hosted_setup) 4 | - Please see our [Introduction](/) page before referring to this guide. 5 | - For direct installation steps, please head to the README of RowBoat's Github repo: [@rowboatlabs/rowboat](https://github.com/rowboatlabs/rowboat/). This page provides more context about the installation process and the different components involved. 6 | 7 | ## Overview 8 | 9 | RowBoat's codebase has three main components: 10 | 11 | | Component | Description | 12 | |--------------|---------------| 13 | | **Agents** | Python framework responsible for carrying out multi-agent conversations | 14 | | **Copilot** | Python framework powering the copilot in RowBoat Studio | 15 | | **RowBoat** | Frontend and backend services to power RowBoat Studio and Chat APIs | 16 | 17 | These components are structured as separate services, each containerized with Docker. Running `docker-compose up --build` enables you to use the Studio in your browser, as well as stands up the APIs and SDK. 18 | 19 | ## Prerequisites 20 | All of these prerequisites have open-source or free versions. 21 | 22 | | Prerequisite | Description | 23 | |--------------|---------------| 24 | | **Docker** | Bundles and builds all services | 25 | | **OpenAI API Key** | Agents and Copilot services are powered by OpenAI LLMs | 26 | | **MongoDB** | Stores workflow versions, chats and RAG embeddings | 27 | 28 | Refer to our [Github Readme for Prerequisites](https://github.com/rowboatlabs/rowboat/?tab=readme-ov-file#prerequisites) to set up prerequisites. 29 | 30 | ## Setting up 31 | 32 | Refer to our [Github Readme for Local Development](https://github.com/rowboatlabs/rowboat/?tab=readme-ov-file#local-development-setup) to set up Studio, Chat API and SDK via `docker-compose`. -------------------------------------------------------------------------------- /apps/docs/docs/playground.md: -------------------------------------------------------------------------------- 1 | ## Try an example chat in the playground 2 | 3 | ### Chat with the assistant 4 | 5 | The playground is intended to test out the assistant as you build it. The User and Assistant messages represent the conversation that your end-user will have if your assistant is deployed in production. The playground also has debug elements which show the flow of control between different agents in your system, as well as which agent finally responded to the user. 6 | 7 | ![Try Chat](img/chat-delivery.png) -------------------------------------------------------------------------------- /apps/docs/docs/prompts.md: -------------------------------------------------------------------------------- 1 | # Prompts 2 | 3 | - Prompts are reusable pieces of agent instructions in Studio. 4 | - Prompts can be defined once and reused across multiple agents. 5 | - Common examples of prompts are style prompts which indicate brand voice and structured output prompts which specify a format for the agent to provide its output in (e.g. ReAct) -------------------------------------------------------------------------------- /apps/docs/docs/simulate.md: -------------------------------------------------------------------------------- 1 | ## Simulate real-world user scenarios 2 | Create a test-bench of real-world scenarios in the simulator. 3 | ![Scenarios](img/scenarios.png) 4 | 5 | Run the scenarios as simulated chats betweeen a user (role-played) and the assistant, in the playground. 6 | ![Simulation](img/simulate.png) -------------------------------------------------------------------------------- /apps/docs/docs/studio_overview.md: -------------------------------------------------------------------------------- 1 | # Building Assistants in Studio 2 | This is a guide to building your first assistant on RowBoat Studio, with examples.
3 | 4 | Prerequisite: 5 | 6 | 1. **Open Source Users:** Complete the [open-source installation steps](/oss_installation/) to set up RowBoat Studio. 7 | 2. **Hosted App Users:** Sign in to [https://app.rowboatlabs.com/](https://app.rowboatlabs.com/) -------------------------------------------------------------------------------- /apps/docs/docs/tools.md: -------------------------------------------------------------------------------- 1 | # Tools 2 | - Tools are used to carry out specific tasks such as fetching or updating information. 3 | - Tools can be defined once in RowBoat Studio and reused across different agents. 4 | - RowBoat uses OpenAI style tools with name, description and parameters. 5 | - For the purposes of quick testing in the Playground, RowBoat Studio can mock tool responses based on tool descriptions. 6 | - Developers can easily connect tools to APIs by configuring MCP servers or Webhook URL in Settings. -------------------------------------------------------------------------------- /apps/docs/docs/update_agents.md: -------------------------------------------------------------------------------- 1 | ## Update agent behavior 2 | 3 | There are three ways for you to update the agent's behavior: 4 | 5 | ### 1. With help of Copilot 6 | 7 | Copilot can help you update agent behavior. It is also aware of the current chat in the playground so you can make references to the current chat while instructing copilot to update agents. 8 | 9 | ![Update Agent Behavior](img/update-agent-copilot.png) 10 | 11 | ### 2. Using the Generate button 12 | 13 | ![Update Agent Behavior](img/update-agent-generate.png) 14 | 15 | ### 3. By manually editing the instructions 16 | 17 | You can manually edit the agent instructions anytime. 18 | 19 | ![Update Agent Behavior](img/update-agent-manual.png) 20 | -------------------------------------------------------------------------------- /apps/docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: RowBoat docs 2 | site_url: https://docs.rowboatlabs.com 3 | theme: 4 | name: material 5 | favicon: img/favicon.ico 6 | nav: 7 | - Getting Started: setup.md 8 | - Overview: 9 | - Introduction: index.md 10 | - Open Source License: license.md 11 | 12 | - Building in Studio: 13 | - Create agents: create_agents.md 14 | - Test chats in the playground: playground.md 15 | - Add tools: add_tools.md 16 | - Update agents: update_agents.md 17 | - Using RAG: using_rag.md 18 | 19 | - API & SDK: 20 | - Using the API: using_the_api.md 21 | - Using the SDK: using_the_sdk.md 22 | 23 | - Concepts: 24 | - Agents: agents.md 25 | - Tools: tools.md 26 | - Prompts: prompts.md 27 | -------------------------------------------------------------------------------- /apps/docs/readme.md: -------------------------------------------------------------------------------- 1 | # Documentation Site 2 | 3 | This documentation site is built using [MkDocs Material](https://squidfunk.github.io/mkdocs-material/), a modern documentation framework that creates beautiful and functional static sites. 4 | 5 | ## Prerequisites 6 | 7 | - Python 3.x 8 | - pip (Python package manager) 9 | 10 | ## Setup 11 | 12 | Install the required dependencies: 13 | 14 | ```bash 15 | pip install -r requirements.txt 16 | ``` 17 | 18 | ## Development 19 | 20 | To run the documentation site locally: 21 | 22 | ```bash 23 | mkdocs serve 24 | ``` 25 | 26 | This will start a local server, and you can view the documentation site at `http://localhost:8000`. 27 | 28 | ### Building the static site 29 | 30 | To build the static site: 31 | 32 | ```bash 33 | mkdocs build 34 | ``` 35 | 36 | This will generate the static site in the `site` directory. 37 | 38 | ### Project structure 39 | 40 | - `mkdocs.yml`: The main configuration file for MkDocs. 41 | - `docs/`: The directory containing the Markdown files for the documentation. 42 | - `site/`: The directory generated by the `mkdocs build` command, containing the static site files. 43 | 44 | ## Writing Documentation 45 | 46 | - Documentation files are written in Markdown format 47 | - Place new documentation files in the `docs` directory 48 | - Update `mkdocs.yml` to include new pages in the navigation 49 | 50 | ## Additional Resources 51 | 52 | - [Mkdocs documentation](https://www.mkdocs.org/getting-started/) 53 | - [MkDocs Material Documentation](https://squidfunk.github.io/mkdocs-material/getting-started/) 54 | - [Markdown Guide](https://www.markdownguide.org/basic-syntax/) 55 | 56 | -------------------------------------------------------------------------------- /apps/docs/requirements.txt: -------------------------------------------------------------------------------- 1 | babel==2.16.0 2 | certifi==2024.12.14 3 | charset-normalizer==3.4.1 4 | click==8.1.8 5 | colorama==0.4.6 6 | ghp-import==2.1.0 7 | idna==3.10 8 | Jinja2==3.1.5 9 | Markdown==3.7 10 | MarkupSafe==3.0.2 11 | mergedeep==1.3.4 12 | mkdocs==1.6.1 13 | mkdocs-get-deps==0.2.0 14 | mkdocs-material==9.5.50 15 | mkdocs-material-extensions==1.3.1 16 | packaging==24.2 17 | paginate==0.5.7 18 | pathspec==0.12.1 19 | platformdirs==4.3.6 20 | Pygments==2.19.1 21 | pymdown-extensions==10.14.1 22 | python-dateutil==2.9.0.post0 23 | PyYAML==6.0.2 24 | pyyaml_env_tag==0.1 25 | regex==2024.11.6 26 | requests==2.32.3 27 | six==1.17.0 28 | urllib3==2.3.0 29 | watchdog==6.0.0 30 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .dockerignore 3 | node_modules 4 | npm-debug.log 5 | README.md 6 | .next 7 | .git 8 | .env* -------------------------------------------------------------------------------- /apps/experimental/chat_widget/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # env files (can opt-in for commiting if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker.io/docker/dockerfile:1 2 | 3 | FROM node:18-alpine AS base 4 | 5 | # Install dependencies only when needed 6 | FROM base AS deps 7 | # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. 8 | RUN apk add --no-cache libc6-compat 9 | WORKDIR /app 10 | 11 | # Install dependencies based on the preferred package manager 12 | COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./ 13 | RUN \ 14 | if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ 15 | elif [ -f package-lock.json ]; then npm ci; \ 16 | elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ 17 | else echo "Lockfile not found." && exit 1; \ 18 | fi 19 | 20 | 21 | # Rebuild the source code only when needed 22 | FROM base AS builder 23 | WORKDIR /app 24 | COPY --from=deps /app/node_modules ./node_modules 25 | COPY . . 26 | 27 | # Next.js collects completely anonymous telemetry data about general usage. 28 | # Learn more here: https://nextjs.org/telemetry 29 | # Uncomment the following line in case you want to disable telemetry during the build. 30 | # ENV NEXT_TELEMETRY_DISABLED=1 31 | 32 | RUN \ 33 | if [ -f yarn.lock ]; then yarn run build; \ 34 | elif [ -f package-lock.json ]; then npm run build; \ 35 | elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ 36 | else echo "Lockfile not found." && exit 1; \ 37 | fi 38 | 39 | # Production image, copy all the files and run next 40 | FROM base AS runner 41 | WORKDIR /app 42 | 43 | ENV NODE_ENV=production 44 | # Uncomment the following line in case you want to disable telemetry during runtime. 45 | # ENV NEXT_TELEMETRY_DISABLED=1 46 | 47 | RUN addgroup --system --gid 1001 nodejs 48 | RUN adduser --system --uid 1001 nextjs 49 | 50 | COPY --from=builder /app/public ./public 51 | 52 | # Automatically leverage output traces to reduce image size 53 | # https://nextjs.org/docs/advanced-features/output-file-tracing 54 | COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ 55 | COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static 56 | 57 | USER nextjs 58 | 59 | EXPOSE 3000 60 | 61 | ENV PORT=3000 62 | 63 | # server.js is created by next build from the standalone output 64 | # https://nextjs.org/docs/pages/api-reference/config/next-config-js/output 65 | ENV HOSTNAME="0.0.0.0" 66 | ENV PORT=3000 67 | CMD echo "Starting server $CHAT_WIDGET_HOST, $ROWBOAT_HOST" && node server.js 68 | #CMD ["node", "server.js"] -------------------------------------------------------------------------------- /apps/experimental/chat_widget/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/app/api/bootstrap.js/route.ts: -------------------------------------------------------------------------------- 1 | export const dynamic = 'force-dynamic' 2 | 3 | // Fetch template once when module loads 4 | const templatePromise = fetch(process.env.CHAT_WIDGET_HOST + '/bootstrap.template.js') 5 | .then(res => res.text()); 6 | 7 | export async function GET() { 8 | try { 9 | // Reuse the cached content 10 | const template = await templatePromise; 11 | 12 | // Replace placeholder values with actual URLs 13 | const contents = template 14 | .replace('__CHAT_WIDGET_HOST__', process.env.CHAT_WIDGET_HOST || '') 15 | .replace('__ROWBOAT_HOST__', process.env.ROWBOAT_HOST || ''); 16 | 17 | return new Response(contents, { 18 | headers: { 19 | 'Content-Type': 'application/javascript', 20 | 'Cache-Control': 'no-cache, no-store, must-revalidate', 21 | }, 22 | }); 23 | } catch (error) { 24 | console.error('Error serving bootstrap.js:', error); 25 | return new Response('Error loading script', { status: 500 }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/experimental/chat_widget/app/favicon.ico -------------------------------------------------------------------------------- /apps/experimental/chat_widget/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/experimental/chat_widget/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /apps/experimental/chat_widget/app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/experimental/chat_widget/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /apps/experimental/chat_widget/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | font-family: Arial, Helvetica, sans-serif; 7 | } 8 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import localFont from "next/font/local"; 3 | import "./globals.css"; 4 | 5 | const geistSans = localFont({ 6 | src: "./fonts/GeistVF.woff", 7 | variable: "--font-geist-sans", 8 | weight: "100 900", 9 | }); 10 | const geistMono = localFont({ 11 | src: "./fonts/GeistMonoVF.woff", 12 | variable: "--font-geist-mono", 13 | weight: "100 900", 14 | }); 15 | 16 | export const metadata: Metadata = { 17 | title: "RowBoat Chat", 18 | description: "RowBoat Chat", 19 | }; 20 | 21 | export default function RootLayout({ 22 | children, 23 | }: Readonly<{ 24 | children: React.ReactNode; 25 | }>) { 26 | return ( 27 | 28 | 31 | {children} 32 | 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/app/markdown-content.tsx: -------------------------------------------------------------------------------- 1 | import Markdown from 'react-markdown' 2 | import remarkGfm from 'remark-gfm' 3 | 4 | export default function MarkdownContent({ content }: { content: string }) { 5 | return {children} 11 | }, 12 | p({ children }) { 13 | return

{children}

14 | }, 15 | ul({ children }) { 16 | return
    {children}
17 | }, 18 | ol({ children }) { 19 | return
    {children}
20 | }, 21 | h3({ children }) { 22 | return

{children}

23 | }, 24 | table({ children }) { 25 | return {children}
26 | }, 27 | th({ children }) { 28 | return {children} 29 | }, 30 | td({ children }) { 31 | return {children} 32 | }, 33 | blockquote({ children }) { 34 | return
{children}
; 35 | }, 36 | a(props) { 37 | const { children, ...rest } = props 38 | return 39 | 40 | {children} 41 | 42 | 45 | 46 | }, 47 | }} 48 | > 49 | {content} 50 |
; 51 | } -------------------------------------------------------------------------------- /apps/experimental/chat_widget/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react'; 2 | import { App } from './app'; 3 | 4 | export const dynamic = 'force-dynamic'; 5 | 6 | export default function Page() { 7 | return 8 | 9 | 10 | } 11 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/app/providers.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | // 1. import `NextUIProvider` component 4 | import {NextUIProvider} from "@nextui-org/react"; 5 | 6 | export default function Providers({ 7 | children, 8 | }: { 9 | children: React.ReactNode; 10 | }) { 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | } -------------------------------------------------------------------------------- /apps/experimental/chat_widget/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | output: 'standalone', 4 | }; 5 | 6 | export default nextConfig; 7 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-widget", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@nextui-org/react": "^2.4.8", 13 | "framer-motion": "^11.11.11", 14 | "next": "^14.2.25", 15 | "react": "^18.3.1", 16 | "react-dom": "^18.3.1", 17 | "react-markdown": "^9.0.1", 18 | "remark-gfm": "^4.0.0", 19 | "rowboat-shared": "github:rowboatlabs/shared", 20 | "zod": "^3.23.8" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "^20", 24 | "@types/react": "^18", 25 | "@types/react-dom": "^18", 26 | "eslint": "^8", 27 | "eslint-config-next": "15.0.2", 28 | "postcss": "^8", 29 | "tailwindcss": "^3.4.1", 30 | "typescript": "^5" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import { nextui } from "@nextui-org/react"; 2 | import type { Config } from "tailwindcss"; 3 | 4 | const config: Config = { 5 | content: [ 6 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 9 | "./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}", 10 | ], 11 | theme: { 12 | extend: {}, 13 | }, 14 | plugins: [nextui()], 15 | }; 16 | export default config; 17 | -------------------------------------------------------------------------------- /apps/experimental/chat_widget/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/experimental/simulation_runner/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use official Python runtime as base image 2 | FROM python:3.11-slim 3 | 4 | # Set working directory in container 5 | WORKDIR /app 6 | 7 | # Copy requirements file 8 | COPY requirements.txt . 9 | 10 | # Install dependencies 11 | RUN pip install --no-cache-dir -r requirements.txt 12 | 13 | # Copy project files 14 | COPY . . 15 | 16 | # Expose port if your app needs it (adjust as needed) 17 | ENV PYTHONUNBUFFERED=1 18 | 19 | # Command to run the simulation service 20 | CMD ["python", "service.py"] 21 | -------------------------------------------------------------------------------- /apps/experimental/simulation_runner/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/experimental/simulation_runner/__init__.py -------------------------------------------------------------------------------- /apps/experimental/simulation_runner/requirements.txt: -------------------------------------------------------------------------------- 1 | annotated-types==0.7.0 2 | anyio==4.8.0 3 | certifi==2025.1.31 4 | charset-normalizer==3.4.1 5 | distro==1.9.0 6 | dnspython==2.7.0 7 | h11==0.14.0 8 | httpcore==1.0.7 9 | httpx==0.28.1 10 | idna==3.10 11 | iniconfig==2.0.0 12 | jiter==0.8.2 13 | motor==3.7.0 14 | openai==1.63.0 15 | packaging==24.2 16 | pluggy==1.5.0 17 | pydantic==2.10.6 18 | pydantic_core==2.27.2 19 | pymongo==4.11.1 20 | pytest==8.3.4 21 | pytest-asyncio==0.25.3 22 | python-dateutil==2.9.0.post0 23 | requests==2.32.3 24 | rowboat==2.1.0 25 | six==1.17.0 26 | sniffio==1.3.1 27 | tqdm==4.67.1 28 | typing_extensions==4.12.2 29 | urllib3==2.3.0 30 | -------------------------------------------------------------------------------- /apps/experimental/simulation_runner/scenario_types.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional, List, Literal 3 | from pydantic import BaseModel, Field 4 | 5 | # Define run statuses to include the new "error" status 6 | RunStatus = Literal["pending", "running", "completed", "cancelled", "failed", "error"] 7 | 8 | class TestScenario(BaseModel): 9 | # `_id` in Mongo will be stored as ObjectId; we return it as a string 10 | id: str 11 | projectId: str 12 | name: str 13 | description: str 14 | createdAt: datetime 15 | lastUpdatedAt: datetime 16 | 17 | class TestSimulation(BaseModel): 18 | id: str 19 | projectId: str 20 | name: str 21 | scenarioId: str 22 | profileId: str 23 | passCriteria: str 24 | createdAt: datetime 25 | lastUpdatedAt: datetime 26 | 27 | class AggregateResults(BaseModel): 28 | total: int 29 | passCount: int 30 | failCount: int 31 | 32 | class TestRun(BaseModel): 33 | id: str 34 | projectId: str 35 | name: str 36 | simulationIds: List[str] 37 | workflowId: str 38 | status: RunStatus 39 | startedAt: datetime 40 | completedAt: Optional[datetime] = None 41 | aggregateResults: Optional[AggregateResults] = None 42 | lastHeartbeat: Optional[datetime] = None 43 | 44 | class TestResult(BaseModel): 45 | projectId: str 46 | runId: str 47 | simulationId: str 48 | result: Literal["pass", "fail"] 49 | details: str 50 | transcript: str 51 | -------------------------------------------------------------------------------- /apps/experimental/tools_webhook/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use official Python runtime as base image 2 | FROM python:3.11-slim 3 | 4 | # Set working directory in container 5 | WORKDIR /app 6 | 7 | # Copy requirements file 8 | COPY requirements.txt . 9 | 10 | # Install dependencies 11 | RUN pip install --no-cache-dir -r requirements.txt 12 | 13 | # Copy project files 14 | COPY . . 15 | 16 | # Expose port if your app needs it (adjust as needed) 17 | ENV FLASK_APP=app 18 | ENV PYTHONUNBUFFERED=1 19 | 20 | # Command to run Flask development server 21 | CMD ["flask", "run", "--host=0.0.0.0", "--port=3005"] 22 | -------------------------------------------------------------------------------- /apps/experimental/tools_webhook/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/experimental/tools_webhook/__init__.py -------------------------------------------------------------------------------- /apps/experimental/tools_webhook/function_map.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | function_map.py 4 | 5 | Defines all the callable functions and a mapping from 6 | string names to these functions. 7 | """ 8 | 9 | def greet(name: str, message: str): 10 | """Return a greeting string.""" 11 | return f"{message}, {name}!" 12 | 13 | def add(a: int, b: int): 14 | """Return the sum of two integers.""" 15 | return a + b 16 | 17 | def get_account_balance(user_id: str): 18 | """Return a mock account balance for the given user_id.""" 19 | return f"User {user_id} has a balance of $123.45." 20 | 21 | # A configurable mapping from function identifiers to actual Python functions 22 | FUNCTIONS_MAP = { 23 | "greet": greet, 24 | "add": add, 25 | "get_account_balance": get_account_balance 26 | } 27 | -------------------------------------------------------------------------------- /apps/experimental/tools_webhook/requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.9.0 2 | click==8.1.8 3 | Flask==3.1.0 4 | iniconfig==2.0.0 5 | itsdangerous==2.2.0 6 | Jinja2==3.1.5 7 | MarkupSafe==3.0.2 8 | packaging==24.2 9 | pluggy==1.5.0 10 | PyJWT==2.10.1 11 | pytest==8.3.4 12 | Werkzeug==3.1.3 13 | -------------------------------------------------------------------------------- /apps/experimental/tools_webhook/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/experimental/tools_webhook/tests/__init__.py -------------------------------------------------------------------------------- /apps/experimental/tools_webhook/tests/test_tool_caller.py: -------------------------------------------------------------------------------- 1 | # tests/test_tool_caller.py 2 | 3 | import pytest 4 | from tools_webhook.tool_caller import call_tool 5 | from tools_webhook.function_map import FUNCTIONS_MAP 6 | 7 | def test_call_tool_greet(): 8 | # Normal case 9 | result = call_tool("greet", {"name": "Alice", "message": "Hello"}, FUNCTIONS_MAP) 10 | assert result == "Hello, Alice!" 11 | 12 | def test_call_tool_add(): 13 | # Normal case 14 | result = call_tool("add", {"a": 2, "b": 5}, FUNCTIONS_MAP) 15 | assert result == 7 16 | 17 | def test_call_tool_missing_func(): 18 | # Should raise ValueError if function is not in FUNCTIONS_MAP 19 | with pytest.raises(ValueError) as exc_info: 20 | call_tool("non_existent_func", {}, FUNCTIONS_MAP) 21 | assert "Function 'non_existent_func' not found" in str(exc_info.value) 22 | 23 | def test_call_tool_missing_param(): 24 | # greet requires `name` and `message` 25 | with pytest.raises(ValueError) as exc_info: 26 | call_tool("greet", {"name": "Alice"}, FUNCTIONS_MAP) 27 | assert "Missing required parameter: message" in str(exc_info.value) 28 | 29 | def test_call_tool_unexpected_param(): 30 | # `greet` only expects name and message 31 | with pytest.raises(ValueError) as exc_info: 32 | call_tool("greet", {"name": "Alice", "message": "Hello", "extra": "???"}, 33 | FUNCTIONS_MAP) 34 | assert "Unexpected parameter: extra" in str(exc_info.value) 35 | 36 | def test_call_tool_type_conversion_error(): 37 | # `add` expects integers `a` and `b`, so passing a string should fail 38 | with pytest.raises(ValueError) as exc_info: 39 | call_tool("add", {"a": "not_an_int", "b": 3}, FUNCTIONS_MAP) 40 | assert "Parameter 'a' must be of type int" in str(exc_info.value) 41 | -------------------------------------------------------------------------------- /apps/experimental/twilio_handler/.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .venv/ -------------------------------------------------------------------------------- /apps/experimental/twilio_handler/.env.example: -------------------------------------------------------------------------------- 1 | # Environment variables for the Voice API application 2 | 3 | # Twilio configuration 4 | TWILIO_ACCOUNT_SID=your_account_sid_here 5 | TWILIO_AUTH_TOKEN=your_auth_token_here 6 | BASE_URL=https://your-public-url-here.ngrok.io 7 | 8 | # RowBoat API configuration 9 | ROWBOAT_API_HOST=http://localhost:3000 10 | ROWBOAT_PROJECT_ID=your_project_id_here 11 | ROWBOAT_API_KEY=your_api_key_here 12 | 13 | # Speech processing APIs 14 | DEEPGRAM_API_KEY=your_deepgram_api_key_here 15 | ELEVENLABS_API_KEY=your_elevenlabs_api_key_here 16 | 17 | # Server configuration 18 | PORT=3009 19 | WHATSAPP_PORT=3010 20 | 21 | # Redis configuration for persistent state 22 | REDIS_URL=redis://localhost:6379/0 23 | REDIS_EXPIRY_SECONDS=86400 24 | SERVICE_NAME=rowboat-voice -------------------------------------------------------------------------------- /apps/experimental/twilio_handler/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .venv 3 | -------------------------------------------------------------------------------- /apps/experimental/twilio_handler/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /app 4 | 5 | # Copy requirements first to leverage Docker cache 6 | COPY requirements.txt . 7 | RUN pip install --no-cache-dir -r requirements.txt 8 | 9 | # Copy application code 10 | COPY . . 11 | 12 | # Set environment variables 13 | ENV FLASK_APP=app 14 | ENV PYTHONUNBUFFERED=1 15 | ENV PYTHONPATH=/app 16 | 17 | # Command to run Flask development server 18 | CMD ["flask", "run", "--host=0.0.0.0", "--port=4010"] -------------------------------------------------------------------------------- /apps/experimental/twilio_handler/load_env.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | import os 3 | 4 | def load_environment(): 5 | """Load environment variables from .env file""" 6 | load_dotenv() -------------------------------------------------------------------------------- /apps/experimental/twilio_handler/requirements.txt: -------------------------------------------------------------------------------- 1 | aiohappyeyeballs==2.5.0 2 | aiohttp==3.11.13 3 | aiohttp-retry==2.9.1 4 | aiosignal==1.3.2 5 | annotated-types==0.7.0 6 | anyio==4.8.0 7 | attrs==25.1.0 8 | blinker==1.9.0 9 | certifi==2025.1.31 10 | charset-normalizer==3.4.1 11 | click==8.1.8 12 | dnspython==2.7.0 13 | dotenv==0.9.9 14 | elevenlabs==1.52.0 15 | Flask==3.1.0 16 | frozenlist==1.5.0 17 | h11==0.14.0 18 | httpcore==1.0.7 19 | httpx==0.28.1 20 | idna==3.10 21 | itsdangerous==2.2.0 22 | Jinja2==3.1.6 23 | MarkupSafe==3.0.2 24 | multidict==6.1.0 25 | propcache==0.3.0 26 | pydantic==2.10.6 27 | pydantic_core==2.27.2 28 | PyJWT==2.10.1 29 | pymongo==4.11.2 30 | python-dotenv==1.0.1 31 | requests==2.32.3 32 | rowboat==2.1.0 33 | sniffio==1.3.1 34 | twilio==9.4.6 35 | typing_extensions==4.12.2 36 | urllib3==2.3.0 37 | websockets==15.0.1 38 | Werkzeug==3.1.3 39 | yarl==1.18.3 40 | -------------------------------------------------------------------------------- /apps/python-sdk/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | venv/ 3 | .venv/ 4 | dist/ -------------------------------------------------------------------------------- /apps/python-sdk/README.md: -------------------------------------------------------------------------------- 1 | # Rowboat Python SDK 2 | 3 | A Python SDK for interacting with the Rowboat API. 4 | 5 | ## Installation 6 | 7 | You can install the package using pip: 8 | 9 | ```bash 10 | pip install rowboat 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Basic Usage with StatefulChat 16 | 17 | The easiest way to interact with Rowboat is using the `StatefulChat` class, which maintains conversation state automatically: 18 | 19 | ```python 20 | from rowboat import Client, StatefulChat 21 | 22 | # Initialize the client 23 | client = Client( 24 | host="", 25 | project_id="", 26 | api_key="" 27 | ) 28 | 29 | # Create a stateful chat session 30 | chat = StatefulChat(client) 31 | 32 | # Have a conversation 33 | response = chat.run("What is the capital of France?") 34 | print(response) 35 | # The capital of France is Paris. 36 | 37 | # Continue the conversation - the context is maintained automatically 38 | response = chat.run("What other major cities are in that country?") 39 | print(response) 40 | # Other major cities in France include Lyon, Marseille, Toulouse, and Nice. 41 | 42 | response = chat.run("What's the population of the first city you mentioned?") 43 | print(response) 44 | # Lyon has a population of approximately 513,000 in the city proper. 45 | ``` 46 | 47 | ### Advanced Usage 48 | 49 | #### Using a specific workflow 50 | 51 | You can specify a workflow ID to use a particular conversation configuration: 52 | 53 | ```python 54 | chat = StatefulChat( 55 | client, 56 | workflow_id="" 57 | ) 58 | ``` 59 | 60 | #### Using a test profile 61 | 62 | You can specify a test profile ID to use a specific test configuration: 63 | 64 | ```python 65 | chat = StatefulChat( 66 | client, 67 | test_profile_id="" 68 | ) 69 | ``` 70 | 71 | ### Low-Level Usage 72 | 73 | For more control over the conversation, you can use the `Client` class directly: 74 | 75 | ```python 76 | from rowboat.schema import UserMessage 77 | 78 | # Initialize the client 79 | client = Client( 80 | host="", 81 | project_id="", 82 | api_key="" 83 | ) 84 | 85 | # Create messages 86 | messages = [ 87 | UserMessage(role='user', content="Hello, how are you?") 88 | ] 89 | 90 | # Get response 91 | response = client.chat(messages=messages) 92 | print(response.messages[-1].content) 93 | 94 | # For subsequent messages, you need to manage the message history and state manually 95 | messages.extend(response.messages) 96 | messages.append(UserMessage(role='user', content="What's your name?")) 97 | response = client.chat(messages=messages, state=response.state) 98 | ``` 99 | -------------------------------------------------------------------------------- /apps/python-sdk/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "rowboat" 7 | version = "3.1.0" 8 | authors = [ 9 | { name = "Ramnique Singh", email = "ramnique@rowboatlabs.com" }, 10 | ] 11 | description = "Python sdk for the Rowboat API" 12 | readme = "README.md" 13 | requires-python = ">=3.7" 14 | classifiers = [ 15 | "Programming Language :: Python :: 3", 16 | "License :: OSI Approved :: Apache Software License", 17 | "Operating System :: OS Independent", 18 | ] 19 | dependencies = [ 20 | "requests>=2.25.0", 21 | "pydantic>=2.0.0", 22 | ] 23 | 24 | [project.urls] 25 | "Homepage" = "https://github.com/rowboatlabs/rowboat/tree/main/apps/python-sdk" 26 | "Bug Tracker" = "https://github.com/rowboatlabs/rowboat/issues" -------------------------------------------------------------------------------- /apps/python-sdk/requirements.txt: -------------------------------------------------------------------------------- 1 | annotated-types==0.7.0 2 | certifi==2024.12.14 3 | charset-normalizer==3.4.1 4 | idna==3.10 5 | pydantic==2.10.5 6 | pydantic_core==2.27.2 7 | requests==2.32.3 8 | typing_extensions==4.12.2 9 | urllib3==2.3.0 10 | -------------------------------------------------------------------------------- /apps/python-sdk/src/rowboat/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import Client, StatefulChat 2 | from .schema import ( 3 | ApiMessage, 4 | UserMessage, 5 | SystemMessage, 6 | AssistantMessage, 7 | AssistantMessageWithToolCalls, 8 | ToolMessage, 9 | ApiRequest, 10 | ApiResponse 11 | ) 12 | 13 | __version__ = "0.1.0" 14 | 15 | __all__ = [ 16 | "Client", 17 | "StatefulChat", 18 | # Message types 19 | "ApiMessage", 20 | "UserMessage", 21 | "SystemMessage", 22 | "AssistantMessage", 23 | "AssistantMessageWithToolCalls", 24 | "ToolMessage", 25 | # Request/Response types 26 | "ApiRequest", 27 | "ApiResponse", 28 | ] -------------------------------------------------------------------------------- /apps/python-sdk/src/rowboat/schema.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Union, Any, Literal 2 | from pydantic import BaseModel 3 | 4 | class SystemMessage(BaseModel): 5 | role: Literal['system'] 6 | content: str 7 | 8 | class UserMessage(BaseModel): 9 | role: Literal['user'] 10 | content: str 11 | 12 | class AssistantMessage(BaseModel): 13 | role: Literal['assistant'] 14 | content: str 15 | agenticSender: Optional[str] = None 16 | agenticResponseType: Literal['internal', 'external'] 17 | 18 | class FunctionCall(BaseModel): 19 | name: str 20 | arguments: str 21 | 22 | class ToolCall(BaseModel): 23 | id: str 24 | type: Literal['function'] 25 | function: FunctionCall 26 | 27 | class AssistantMessageWithToolCalls(BaseModel): 28 | role: Literal['assistant'] 29 | content: Optional[str] = None 30 | tool_calls: List[ToolCall] 31 | agenticSender: Optional[str] = None 32 | agenticResponseType: Literal['internal', 'external'] 33 | 34 | class ToolMessage(BaseModel): 35 | role: Literal['tool'] 36 | content: str 37 | tool_call_id: str 38 | tool_name: str 39 | 40 | ApiMessage = Union[ 41 | SystemMessage, 42 | UserMessage, 43 | AssistantMessage, 44 | AssistantMessageWithToolCalls, 45 | ToolMessage 46 | ] 47 | 48 | class ApiRequest(BaseModel): 49 | messages: List[ApiMessage] 50 | state: Any 51 | workflowId: Optional[str] = None 52 | testProfileId: Optional[str] = None 53 | 54 | class ApiResponse(BaseModel): 55 | messages: List[ApiMessage] 56 | state: Any -------------------------------------------------------------------------------- /apps/rowboat/.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .dockerignore 3 | node_modules 4 | npm-debug.log 5 | README.md 6 | .next 7 | .git 8 | .env* -------------------------------------------------------------------------------- /apps/rowboat/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /apps/rowboat/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | # crawler script artifacts 39 | chunked.jsonl 40 | crawled.jsonl 41 | embeddings.jsonl 42 | rewritten.jsonl 43 | -------------------------------------------------------------------------------- /apps/rowboat/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker.io/docker/dockerfile:1 2 | 3 | FROM node:18-alpine AS base 4 | 5 | # Install dependencies only when needed 6 | FROM base AS deps 7 | # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. 8 | RUN apk add --no-cache libc6-compat 9 | WORKDIR /app 10 | 11 | # Install dependencies based on the preferred package manager 12 | COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./ 13 | RUN \ 14 | if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ 15 | elif [ -f package-lock.json ]; then npm ci; \ 16 | elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ 17 | else echo "Lockfile not found." && exit 1; \ 18 | fi 19 | 20 | 21 | # Rebuild the source code only when needed 22 | FROM base AS builder 23 | WORKDIR /app 24 | COPY --from=deps /app/node_modules ./node_modules 25 | COPY . . 26 | 27 | # Next.js collects completely anonymous telemetry data about general usage. 28 | # Learn more here: https://nextjs.org/telemetry 29 | # Uncomment the following line in case you want to disable telemetry during the build. 30 | # ENV NEXT_TELEMETRY_DISABLED=1 31 | 32 | RUN \ 33 | if [ -f yarn.lock ]; then yarn run build; \ 34 | elif [ -f package-lock.json ]; then npm run build; \ 35 | elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ 36 | else echo "Lockfile not found." && exit 1; \ 37 | fi 38 | 39 | # Production image, copy all the files and run next 40 | FROM base AS runner 41 | WORKDIR /app 42 | 43 | ENV NODE_ENV=production 44 | # Uncomment the following line in case you want to disable telemetry during runtime. 45 | # ENV NEXT_TELEMETRY_DISABLED=1 46 | 47 | RUN addgroup --system --gid 1001 nodejs 48 | RUN adduser --system --uid 1001 nextjs 49 | 50 | COPY --from=builder /app/public ./public 51 | 52 | # Automatically leverage output traces to reduce image size 53 | # https://nextjs.org/docs/advanced-features/output-file-tracing 54 | COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ 55 | COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static 56 | 57 | USER nextjs 58 | 59 | EXPOSE 3000 60 | 61 | ENV PORT=3000 62 | 63 | # server.js is created by next build from the standalone output 64 | # https://nextjs.org/docs/pages/api-reference/config/next-config-js/output 65 | ENV HOSTNAME="0.0.0.0" 66 | ENV PORT=3000 67 | CMD ["node", "server.js"] -------------------------------------------------------------------------------- /apps/rowboat/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | Install dependencies: 6 | 7 | ```bash 8 | npm install 9 | ``` 10 | 11 | First, run the development server: 12 | 13 | ```bash 14 | npm run dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /apps/rowboat/app/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat/app/android-chrome-192x192.png -------------------------------------------------------------------------------- /apps/rowboat/app/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat/app/android-chrome-512x512.png -------------------------------------------------------------------------------- /apps/rowboat/app/api/auth/[auth0]/route.ts: -------------------------------------------------------------------------------- 1 | // pages/api/auth/[auth0].js 2 | import { handleAuth, handleLogin } from '@auth0/nextjs-auth0'; 3 | 4 | export const GET = handleAuth({ 5 | login: handleLogin({ 6 | authorizationParams: { 7 | prompt: 'login' 8 | } 9 | }) 10 | }); -------------------------------------------------------------------------------- /apps/rowboat/app/api/copilot-stream-response/[streamId]/route.ts: -------------------------------------------------------------------------------- 1 | import { redisClient } from "@/app/lib/redis"; 2 | 3 | export async function GET(request: Request, { params }: { params: { streamId: string } }) { 4 | // get the payload from redis 5 | const payload = await redisClient.get(`copilot-stream-${params.streamId}`); 6 | if (!payload) { 7 | return new Response("Stream not found", { status: 404 }); 8 | } 9 | 10 | // Fetch the upstream SSE stream. 11 | const upstreamResponse = await fetch(`${process.env.COPILOT_API_URL}/chat_stream`, { 12 | method: 'POST', 13 | body: payload, 14 | headers: { 15 | 'Content-Type': 'application/json', 16 | 'Authorization': `Bearer ${process.env.COPILOT_API_KEY || 'test'}`, 17 | }, 18 | cache: 'no-store', 19 | }); 20 | 21 | // If the upstream request fails, return a 502 Bad Gateway. 22 | if (!upstreamResponse.ok || !upstreamResponse.body) { 23 | return new Response("Error connecting to upstream SSE stream", { status: 502 }); 24 | } 25 | 26 | const reader = upstreamResponse.body.getReader(); 27 | 28 | const stream = new ReadableStream({ 29 | async start(controller) { 30 | try { 31 | // Read from the upstream stream continuously. 32 | while (true) { 33 | const { done, value } = await reader.read(); 34 | if (done) break; 35 | // Immediately enqueue each received chunk. 36 | controller.enqueue(value); 37 | } 38 | controller.close(); 39 | } catch (error) { 40 | controller.error(error); 41 | } 42 | }, 43 | }); 44 | 45 | return new Response(stream, { 46 | headers: { 47 | "Content-Type": "text/event-stream", 48 | "Cache-Control": "no-cache", 49 | "Connection": "keep-alive", 50 | }, 51 | }); 52 | } -------------------------------------------------------------------------------- /apps/rowboat/app/api/stream-response/[streamId]/route.ts: -------------------------------------------------------------------------------- 1 | import { redisClient } from "@/app/lib/redis"; 2 | 3 | export async function GET(request: Request, { params }: { params: { streamId: string } }) { 4 | // get the payload from redis 5 | const payload = await redisClient.get(`chat-stream-${params.streamId}`); 6 | if (!payload) { 7 | return new Response("Stream not found", { status: 404 }); 8 | } 9 | 10 | // Fetch the upstream SSE stream. 11 | const upstreamResponse = await fetch(`${process.env.AGENTS_API_URL}/chat_stream`, { 12 | method: 'POST', 13 | body: payload, 14 | headers: { 15 | 'Content-Type': 'application/json', 16 | 'Authorization': `Bearer ${process.env.AGENTS_API_KEY || 'test'}`, 17 | }, 18 | cache: 'no-store', 19 | }); 20 | 21 | // If the upstream request fails, return a 502 Bad Gateway. 22 | if (!upstreamResponse.ok || !upstreamResponse.body) { 23 | return new Response("Error connecting to upstream SSE stream", { status: 502 }); 24 | } 25 | 26 | const reader = upstreamResponse.body.getReader(); 27 | 28 | const stream = new ReadableStream({ 29 | async start(controller) { 30 | try { 31 | // Read from the upstream stream continuously. 32 | while (true) { 33 | const { done, value } = await reader.read(); 34 | if (done) break; 35 | // Immediately enqueue each received chunk. 36 | controller.enqueue(value); 37 | } 38 | controller.close(); 39 | } catch (error) { 40 | controller.error(error); 41 | } 42 | }, 43 | }); 44 | 45 | return new Response(stream, { 46 | headers: { 47 | "Content-Type": "text/event-stream", 48 | "Cache-Control": "no-cache", 49 | "Connection": "keep-alive", 50 | }, 51 | }); 52 | } -------------------------------------------------------------------------------- /apps/rowboat/app/api/v1/utils.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from "next/server"; 2 | import { apiKeysCollection, projectsCollection } from "../../lib/mongodb"; 3 | 4 | export async function authCheck(projectId: string, req: NextRequest, handler: () => Promise): Promise { 5 | const authHeader = req.headers.get('Authorization'); 6 | if (!authHeader?.startsWith('Bearer ')) { 7 | return Response.json({ error: "Authorization header must be a Bearer token" }, { status: 400 }); 8 | } 9 | const key = authHeader.split(' ')[1]; 10 | if (!key) { 11 | return Response.json({ error: "Missing API key in request" }, { status: 400 }); 12 | } 13 | 14 | // check if api key is valid 15 | // while also updating last used timestamp 16 | const result = await apiKeysCollection.findOneAndUpdate( 17 | { 18 | projectId, 19 | key, 20 | }, 21 | { $set: { lastUsedAt: new Date().toISOString() } } 22 | ); 23 | if (!result) { 24 | return Response.json({ error: "Invalid API key" }, { status: 403 }); 25 | } 26 | 27 | return await handler(); 28 | } 29 | -------------------------------------------------------------------------------- /apps/rowboat/app/api/widget/v1/chats/[chatId]/close/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from "next/server"; 2 | import { chatsCollection } from "../../../../../../lib/mongodb"; 3 | import { ObjectId } from "mongodb"; 4 | import { authCheck } from "../../../utils"; 5 | 6 | export async function POST( 7 | request: NextRequest, 8 | { params }: { params: { chatId: string } } 9 | ): Promise { 10 | return await authCheck(request, async (session) => { 11 | const { chatId } = params; 12 | 13 | const result = await chatsCollection.findOneAndUpdate( 14 | { 15 | _id: new ObjectId(chatId), 16 | projectId: session.projectId, 17 | userId: session.userId, 18 | closed: { $exists: false }, 19 | }, 20 | { 21 | $set: { 22 | closed: true, 23 | closedAt: new Date().toISOString(), 24 | closeReason: "user-closed-chat", 25 | }, 26 | }, 27 | { returnDocument: 'after' } 28 | ); 29 | 30 | if (!result) { 31 | return Response.json({ error: "Chat not found" }, { status: 404 }); 32 | } 33 | 34 | return Response.json(result); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /apps/rowboat/app/api/widget/v1/chats/[chatId]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from "next/server"; 2 | import { apiV1 } from "rowboat-shared"; 3 | import { db } from "../../../../../lib/mongodb"; 4 | import { z } from "zod"; 5 | import { ObjectId } from "mongodb"; 6 | import { authCheck } from "../../utils"; 7 | 8 | const chatsCollection = db.collection>("chats"); 9 | 10 | // get chat 11 | export async function GET( 12 | req: NextRequest, 13 | { params }: { params: Promise<{ chatId: string }> } 14 | ): Promise { 15 | return await authCheck(req, async (session) => { 16 | const { chatId } = await params; 17 | 18 | // fetch the chat from the database 19 | let chatIdObj: ObjectId; 20 | try { 21 | chatIdObj = new ObjectId(chatId); 22 | } catch (e) { 23 | return Response.json({ error: "Invalid chat ID" }, { status: 400 }); 24 | } 25 | 26 | const chat = await chatsCollection.findOne({ 27 | projectId: session.projectId, 28 | userId: session.userId, 29 | _id: chatIdObj 30 | }); 31 | 32 | if (!chat) { 33 | return Response.json({ error: "Chat not found" }, { status: 404 }); 34 | } 35 | 36 | // return the chat 37 | return Response.json({ 38 | ...chat, 39 | id: chat._id.toString(), 40 | _id: undefined, 41 | }); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /apps/rowboat/app/api/widget/v1/session/guest/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from "next/server"; 2 | import { clientIdCheck } from "../../utils"; 3 | import { SignJWT } from "jose"; 4 | import { z } from "zod"; 5 | import { Session } from "../../utils"; 6 | import { apiV1 } from "rowboat-shared"; 7 | 8 | export async function POST(req: NextRequest): Promise { 9 | return await clientIdCheck(req, async (projectId) => { 10 | // create a new guest user 11 | const session: z.infer = { 12 | userId: `guest-${crypto.randomUUID()}`, 13 | userName: 'Guest User', 14 | projectId: projectId 15 | }; 16 | 17 | // Create and sign JWT 18 | const token = await new SignJWT(session) 19 | .setProtectedHeader({ alg: 'HS256' }) 20 | .setIssuedAt() 21 | .setExpirationTime('24h') 22 | .sign(new TextEncoder().encode(process.env.CHAT_WIDGET_SESSION_JWT_SECRET)); 23 | 24 | const response: z.infer = { 25 | sessionId: token, 26 | }; 27 | 28 | return Response.json(response); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /apps/rowboat/app/api/widget/v1/session/user/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from "next/server"; 2 | import { clientIdCheck } from "../../utils"; 3 | import { SignJWT, jwtVerify } from "jose"; 4 | import { z } from "zod"; 5 | import { Session } from "../../utils"; 6 | import { apiV1 } from "rowboat-shared"; 7 | import { projectsCollection } from "../../../../../lib/mongodb"; 8 | 9 | export async function POST(req: NextRequest): Promise { 10 | return await clientIdCheck(req, async (projectId) => { 11 | // decode and validate JWT 12 | const json = await req.json(); 13 | const parsedRequest = apiV1.ApiCreateUserSessionRequest.parse(json); 14 | 15 | // fetch client signing key from db 16 | const project = await projectsCollection.findOne({ 17 | _id: projectId 18 | }); 19 | if (!project) { 20 | return Response.json({ error: 'Project not found' }, { status: 404 }); 21 | } 22 | const clientSigningKey = project.secret; 23 | 24 | // verify client signing key 25 | let verified; 26 | try { 27 | verified = await jwtVerify<{ 28 | userId: string; 29 | userName?: string; 30 | }>(parsedRequest.userDataJwt, new TextEncoder().encode(clientSigningKey)); 31 | } catch (e) { 32 | return Response.json({ error: 'Invalid jwt' }, { status: 403 }); 33 | } 34 | 35 | // create new user session 36 | const session: z.infer = { 37 | userId: verified.payload.userId, 38 | userName: verified.payload.userName ?? 'Unknown', 39 | projectId: projectId 40 | }; 41 | 42 | // Create and sign JWT 43 | const token = await new SignJWT(session) 44 | .setProtectedHeader({ alg: 'HS256' }) 45 | .setIssuedAt() 46 | .setExpirationTime('24h') 47 | .sign(new TextEncoder().encode(process.env.CHAT_WIDGET_SESSION_JWT_SECRET)); 48 | 49 | const response: z.infer = { 50 | sessionId: token, 51 | }; 52 | 53 | return Response.json(response); 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /apps/rowboat/app/app.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { TypewriterEffect } from "./lib/components/typewriter"; 3 | import Image from 'next/image'; 4 | import logo from "@/public/logo.png"; 5 | import { useUser } from "@auth0/nextjs-auth0/client"; 6 | import { useRouter } from "next/navigation"; 7 | import { Spinner } from "@heroui/react"; 8 | import { LogInIcon } from "lucide-react"; 9 | 10 | export function App() { 11 | const router = useRouter(); 12 | const { user, error, isLoading } = useUser(); 13 | 14 | if (user) { 15 | router.push("/projects"); 16 | } 17 | 18 | // Add auto-redirect for non-authenticated users 19 | if (!isLoading && !user && !error) { 20 | router.push("/api/auth/login"); 21 | } 22 | 23 | return ( 24 |
25 | {/* Main content box */} 26 |
27 |
28 | RowBoat Logo 33 | {(isLoading || (!user && !error)) && } 34 | {error &&
{error.message}
} 35 | {user &&
36 | 37 |
Welcome, {user.name}
38 |
} 39 |
40 |
41 | 42 | {/* Footer */} 43 |
44 |
© 2025 RowBoat Labs
45 | 49 |
50 |
51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /apps/rowboat/app/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat/app/apple-touch-icon.png -------------------------------------------------------------------------------- /apps/rowboat/app/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/rowboat/app/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat/app/favicon-16x16.png -------------------------------------------------------------------------------- /apps/rowboat/app/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat/app/favicon-32x32.png -------------------------------------------------------------------------------- /apps/rowboat/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat/app/favicon.ico -------------------------------------------------------------------------------- /apps/rowboat/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import { ThemeProvider } from "./providers/theme-provider"; 3 | import { UserProvider } from '@auth0/nextjs-auth0/client'; 4 | import { Inter } from "next/font/google"; 5 | import { Providers } from "./providers"; 6 | import { Metadata } from "next"; 7 | import { HelpModalProvider } from "./providers/help-modal-provider"; 8 | 9 | const inter = Inter({ subsets: ["latin"] }); 10 | 11 | export const metadata: Metadata = { 12 | title: { 13 | default: "RowBoat labs", 14 | template: "%s | RowBoat Labs", 15 | } 16 | }; 17 | 18 | export default function RootLayout({ 19 | children, 20 | }: Readonly<{ 21 | children: React.ReactNode; 22 | }>) { 23 | return 24 | 25 | 26 | 27 | 28 | 29 | {children} 30 | 31 | 32 | 33 | 34 | 35 | ; 36 | } 37 | -------------------------------------------------------------------------------- /apps/rowboat/app/lib/client_utils.ts: -------------------------------------------------------------------------------- 1 | import { WorkflowTool, WorkflowAgent, WorkflowPrompt } from "./types/workflow_types"; 2 | import { z } from "zod"; 3 | 4 | export class QueryLimitError extends Error { 5 | constructor(message: string = 'Query limit exceeded') { 6 | super(message); 7 | this.name = 'QueryLimitError'; 8 | } 9 | } 10 | 11 | export function validateConfigChanges(configType: string, configChanges: Record, name: string) { 12 | let testObject: any; 13 | let schema: z.ZodType; 14 | 15 | switch (configType) { 16 | case 'tool': { 17 | testObject = { 18 | name: 'test', 19 | description: 'test', 20 | parameters: { 21 | type: 'object', 22 | properties: {}, 23 | required: [], 24 | }, 25 | } as z.infer; 26 | schema = WorkflowTool; 27 | break; 28 | } 29 | case 'agent': { 30 | testObject = { 31 | name: 'test', 32 | description: 'test', 33 | type: 'conversation', 34 | instructions: 'test', 35 | prompts: [], 36 | tools: [], 37 | model: 'gpt-4o', 38 | ragReturnType: 'chunks', 39 | ragK: 10, 40 | connectedAgents: [], 41 | controlType: 'retain', 42 | outputVisibility: 'user_facing', 43 | maxCallsPerParentAgent: 3, 44 | } as z.infer; 45 | schema = WorkflowAgent; 46 | break; 47 | } 48 | case 'prompt': { 49 | testObject = { 50 | name: 'test', 51 | type: 'base_prompt', 52 | prompt: "test", 53 | } as z.infer; 54 | schema = WorkflowPrompt; 55 | break; 56 | } 57 | default: 58 | return { error: `Unknown config type: ${configType}` }; 59 | } 60 | 61 | // Validate each field and remove invalid ones 62 | const validatedChanges = { ...configChanges }; 63 | for (const [key, value] of Object.entries(configChanges)) { 64 | const result = schema.safeParse({ 65 | ...testObject, 66 | [key]: value, 67 | }); 68 | if (!result.success) { 69 | console.log(`discarding field ${key} from ${configType}: ${name}`, result.error.message); 70 | delete validatedChanges[key]; 71 | } 72 | } 73 | 74 | return { changes: validatedChanges }; 75 | } 76 | -------------------------------------------------------------------------------- /apps/rowboat/app/lib/components/atmentions.ts: -------------------------------------------------------------------------------- 1 | interface AtMentionItem { 2 | id: string; 3 | value: string; 4 | [key: string]: string; // Add index signature to allow any string key 5 | } 6 | 7 | interface CreateAtMentionsProps { 8 | agents: any[]; 9 | prompts: any[]; 10 | tools: any[]; 11 | currentAgentName?: string; 12 | } 13 | 14 | export function createAtMentions({ agents, prompts, tools, currentAgentName }: CreateAtMentionsProps): AtMentionItem[] { 15 | const atMentions: AtMentionItem[] = []; 16 | 17 | // Add agents 18 | for (const a of agents) { 19 | if (a.disabled || a.name === currentAgentName) { 20 | continue; 21 | } 22 | const id = `agent:${a.name}`; 23 | atMentions.push({ 24 | id, 25 | value: id, 26 | denotationChar: "@", // Add required properties for Match type 27 | link: id, 28 | target: "_self" 29 | }); 30 | } 31 | 32 | // Add prompts 33 | for (const prompt of prompts) { 34 | const id = `prompt:${prompt.name}`; 35 | atMentions.push({ 36 | id, 37 | value: id, 38 | denotationChar: "@", 39 | link: id, 40 | target: "_self" 41 | }); 42 | } 43 | 44 | // Add tools 45 | for (const tool of tools) { 46 | const id = `tool:${tool.name}`; 47 | atMentions.push({ 48 | id, 49 | value: id, 50 | denotationChar: "@", 51 | link: id, 52 | target: "_self" 53 | }); 54 | } 55 | 56 | return atMentions; 57 | } -------------------------------------------------------------------------------- /apps/rowboat/app/lib/components/datasource-icon.tsx: -------------------------------------------------------------------------------- 1 | import { FileIcon, FilesIcon, FileTextIcon, GlobeIcon } from "lucide-react"; 2 | 3 | export function DataSourceIcon({ 4 | type = undefined, 5 | size = "sm", 6 | }: { 7 | type?: "crawl" | "urls" | "files" | "text" | undefined; 8 | size?: "sm" | "md"; 9 | }) { 10 | const sizeClass = size === "sm" ? "w-4 h-4" : "w-6 h-6"; 11 | return <> 12 | {type === undefined && } 13 | {type == "crawl" && } 14 | {type == "urls" && } 15 | {type == "files" && } 16 | {type == "text" && } 17 | ; 18 | } 19 | -------------------------------------------------------------------------------- /apps/rowboat/app/lib/components/dropdown.tsx: -------------------------------------------------------------------------------- 1 | import { Select, SelectItem, Button } from "@heroui/react"; 2 | import { ReactNode } from "react"; 3 | 4 | export interface DropdownOption { 5 | key: string; 6 | label: string; 7 | } 8 | 9 | interface DropdownProps { 10 | options: DropdownOption[]; 11 | value?: string; 12 | onChange: (value: string) => void; 13 | className?: string; 14 | placeholder?: string; 15 | } 16 | 17 | export function Dropdown({ 18 | options, 19 | value, 20 | onChange, 21 | className = "w-60", 22 | placeholder 23 | }: DropdownProps) { 24 | return ( 25 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /apps/rowboat/app/lib/components/form-section.tsx: -------------------------------------------------------------------------------- 1 | import { Divider } from "@heroui/react"; 2 | import { Label } from "./label"; 3 | 4 | export function FormSection({ 5 | label, 6 | children, 7 | showDivider = false, 8 | }: { 9 | label?: string; 10 | children: React.ReactNode; 11 | showDivider?: boolean; 12 | }) { 13 | return ( 14 | <> 15 |
16 | {label &&
19 | {showDivider && } 20 | 21 | ); 22 | } -------------------------------------------------------------------------------- /apps/rowboat/app/lib/components/form-status-button-old.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useFormStatus } from "react-dom"; 4 | import { Button, ButtonProps } from "@heroui/react"; 5 | 6 | export function FormStatusButton({ 7 | props 8 | }: { 9 | props: ButtonProps; 10 | }) { 11 | const { pending } = useFormStatus(); 12 | 13 | return 28 | ); 29 | }; 30 | 31 | export default MenuItem; -------------------------------------------------------------------------------- /apps/rowboat/app/lib/components/page-section.tsx: -------------------------------------------------------------------------------- 1 | export function PageSection({ 2 | title, 3 | children, 4 | danger = false, 5 | }: { 6 | title: string; 7 | children: React.ReactNode; 8 | danger?: boolean; 9 | }) { 10 | return
11 |
12 | {title} 13 |
14 |
15 | {children} 16 |
17 |
18 | } -------------------------------------------------------------------------------- /apps/rowboat/app/lib/components/pagination.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Pagination as NextUiPagination } from "@heroui/react"; 4 | import { usePathname, useRouter } from "next/navigation"; 5 | 6 | export function Pagination({ 7 | total, 8 | page, 9 | }: { 10 | total: number; 11 | page: number; 12 | }) { 13 | const pathname = usePathname(); 14 | const router = useRouter(); 15 | 16 | return { 21 | router.push(`${pathname}?page=${page}`); 22 | }} 23 | />; 24 | } -------------------------------------------------------------------------------- /apps/rowboat/app/lib/components/structured-list.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | import { ActionButton } from "./structured-panel"; 3 | 4 | export function SectionHeader({ title, children }: { title: string; children: React.ReactNode }) { 5 | return ( 6 |
7 |
{title}
8 |
9 | {children} 10 |
11 |
12 | ); 13 | } 14 | 15 | export function ListItem({ 16 | name, 17 | isSelected, 18 | onClick, 19 | disabled, 20 | rightElement, 21 | selectedRef, 22 | icon 23 | }: { 24 | name: string; 25 | isSelected: boolean; 26 | onClick: () => void; 27 | disabled?: boolean; 28 | rightElement?: React.ReactNode; 29 | selectedRef?: React.RefObject; 30 | icon?: React.ReactNode; 31 | }) { 32 | return ( 33 | 49 | ); 50 | } -------------------------------------------------------------------------------- /apps/rowboat/app/lib/components/typewriter.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect } from "react"; 4 | 5 | const phrases = [ 6 | "Can you help me choose the right product?", 7 | "Which plan is right for me?", 8 | "Do you have a discount code available?", 9 | "How do I get early access?", 10 | "Can you explain the charges?", 11 | ]; 12 | 13 | export function TypewriterEffect() { 14 | const [displayText, setDisplayText] = useState(""); 15 | const [index, setIndex] = useState(0); 16 | const [phraseIndex, setPhraseIndex] = useState(0); 17 | const [isTyping, setIsTyping] = useState(true); 18 | 19 | useEffect(() => { 20 | let timer: NodeJS.Timeout; 21 | const currentPhrase = phrases[phraseIndex]; 22 | 23 | if (isTyping) { 24 | if (index < currentPhrase.length) { 25 | timer = setTimeout(() => { 26 | setDisplayText((prev) => prev + currentPhrase[index]); 27 | setIndex((prev) => prev + 1); 28 | }, 20); 29 | } else { 30 | // Pause at the end 31 | timer = setTimeout(() => setIsTyping(false), 2000); 32 | } 33 | } else { 34 | if (index > 0) { 35 | timer = setTimeout(() => { 36 | setDisplayText((prev) => prev.slice(0, -1)); 37 | setIndex((prev) => prev - 1); 38 | }, 10); 39 | } else { 40 | // Move to next phrase 41 | setPhraseIndex((prev) => (prev + 1) % phrases.length); 42 | setIsTyping(true); 43 | } 44 | } 45 | 46 | return () => clearTimeout(timer); 47 | }, [index, isTyping, phraseIndex]); 48 | 49 | return
50 | {displayText} 51 |
; 52 | }; -------------------------------------------------------------------------------- /apps/rowboat/app/lib/components/user_button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useUser } from '@auth0/nextjs-auth0/client'; 3 | import { Avatar, Dropdown, DropdownItem, DropdownSection, DropdownTrigger, DropdownMenu } from "@heroui/react"; 4 | import { useRouter } from 'next/navigation'; 5 | 6 | export function UserButton() { 7 | const router = useRouter(); 8 | const { user } = useUser(); 9 | if (!user) { 10 | return <>; 11 | } 12 | 13 | const name = user.name ?? user.email ?? 'Unknown user'; 14 | 15 | return 16 | 17 | 22 | 23 | { 25 | if (key === 'logout') { 26 | router.push('/api/auth/logout'); 27 | } 28 | }} 29 | > 30 | 31 | 32 | Logout 33 | 34 | 35 | 36 | 37 | } -------------------------------------------------------------------------------- /apps/rowboat/app/lib/embedding.ts: -------------------------------------------------------------------------------- 1 | import { createOpenAI } from "@ai-sdk/openai"; 2 | 3 | const EMBEDDING_PROVIDER_API_KEY = process.env.EMBEDDING_PROVIDER_API_KEY || process.env.OPENAI_API_KEY || ''; 4 | const EMBEDDING_PROVIDER_BASE_URL = process.env.EMBEDDING_PROVIDER_BASE_URL || undefined; 5 | const EMBEDDING_MODEL = process.env.EMBEDDING_MODEL || 'text-embedding-3-small'; 6 | 7 | const openai = createOpenAI({ 8 | apiKey: EMBEDDING_PROVIDER_API_KEY, 9 | baseURL: EMBEDDING_PROVIDER_BASE_URL, 10 | }); 11 | 12 | export const embeddingModel = openai.embedding(EMBEDDING_MODEL); -------------------------------------------------------------------------------- /apps/rowboat/app/lib/feature_flags.ts: -------------------------------------------------------------------------------- 1 | export const USE_RAG = process.env.USE_RAG === 'true'; 2 | export const USE_RAG_UPLOADS = process.env.USE_RAG_UPLOADS === 'true'; 3 | export const USE_RAG_SCRAPING = process.env.USE_RAG_SCRAPING === 'true'; 4 | export const USE_CHAT_WIDGET = process.env.USE_CHAT_WIDGET === 'true'; 5 | export const USE_AUTH = process.env.USE_AUTH === 'true'; 6 | export const USE_RAG_S3_UPLOADS = process.env.USE_RAG_S3_UPLOADS === 'true'; 7 | export const USE_GEMINI_FILE_PARSING = process.env.USE_GEMINI_FILE_PARSING === 'true'; 8 | 9 | // Hardcoded flags 10 | export const USE_MULTIPLE_PROJECTS = true; 11 | export const USE_TESTING_FEATURE = false; 12 | export const USE_VOICE_FEATURE = false; 13 | export const USE_TRANSFER_CONTROL_OPTIONS = false; 14 | export const USE_PRODUCT_TOUR = true; 15 | export const SHOW_COPILOT_MARQUEE = false; -------------------------------------------------------------------------------- /apps/rowboat/app/lib/loadenv.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv' 2 | dotenv.config({path: [".env.local", ".env"]}); -------------------------------------------------------------------------------- /apps/rowboat/app/lib/qdrant.ts: -------------------------------------------------------------------------------- 1 | import {QdrantClient} from '@qdrant/js-client-rest'; 2 | 3 | // TO connect to Qdrant running locally 4 | export const qdrantClient = new QdrantClient({ 5 | url: process.env.QDRANT_URL, 6 | ...(process.env.QDRANT_API_KEY ? { apiKey: process.env.QDRANT_API_KEY } : {}), 7 | }); -------------------------------------------------------------------------------- /apps/rowboat/app/lib/rate_limiting.ts: -------------------------------------------------------------------------------- 1 | import { redisClient } from "./redis"; 2 | 3 | const MAX_QUERIES_PER_MINUTE = Number(process.env.MAX_QUERIES_PER_MINUTE) || 0; 4 | 5 | export async function check_query_limit(projectId: string): Promise { 6 | // if the limit is 0, we don't want to check the limit 7 | if (MAX_QUERIES_PER_MINUTE === 0) { 8 | return true; 9 | } 10 | 11 | const minutes_since_epoch = Math.floor(Date.now() / 1000 / 60); // 60 second window 12 | const key = `rate_limit:${projectId}:${minutes_since_epoch}`; 13 | 14 | // increment the counter and return the count 15 | const count = await redisClient.incr(key); 16 | if (count === 1) { 17 | await redisClient.expire(key, 70); // Set TTL to clean up automatically 18 | } 19 | 20 | return count <= MAX_QUERIES_PER_MINUTE; 21 | } -------------------------------------------------------------------------------- /apps/rowboat/app/lib/redis.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from 'redis'; 2 | 3 | export const redisClient = createClient({ 4 | url: process.env.REDIS_URL, 5 | }); 6 | 7 | redisClient.connect(); 8 | -------------------------------------------------------------------------------- /apps/rowboat/app/lib/types/project_types.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { MCPServer } from "./types"; 3 | 4 | export const Project = z.object({ 5 | _id: z.string().uuid(), 6 | name: z.string(), 7 | createdAt: z.string().datetime(), 8 | lastUpdatedAt: z.string().datetime(), 9 | createdByUserId: z.string(), 10 | secret: z.string(), 11 | chatClientId: z.string(), 12 | webhookUrl: z.string().optional(), 13 | publishedWorkflowId: z.string().optional(), 14 | nextWorkflowNumber: z.number().optional(), 15 | testRunCounter: z.number().default(0), 16 | mcpServers: z.array(MCPServer).optional(), 17 | }); 18 | 19 | export const ProjectMember = z.object({ 20 | userId: z.string(), 21 | projectId: z.string(), 22 | createdAt: z.string().datetime(), 23 | lastUpdatedAt: z.string().datetime(), 24 | }); 25 | 26 | export const ApiKey = z.object({ 27 | projectId: z.string(), 28 | key: z.string(), 29 | createdAt: z.string().datetime(), 30 | lastUsedAt: z.string().datetime().optional(), 31 | }); -------------------------------------------------------------------------------- /apps/rowboat/app/lib/types/testing_types.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const TestScenario = z.object({ 4 | projectId: z.string(), 5 | name: z.string().min(1, "Name cannot be empty"), 6 | description: z.string().min(1, "Description cannot be empty"), 7 | createdAt: z.string().datetime(), 8 | lastUpdatedAt: z.string().datetime(), 9 | }); 10 | 11 | export const TestProfile = z.object({ 12 | projectId: z.string(), 13 | name: z.string().min(1, "Name cannot be empty"), 14 | context: z.string(), 15 | createdAt: z.string().datetime(), 16 | lastUpdatedAt: z.string().datetime(), 17 | mockTools: z.boolean(), 18 | mockPrompt: z.string().optional(), 19 | }); 20 | 21 | export const TestSimulation = z.object({ 22 | projectId: z.string(), 23 | name: z.string(), 24 | description: z.string().optional().nullable(), 25 | createdAt: z.string().datetime(), 26 | lastUpdatedAt: z.string().datetime(), 27 | scenarioId: z.string(), 28 | profileId: z.string().nullable(), 29 | passCriteria: z.string(), 30 | }); 31 | 32 | export const TestRun = z.object({ 33 | projectId: z.string(), 34 | name: z.string(), 35 | simulationIds: z.array(z.string()), 36 | workflowId: z.string(), 37 | status: z.enum(['pending', 'running', 'completed', 'cancelled', 'failed', 'error']), 38 | startedAt: z.string(), 39 | completedAt: z.string().optional(), 40 | aggregateResults: z.object({ 41 | total: z.number(), 42 | passCount: z.number(), 43 | failCount: z.number(), 44 | }).optional(), 45 | }); 46 | 47 | export const TestResult = z.object({ 48 | projectId: z.string(), 49 | runId: z.string(), 50 | simulationId: z.string(), 51 | result: z.union([z.literal('pass'), z.literal('fail')]), 52 | details: z.string(), 53 | transcript: z.string() 54 | }); -------------------------------------------------------------------------------- /apps/rowboat/app/lib/types/tool_types.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { apiV1 } from "rowboat-shared" 3 | 4 | export const GetInformationToolResultItem = z.object({ 5 | title: z.string(), 6 | name: z.string(), 7 | content: z.string(), 8 | docId: z.string(), 9 | sourceId: z.string(), 10 | });export const GetInformationToolResult = z.object({ 11 | results: z.array(GetInformationToolResultItem) 12 | }); 13 | export const WebpageCrawlResponse = z.object({ 14 | title: z.string(), 15 | content: z.string(), 16 | }); 17 | export const ClientToolCallRequestBody = z.object({ 18 | toolCall: apiV1.AssistantMessageWithToolCalls.shape.tool_calls.element, 19 | }); 20 | export const ClientToolCallJwt = z.object({ 21 | requestId: z.string().uuid(), 22 | projectId: z.string(), 23 | bodyHash: z.string(), 24 | iat: z.number(), 25 | exp: z.number(), 26 | }); 27 | export const ClientToolCallRequest = z.object({ 28 | requestId: z.string().uuid(), 29 | content: z.string(), // json stringified ClientToolCallRequestBody 30 | }); 31 | export const ClientToolCallResponse = z.unknown(); 32 | 33 | -------------------------------------------------------------------------------- /apps/rowboat/app/lib/types/voice_types.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | import { WithId } from 'mongodb'; 3 | 4 | export const TwilioConfigParams = z.object({ 5 | phone_number: z.string(), 6 | account_sid: z.string(), 7 | auth_token: z.string(), 8 | label: z.string(), 9 | project_id: z.string(), 10 | workflow_id: z.string(), 11 | }); 12 | 13 | export const TwilioConfig = TwilioConfigParams.extend({ 14 | createdAt: z.date(), 15 | status: z.enum(['active', 'deleted']), 16 | }); 17 | 18 | export type TwilioConfigParams = z.infer; 19 | export type TwilioConfig = WithId>; 20 | 21 | export interface TwilioConfigResponse { 22 | success: boolean; 23 | error?: string; 24 | } 25 | 26 | export interface InboundConfigResponse { 27 | status: 'configured' | 'reconfigured'; 28 | phone_number: string; 29 | workflow_id: string; 30 | previous_webhook?: string; 31 | error?: string; 32 | } -------------------------------------------------------------------------------- /apps/rowboat/app/lib/uploads_s3_client.ts: -------------------------------------------------------------------------------- 1 | import { S3Client } from "@aws-sdk/client-s3"; 2 | 3 | export const uploadsS3Client = new S3Client({ 4 | region: process.env.UPLOADS_AWS_REGION || 'us-east-1', 5 | credentials: { 6 | accessKeyId: process.env.AWS_ACCESS_KEY_ID || '', 7 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || '', 8 | }, 9 | }); -------------------------------------------------------------------------------- /apps/rowboat/app/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Spinner } from "@heroui/react"; 2 | 3 | export default function Loading() { 4 | // Stack uses React Suspense, which will render this page while user data is being fetched. 5 | // See: https://nextjs.org/docs/app/api-reference/file-conventions/loading 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /apps/rowboat/app/new-chat-link.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | export function NewChatLink({demo}: {demo: string}) { 4 | return 8 | Start new chat → 9 | 10 | } -------------------------------------------------------------------------------- /apps/rowboat/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { App } from "./app"; 2 | import { redirect } from "next/navigation"; 3 | import { USE_AUTH } from "./lib/feature_flags"; 4 | 5 | export const dynamic = 'force-dynamic'; 6 | 7 | export default function Home() { 8 | if (!USE_AUTH) { 9 | redirect("/projects"); 10 | } 11 | return 12 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/config/components/shared-styles.ts: -------------------------------------------------------------------------------- 1 | export const sectionHeaderStyles = "text-sm font-semibold text-gray-900 dark:text-gray-100 mb-2"; 2 | export const sectionDescriptionStyles = "text-sm text-gray-500 dark:text-gray-400 mb-4"; 3 | export const textareaStyles = "rounded-lg p-3 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-750 focus:shadow-inner focus:ring-2 focus:ring-indigo-500/20 dark:focus:ring-indigo-400/20 placeholder:text-gray-400 dark:placeholder:text-gray-500"; 4 | export const inputStyles = "rounded-lg px-3 py-2 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-750 focus:shadow-inner focus:ring-2 focus:ring-indigo-500/20 dark:focus:ring-indigo-400/20"; -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/config/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | import App from "./app"; 3 | import { USE_CHAT_WIDGET } from "@/app/lib/feature_flags"; 4 | 5 | export const metadata: Metadata = { 6 | title: "Project config", 7 | }; 8 | 9 | export default function Page({ 10 | params, 11 | }: { 12 | params: { 13 | projectId: string; 14 | }; 15 | }) { 16 | return ; 21 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/copilot/example.md: -------------------------------------------------------------------------------- 1 | This is a response in markdown from the copilot. 2 | 3 | This is some text. 4 | 5 | I'm adding a tool `get_status()` below: 6 | 7 | ```copilot_change 8 | // action: create_new 9 | // config_type: tool 10 | // name: get_status 11 | { 12 | "change_description": "added a new tool...", 13 | "config_changes": { 14 | // same as before 15 | } 16 | } 17 | ``` 18 | 19 | I'm also updating the example agent: 20 | 21 | ```copilot_change 22 | // action: edit 23 | // config_type: agent 24 | // name: Example agent 25 | { 26 | "change_description": "updated the instructions...", 27 | "config_changes": { 28 | // same as before 29 | } 30 | } 31 | ``` 32 | 33 | This concludes my changes. Would you like some more help? -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/copilot/use-parsed-blocks.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | 3 | type Block = 4 | | { type: "text"; content: string } 5 | | { type: "code"; content: string }; 6 | 7 | const copilotCodeMarker = "copilot_change\n"; 8 | 9 | function parseMarkdown(markdown: string): Block[] { 10 | // Split on triple backticks but keep the delimiters 11 | // This gives us the raw content between and including delimiters 12 | const parts = markdown.split("```"); 13 | const blocks: Block[] = []; 14 | 15 | for (const part of parts) { 16 | if (part.trim().startsWith(copilotCodeMarker)) { 17 | blocks.push({ type: 'code', content: part.slice(copilotCodeMarker.length) }); 18 | } else { 19 | blocks.push({ type: 'text', content: part }); 20 | } 21 | } 22 | 23 | return blocks; 24 | } 25 | 26 | export function useParsedBlocks(text: string): Block[] { 27 | return useMemo(() => { 28 | return parseMarkdown(text); 29 | }, [text]); 30 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/layout.tsx: -------------------------------------------------------------------------------- 1 | export default async function Layout({ 2 | params, 3 | children 4 | }: { 5 | params: { projectId: string } 6 | children: React.ReactNode 7 | }) { 8 | return children; 9 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | 3 | export default function Page({ 4 | params 5 | }: { 6 | params: { projectId: string } 7 | }) { 8 | redirect(`/projects/${params.projectId}/workflow`); 9 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/sources/[sourceId]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SourcePage } from "./source-page"; 2 | 3 | export default async function Page({ 4 | params, 5 | }: { 6 | params: { 7 | projectId: string, 8 | sourceId: string 9 | } 10 | }) { 11 | return ; 12 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/sources/components/delete.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { deleteDataSource } from "../../../../actions/datasource_actions"; 4 | import { FormStatusButton } from "../../../../lib/components/form-status-button"; 5 | 6 | export function DeleteSource({ 7 | projectId, 8 | sourceId, 9 | }: { 10 | projectId: string; 11 | sourceId: string; 12 | }) { 13 | function handleDelete() { 14 | if (window.confirm('Are you sure you want to delete this data source?')) { 15 | deleteDataSource(projectId, sourceId); 16 | } 17 | } 18 | 19 | return
20 | 27 | ; 28 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/sources/components/section.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | interface SectionProps { 4 | title: string; 5 | description?: string; 6 | children: ReactNode; 7 | className?: string; 8 | } 9 | 10 | export function Section({ title, description, children, className }: SectionProps) { 11 | return ( 12 |
13 |
14 |

15 | {title} 16 |

17 | {description && ( 18 |

19 | {description} 20 |

21 | )} 22 |
23 |
24 | {children} 25 |
26 |
27 | ); 28 | } 29 | 30 | export function SectionRow({ children, className }: { children: ReactNode; className?: string }) { 31 | return ( 32 |
33 | {children} 34 |
35 | ); 36 | } 37 | 38 | export function SectionLabel({ children, className }: { children: ReactNode; className?: string }) { 39 | return ( 40 |
41 | {children} 42 |
43 | ); 44 | } 45 | 46 | export function SectionContent({ children, className }: { children: ReactNode; className?: string }) { 47 | return ( 48 |
49 | {children} 50 |
51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/sources/components/self-updating-source-status.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { getDataSource } from "../../../../actions/datasource_actions"; 3 | import { DataSource } from "../../../../lib/types/datasource_types"; 4 | import { useEffect, useState } from "react"; 5 | import { z } from 'zod'; 6 | import { SourceStatus } from "./source-status"; 7 | 8 | export function SelfUpdatingSourceStatus({ 9 | projectId, 10 | sourceId, 11 | initialStatus, 12 | compact = false, 13 | }: { 14 | projectId: string; 15 | sourceId: string, 16 | initialStatus: z.infer['status'], 17 | compact?: boolean; 18 | }) { 19 | const [status, setStatus] = useState(initialStatus); 20 | 21 | useEffect(() => { 22 | let ignore = false; 23 | let timeoutId: NodeJS.Timeout | null = null; 24 | 25 | async function check() { 26 | if (ignore) { 27 | return; 28 | } 29 | const source = await getDataSource(projectId, sourceId); 30 | setStatus(source.status); 31 | timeoutId = setTimeout(check, 15 * 1000); 32 | } 33 | 34 | if (status == 'pending') { 35 | timeoutId = setTimeout(check, 15 * 1000); 36 | } 37 | 38 | return () => { 39 | ignore = true; 40 | if (timeoutId) { 41 | clearTimeout(timeoutId); 42 | } 43 | }; 44 | }, [status, projectId, sourceId]); 45 | 46 | return ; 47 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/sources/components/shared.tsx: -------------------------------------------------------------------------------- 1 | export function UrlList({ urls }: { urls: string }) { 2 | return
 3 |         {urls}
 4 |     
; 5 | } 6 | 7 | export function TableLabel({ children, className }: { children: React.ReactNode, className?: string }) { 8 | return {children}; 9 | } 10 | 11 | export function TableValue({ children, className }: { children: React.ReactNode, className?: string }) { 12 | return {children}; 13 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/sources/components/source-status.tsx: -------------------------------------------------------------------------------- 1 | import { DataSource } from "../../../../lib/types/datasource_types"; 2 | import { Spinner } from "@heroui/react"; 3 | import { z } from 'zod'; 4 | import { CheckCircleIcon, XCircleIcon, ClockIcon } from "lucide-react"; 5 | 6 | export function SourceStatus({ 7 | status, 8 | projectId, 9 | compact = false, 10 | }: { 11 | status: z.infer['status'], 12 | projectId: string, 13 | compact?: boolean; 14 | }) { 15 | return ( 16 |
17 | {status === 'ready' && ( 18 | <> 19 | 20 |
21 | Ready 22 | 23 | This source has been indexed and is ready to use. 24 | 25 |
26 | 27 | )} 28 | 29 | {status === 'pending' && ( 30 | <> 31 |
32 | 33 |
34 |
35 | Processing 36 | 37 | This source is being processed. This may take a few minutes. 38 | 39 |
40 | 41 | )} 42 | 43 | {status === 'error' && ( 44 | <> 45 | 46 |
47 | Error 48 | 49 | There was an error processing this source. 50 | 51 |
52 | 53 | )} 54 |
55 | ); 56 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/sources/components/web-recrawl.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { FormStatusButton } from "../../../../lib/components/form-status-button"; 3 | import { RefreshCwIcon } from "lucide-react"; 4 | 5 | export function Recrawl({ 6 | projectId, 7 | sourceId, 8 | handleRefresh, 9 | }: { 10 | projectId: string; 11 | sourceId: string; 12 | handleRefresh: () => void; 13 | }) { 14 | return
15 | , 19 | children: "Refresh", 20 | }} 21 | /> 22 | ; 23 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/sources/new/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | import { Form } from "./form"; 3 | import { redirect } from "next/navigation"; 4 | import { USE_RAG, USE_RAG_UPLOADS, USE_RAG_S3_UPLOADS, USE_RAG_SCRAPING } from "../../../../lib/feature_flags"; 5 | 6 | export const metadata: Metadata = { 7 | title: "Add data source" 8 | } 9 | 10 | export default async function Page({ 11 | params 12 | }: { 13 | params: { projectId: string } 14 | }) { 15 | if (!USE_RAG) { 16 | redirect(`/projects/${params.projectId}`); 17 | } 18 | 19 | return ( 20 |
26 | ); 27 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/sources/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | import { SourcesList } from "./components/sources-list"; 3 | 4 | export const metadata: Metadata = { 5 | title: "Data sources", 6 | } 7 | 8 | export default async function Page({ 9 | params, 10 | }: { 11 | params: { projectId: string } 12 | }) { 13 | return ; 16 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/test/[[...slug]]/components/item-view.tsx: -------------------------------------------------------------------------------- 1 | // First, let's create a reusable component for item views 2 | export function ItemView({ 3 | items, 4 | actions 5 | }: { 6 | items: { label: string; value: string | React.ReactNode }[]; 7 | actions: React.ReactNode; 8 | }) { 9 | return ( 10 |
11 | {/* Content */} 12 |
13 |
14 | {items.map((item, index) => ( 15 |
19 |
20 | {item.label} 21 |
22 |
23 | {item.value || "—"} 24 |
25 |
26 | ))} 27 |
28 | 29 | {/* Actions */} 30 |
31 |
32 | {actions} 33 |
34 |
35 |
36 |
37 | ); 38 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/test/[[...slug]]/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from "@/components/ui/resizable"; 4 | import { ScenariosApp } from "./scenarios_app"; 5 | import { SimulationsApp } from "./simulations_app"; 6 | import { ProfilesApp } from "./profiles_app"; 7 | import { RunsApp } from "./runs_app"; 8 | import { TestingMenu } from "./testing_menu"; 9 | export default function TestPage({ params }: { params: { projectId: string; slug?: string[] } }) { 10 | const { projectId, slug = [] } = params; 11 | let app: "scenarios" | "simulations" | "profiles" | "runs" = "runs"; 12 | 13 | if (slug[0] === "scenarios") { 14 | app = "scenarios"; 15 | } else if (slug[0] === "simulations") { 16 | app = "simulations"; 17 | } else if (slug[0] === "profiles") { 18 | app = "profiles"; 19 | } else if (slug[0] === "runs") { 20 | app = "runs"; 21 | } 22 | 23 | return ( 24 |
25 | 26 | 27 |
28 | 29 |
30 |
31 | 32 | 33 | 34 | 35 | {app === "scenarios" && } 36 | {app === "simulations" && } 37 | {app === "profiles" && } 38 | {app === "runs" && } 39 | 40 |
41 |
42 | ); 43 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/test/[[...slug]]/testing_menu.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useRouter } from "next/navigation"; 4 | import { StructuredPanel } from "../../../../lib/components/structured-panel"; 5 | import { ListItem } from "../../../../lib/components/structured-list"; 6 | 7 | export function TestingMenu({ 8 | projectId, 9 | app, 10 | }: { 11 | projectId: string; 12 | app: "scenarios" | "simulations" | "profiles" | "runs"; 13 | }) { 14 | const router = useRouter(); 15 | 16 | const menuItems = [ 17 | { 18 | label: "Scenarios", 19 | href: `/projects/${projectId}/test/scenarios`, 20 | isSelected: app === "scenarios" 21 | }, 22 | { 23 | label: "Profiles", 24 | href: `/projects/${projectId}/test/profiles`, 25 | isSelected: app === "profiles" 26 | }, 27 | { 28 | label: "Simulations", 29 | href: `/projects/${projectId}/test/simulations`, 30 | isSelected: app === "simulations" 31 | }, 32 | { 33 | label: "Test Runs", 34 | href: `/projects/${projectId}/test/runs`, 35 | isSelected: app === "runs" 36 | }, 37 | ]; 38 | 39 | return ( 40 | 41 |
42 | {menuItems.map((item) => ( 43 | router.push(item.href)} 48 | /> 49 | ))} 50 |
51 |
52 | ); 53 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/test/[[...slug]]/utils/date.ts: -------------------------------------------------------------------------------- 1 | export const isValidDate = (date: any): boolean => { 2 | const parsed = new Date(date); 3 | return parsed instanceof Date && !isNaN(parsed.getTime()); 4 | }; -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/workflow/config_list.tsx: -------------------------------------------------------------------------------- 1 | import { XIcon } from "lucide-react"; 2 | 3 | export function List({ 4 | items, 5 | onRemove, 6 | }: { 7 | items: { 8 | id: string; 9 | node: React.ReactNode; 10 | }[]; 11 | onRemove: (id: string) => void; 12 | }) { 13 | return
14 | {items.map((item) => ( 15 | onRemove(item.id)}> 16 | {item.node} 17 | 18 | ))} 19 |
; 20 | } 21 | 22 | export function ListItem({ 23 | children, 24 | onRemove, 25 | }: { 26 | children: React.ReactNode; 27 | onRemove: () => void; 28 | }) { 29 | return
30 |
31 |
32 |
{children}
33 | 36 |
37 |
38 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/workflow/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | import { App } from "./app"; 3 | import { USE_RAG } from "@/app/lib/feature_flags"; 4 | import { projectsCollection } from "@/app/lib/mongodb"; 5 | import { notFound } from "next/navigation"; 6 | const DEFAULT_MODEL = process.env.PROVIDER_DEFAULT_MODEL || "gpt-4.1"; 7 | 8 | export const metadata: Metadata = { 9 | title: "Workflow" 10 | } 11 | 12 | export default async function Page({ 13 | params, 14 | }: { 15 | params: { projectId: string }; 16 | }) { 17 | console.log('->>> workflow page being rendered'); 18 | const project = await projectsCollection.findOne({ 19 | _id: params.projectId, 20 | }); 21 | if (!project) { 22 | notFound(); 23 | } 24 | 25 | return ( 26 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/workflow/pane.tsx: -------------------------------------------------------------------------------- 1 | import { StructuredPanel, ActionButton } from "../../../lib/components/structured-panel"; 2 | 3 | // Re-export both components for backward compatibility 4 | export const Pane = StructuredPanel; 5 | export { ActionButton }; 6 | 7 | // TODO: Delete this file once all the files are updated to use StructuredPanel -------------------------------------------------------------------------------- /apps/rowboat/app/projects/[projectId]/workflow/published_badge.tsx: -------------------------------------------------------------------------------- 1 | import { RadioIcon } from "lucide-react"; 2 | 3 | export function PublishedBadge() { 4 | return ( 5 |
6 | 7 |
Live
8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /apps/rowboat/app/projects/layout.tsx: -------------------------------------------------------------------------------- 1 | import { USE_AUTH, USE_RAG } from "../lib/feature_flags"; 2 | import AppLayout from './layout/components/app-layout'; 3 | 4 | export const dynamic = 'force-dynamic'; 5 | 6 | export default function Layout({ 7 | children, 8 | }: Readonly<{ 9 | children: React.ReactNode; 10 | }>) { 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/layout/components/app-layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { ReactNode, useState } from 'react'; 3 | import Sidebar from './sidebar'; 4 | import { usePathname } from 'next/navigation'; 5 | 6 | interface AppLayoutProps { 7 | children: ReactNode; 8 | useRag?: boolean; 9 | useAuth?: boolean; 10 | } 11 | 12 | export default function AppLayout({ children, useRag = false, useAuth = false }: AppLayoutProps) { 13 | const [sidebarCollapsed, setSidebarCollapsed] = useState(true); 14 | const pathname = usePathname(); 15 | const projectId = pathname.split('/')[2]; 16 | 17 | // For invalid projectId, return just the children 18 | if (!projectId && !pathname.startsWith('/projects')) { 19 | return children; 20 | } 21 | 22 | // Layout with sidebar for all routes 23 | return ( 24 |
25 | {/* Sidebar with improved shadow and blur */} 26 |
27 | setSidebarCollapsed(!sidebarCollapsed)} 33 | /> 34 |
35 | 36 | {/* Main content area */} 37 |
38 | {children} 39 |
40 |
41 | ); 42 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/layout/components/menu-item.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { LucideIcon } from "lucide-react"; 3 | 4 | interface MenuItemProps { 5 | href?: string; 6 | icon: LucideIcon; 7 | selected?: boolean; 8 | collapsed?: boolean; 9 | onClick?: () => void; 10 | children?: React.ReactNode; 11 | } 12 | 13 | export default function MenuItem({ 14 | href, 15 | icon: Icon, 16 | selected = false, 17 | collapsed = false, 18 | onClick, 19 | children 20 | }: MenuItemProps) { 21 | const ButtonContent = ( 22 | 36 | ); 37 | 38 | if (href) { 39 | return {ButtonContent}; 40 | } 41 | 42 | return ButtonContent; 43 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/layout/index.tsx: -------------------------------------------------------------------------------- 1 | import { USE_RAG } from "@/app/lib/feature_flags"; 2 | import AppLayout from './components/app-layout'; 3 | 4 | export default async function Layout({ 5 | params, 6 | children 7 | }: { 8 | params: { projectId: string } 9 | children: React.ReactNode 10 | }) { 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/layout/menu.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { usePathname } from "next/navigation"; 3 | import Link from "next/link"; 4 | import { DatabaseIcon, SettingsIcon, WorkflowIcon, PlayIcon, LucideIcon } from "lucide-react"; 5 | import MenuItem from "./components/menu-item"; 6 | 7 | interface NavLinkProps { 8 | href: string; 9 | label: string; 10 | icon: LucideIcon; 11 | collapsed?: boolean; 12 | selected?: boolean; 13 | } 14 | 15 | function NavLink({ href, label, icon, collapsed, selected = false }: NavLinkProps) { 16 | return ( 17 | 18 | 23 | {label} 24 | 25 | 26 | ); 27 | } 28 | 29 | export default function Menu({ 30 | projectId, 31 | collapsed, 32 | useRag, 33 | }: { 34 | projectId: string; 35 | collapsed: boolean; 36 | useRag: boolean; 37 | }) { 38 | const pathname = usePathname(); 39 | 40 | return ( 41 |
42 | 49 | 56 | {useRag && ( 57 | 64 | )} 65 | 72 |
73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /apps/rowboat/app/projects/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from 'next/navigation'; 2 | 3 | export default function Page() { 4 | redirect('/projects/select'); 5 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/select/components/project-card.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Project } from "@/app/lib/types/project_types"; 3 | import { default as NextLink } from "next/link"; 4 | import { z } from "zod"; 5 | import clsx from 'clsx'; 6 | import { ChevronRightIcon } from "@heroicons/react/24/outline"; 7 | import { formatDistanceToNow } from "date-fns"; 8 | import { tokens } from "@/app/styles/design-tokens"; 9 | 10 | interface ProjectCardProps { 11 | project: z.infer; 12 | } 13 | 14 | export function ProjectCard({ project }: ProjectCardProps) { 15 | return ( 16 | 26 |
27 |
28 |

36 | {project.name} 37 |

38 |

43 | Created {formatDistanceToNow(new Date(project.createdAt))} ago 44 |

45 |
46 | 54 |
55 |
56 | ); 57 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/select/components/search-projects.tsx: -------------------------------------------------------------------------------- 1 | import { Project } from "@/app/lib/types/project_types"; 2 | import { z } from "zod"; 3 | import { ProjectList } from "./project-list"; 4 | import { HorizontalDivider } from "@/components/ui/horizontal-divider"; 5 | import clsx from 'clsx'; 6 | import { XMarkIcon } from "@heroicons/react/24/outline"; 7 | 8 | interface SearchProjectsProps { 9 | projects: z.infer[]; 10 | isLoading: boolean; 11 | heading: string; 12 | subheading?: string; 13 | className?: string; 14 | onClose?: () => void; 15 | } 16 | 17 | export function SearchProjects({ 18 | projects, 19 | isLoading, 20 | heading, 21 | subheading, 22 | className, 23 | onClose 24 | }: SearchProjectsProps) { 25 | return ( 26 |
27 |
28 |
29 |

30 | {heading} 31 |

32 | {onClose && ( 33 | 39 | )} 40 |
41 | {subheading && ( 42 |

43 | {subheading} 44 |

45 | )} 46 |
47 | 48 |
49 | 54 |
55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /apps/rowboat/app/projects/select/components/submit-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useFormStatus } from "react-dom"; 3 | import clsx from 'clsx'; 4 | import { tokens } from "@/app/styles/design-tokens"; 5 | import { PlusIcon } from "lucide-react"; 6 | import { Button } from "@/components/ui/button"; 7 | 8 | export function Submit() { 9 | const { pending } = useFormStatus(); 10 | 11 | return ( 12 |
13 | {pending && ( 14 |
19 | Please hold on while we set up your project… 20 |
21 | )} 22 | 32 |
33 | ); 34 | } -------------------------------------------------------------------------------- /apps/rowboat/app/projects/select/page.tsx: -------------------------------------------------------------------------------- 1 | import App from "./app"; 2 | 3 | export default function Page() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /apps/rowboat/app/providers.tsx: -------------------------------------------------------------------------------- 1 | // app/providers.tsx 2 | 'use client' 3 | 4 | import { HeroUIProvider } from "@heroui/react" 5 | import { useRouter } from 'next/navigation' 6 | 7 | export function Providers({ className, children }: { className: string, children: React.ReactNode }) { 8 | const router = useRouter(); 9 | 10 | return ( 11 | 12 | {children} 13 | 14 | ) 15 | } -------------------------------------------------------------------------------- /apps/rowboat/app/providers/help-modal-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { createContext, useContext, useState, ReactNode } from 'react'; 4 | import { HelpModal } from '@/components/common/help-modal'; 5 | 6 | interface HelpModalContextType { 7 | showHelpModal: () => void; 8 | hideHelpModal: () => void; 9 | } 10 | 11 | const HelpModalContext = createContext(undefined); 12 | 13 | export function HelpModalProvider({ children }: { children: ReactNode }) { 14 | const [isOpen, setIsOpen] = useState(false); 15 | 16 | const showHelpModal = () => setIsOpen(true); 17 | const hideHelpModal = () => setIsOpen(false); 18 | 19 | const handleStartTour = () => { 20 | localStorage.removeItem('user_product_tour_completed'); 21 | window.location.reload(); 22 | }; 23 | 24 | return ( 25 | 26 | {children} 27 | 32 | 33 | ); 34 | } 35 | 36 | export function useHelpModal() { 37 | const context = useContext(HelpModalContext); 38 | if (context === undefined) { 39 | throw new Error('useHelpModal must be used within a HelpModalProvider'); 40 | } 41 | return context; 42 | } -------------------------------------------------------------------------------- /apps/rowboat/app/providers/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { createContext, useContext, useEffect, useState } from 'react' 4 | 5 | type Theme = 'dark' | 'light' 6 | 7 | type ThemeProviderProps = { 8 | children: React.ReactNode 9 | defaultTheme?: Theme 10 | } 11 | 12 | type ThemeProviderState = { 13 | theme: Theme 14 | toggleTheme: () => void 15 | } 16 | 17 | const ThemeProviderContext = createContext(undefined) 18 | 19 | export function ThemeProvider({ 20 | children, 21 | defaultTheme = 'light', 22 | }: ThemeProviderProps) { 23 | const [theme, setTheme] = useState(defaultTheme) 24 | 25 | useEffect(() => { 26 | const root = document.documentElement 27 | const storedTheme = localStorage.getItem("theme") 28 | 29 | if (storedTheme === 'dark' || storedTheme === 'light') { 30 | setTheme(storedTheme) 31 | } 32 | 33 | root.classList.remove('light', 'dark') 34 | root.classList.add(theme) 35 | }, [theme]) 36 | 37 | const toggleTheme = () => { 38 | setTheme((prevTheme) => { 39 | const newTheme = prevTheme === 'light' ? 'dark' : 'light' 40 | if (typeof window !== 'undefined') { 41 | localStorage.setItem("theme", newTheme) 42 | } 43 | return newTheme 44 | }) 45 | } 46 | 47 | return ( 48 | 49 | {children} 50 | 51 | ) 52 | } 53 | 54 | export function useTheme() { 55 | const context = useContext(ThemeProviderContext) 56 | if (context === undefined) { 57 | throw new Error('useTheme must be used within a ThemeProvider') 58 | } 59 | return context 60 | } -------------------------------------------------------------------------------- /apps/rowboat/app/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /apps/rowboat/app/scripts/delete_qdrant.ts: -------------------------------------------------------------------------------- 1 | import '../lib/loadenv'; 2 | import { qdrantClient } from '../lib/qdrant'; 3 | 4 | (async () => { 5 | try { 6 | const result = await qdrantClient.deleteCollection('embeddings'); 7 | console.log(`Delete qdrant collection 'embeddings' completed with result: ${result}`); 8 | } catch (error) { 9 | console.error(`Unable to delete qdrant collection 'embeddings': ${error}`); 10 | } 11 | })(); -------------------------------------------------------------------------------- /apps/rowboat/app/scripts/setup_qdrant.ts: -------------------------------------------------------------------------------- 1 | import '../lib/loadenv'; 2 | import { qdrantClient } from '../lib/qdrant'; 3 | 4 | const EMBEDDING_VECTOR_SIZE = Number(process.env.EMBEDDING_VECTOR_SIZE) || 1536; 5 | 6 | (async () => { 7 | try { 8 | const result = await qdrantClient.createCollection('embeddings', { 9 | vectors: { 10 | size: EMBEDDING_VECTOR_SIZE, 11 | distance: 'Dot', 12 | }, 13 | }); 14 | console.log(`Create qdrant collection 'embeddings' completed with result: ${result}`); 15 | } catch (error) { 16 | console.error(`Unable to create qdrant collection 'embeddings': ${error}`); 17 | } 18 | })(); -------------------------------------------------------------------------------- /apps/rowboat/app/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /apps/rowboat/app/styles/pane-effects.ts: -------------------------------------------------------------------------------- 1 | export const getPaneClasses = (isActive: boolean, otherIsActive: boolean) => [ 2 | "transition-all duration-300", 3 | isActive ? "scale-[1.02] shadow-xl relative z-10" : "", 4 | otherIsActive ? "scale-[0.98] opacity-50" : "" 5 | ]; -------------------------------------------------------------------------------- /apps/rowboat/app/styles/quill-mentions.css: -------------------------------------------------------------------------------- 1 | /* Quill mention styles */ 2 | .ql-mention-list-container { 3 | border: 1px solid #e2e8f0; 4 | border-radius: 0.375rem; 5 | box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); 6 | background-color: white; 7 | z-index: 1000; 8 | } 9 | 10 | /* Dark mode styles */ 11 | .dark .ql-mention-list-container { 12 | background-color: #1f2937; 13 | border-color: #374151; 14 | } 15 | 16 | .dark .ql-mention-list-container * { 17 | background-color: #1f2937 !important; 18 | color: #f9fafb !important; 19 | } 20 | 21 | .dark .ql-mention-list-item { 22 | color: #f9fafb !important; 23 | background-color: #1f2937 !important; 24 | } 25 | 26 | .dark .ql-mention-list-container .ql-mention-list-item.selected, 27 | .dark .ql-mention-list-container .ql-mention-list-item:hover { 28 | background-color: #6b7280 !important; 29 | } 30 | 31 | .dark .ql-mention-list-item > * { 32 | background-color: inherit !important; 33 | } 34 | 35 | /* Mention item styles with tags */ 36 | .dark .ql-mention-list-item { 37 | display: flex !important; 38 | align-items: center !important; 39 | justify-content: space-between !important; 40 | padding: 4px 8px !important; 41 | } 42 | 43 | .mention-type-tag { 44 | font-size: 0.7rem !important; 45 | padding: 2px 6px !important; 46 | border-radius: 4px !important; 47 | margin-left: 8px !important; 48 | font-weight: 500 !important; 49 | text-transform: uppercase !important; 50 | } 51 | 52 | .mention-type-tag.agent { 53 | background-color: #3b82f6 !important; 54 | color: white !important; 55 | } 56 | 57 | .mention-type-tag.prompt { 58 | background-color: #10b981 !important; 59 | color: white !important; 60 | } 61 | 62 | .mention-type-tag.tool { 63 | background-color: #f59e0b !important; 64 | color: white !important; 65 | } -------------------------------------------------------------------------------- /apps/rowboat/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /apps/rowboat/components/common/copy-as-json-button.tsx: -------------------------------------------------------------------------------- 1 | import { CopyButton } from "@/components/common/copy-button"; 2 | 3 | export function CopyAsJsonButton({ onCopy }: { onCopy: () => void }) { 4 | return
5 | 10 |
11 | } -------------------------------------------------------------------------------- /apps/rowboat/components/common/copy-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Button } from "@/components/ui/button"; 3 | import { CopyIcon, CheckIcon } from "lucide-react"; 4 | import { useState } from "react"; 5 | 6 | export function CopyButton({ 7 | onCopy, 8 | label, 9 | successLabel, 10 | }: { 11 | onCopy: () => void; 12 | label: string; 13 | successLabel: string; 14 | }) { 15 | const [showCopySuccess, setShowCopySuccess] = useState(false); 16 | 17 | const handleCopy = () => { 18 | onCopy(); 19 | setShowCopySuccess(true); 20 | setTimeout(() => { 21 | setShowCopySuccess(false); 22 | }, 500); 23 | } 24 | 25 | return ( 26 | 40 | ); 41 | } -------------------------------------------------------------------------------- /apps/rowboat/components/ui/dropdown.tsx: -------------------------------------------------------------------------------- 1 | import { Select, SelectItem, SelectProps } from "@heroui/react"; 2 | import { ReactNode, ChangeEvent } from "react"; 3 | 4 | export interface DropdownOption { 5 | key: string; 6 | label: string; 7 | startContent?: ReactNode; 8 | endContent?: ReactNode; 9 | } 10 | 11 | interface DropdownProps extends Omit { 12 | options: DropdownOption[]; 13 | value: string; 14 | onChange: (value: string) => void; 15 | className?: string; 16 | width?: string | number; 17 | containerClassName?: string; 18 | } 19 | 20 | export function Dropdown({ 21 | options, 22 | value, 23 | onChange, 24 | className = "", 25 | width = "100%", 26 | containerClassName = "", 27 | ...selectProps 28 | }: DropdownProps) { 29 | return ( 30 |
31 | 47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /apps/rowboat/components/ui/horizontal-divider.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | 3 | interface HorizontalDividerProps { 4 | className?: string; 5 | } 6 | 7 | export function HorizontalDivider({ className }: HorizontalDividerProps) { 8 | return ( 9 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/rowboat/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import { clsx } from "clsx"; 2 | import { InputHTMLAttributes, forwardRef } from "react"; 3 | 4 | interface InputProps extends InputHTMLAttributes { 5 | error?: string; 6 | label?: string; 7 | } 8 | 9 | export const Input = forwardRef(({ 10 | className, 11 | error, 12 | label, 13 | ...props 14 | }, ref) => { 15 | return ( 16 |
17 | {label && ( 18 | 21 | )} 22 | 35 | {error && ( 36 |

{error}

37 | )} 38 |
39 | ); 40 | }); 41 | 42 | Input.displayName = "Input"; -------------------------------------------------------------------------------- /apps/rowboat/components/ui/page-heading.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import { tokens } from "@/app/styles/design-tokens"; 3 | 4 | interface PageHeadingProps { 5 | title: string; 6 | description?: string; 7 | } 8 | 9 | export function PageHeading({ title, description }: PageHeadingProps) { 10 | return ( 11 |
12 |

18 | {title} 19 |

20 | {description && ( 21 |

27 | {description} 28 |

29 | )} 30 |
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /apps/rowboat/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 clsx from 'clsx'; 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 | -------------------------------------------------------------------------------- /apps/rowboat/components/ui/search-bar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Input } from "@/components/ui/input"; 3 | import { SearchIcon, XIcon } from "lucide-react"; 4 | import { InputHTMLAttributes } from "react"; 5 | import clsx from 'clsx'; 6 | 7 | interface SearchBarProps extends Omit, 'onChange'> { 8 | value: string; 9 | onChange: (value: string) => void; 10 | onClear?: () => void; 11 | } 12 | 13 | export function SearchBar({ 14 | value, 15 | onChange, 16 | onClear, 17 | className, 18 | ...props 19 | }: SearchBarProps) { 20 | return ( 21 |
22 | 26 | onChange(e.target.value)} 30 | className={clsx("pl-9 pr-8 bg-transparent", className)} 31 | {...props} 32 | /> 33 | {value && ( 34 | 41 | )} 42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /apps/rowboat/components/ui/section-heading.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import { tokens } from "@/app/styles/design-tokens"; 3 | 4 | interface SectionHeadingProps { 5 | children: React.ReactNode; 6 | subheading?: React.ReactNode; 7 | } 8 | 9 | export function SectionHeading({ children, subheading }: SectionHeadingProps) { 10 | return ( 11 |
12 |
18 | {children} 19 |
20 | {subheading && ( 21 |

27 | {subheading} 28 |

29 | )} 30 |
31 | ); 32 | } -------------------------------------------------------------------------------- /apps/rowboat/hooks/use-click-away.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, RefObject } from 'react'; 2 | 3 | export function useClickAway( 4 | ref: RefObject, 5 | handler: (event: MouseEvent | TouchEvent) => void 6 | ) { 7 | useEffect(() => { 8 | const listener = (event: MouseEvent | TouchEvent) => { 9 | if (!ref.current || ref.current.contains(event.target as Node)) { 10 | return; 11 | } 12 | handler(event); 13 | }; 14 | 15 | document.addEventListener('mousedown', listener); 16 | document.addEventListener('touchstart', listener); 17 | 18 | return () => { 19 | document.removeEventListener('mousedown', listener); 20 | document.removeEventListener('touchstart', listener); 21 | }; 22 | }, [ref, handler]); 23 | } -------------------------------------------------------------------------------- /apps/rowboat/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /apps/rowboat/lib/utils/date.ts: -------------------------------------------------------------------------------- 1 | export function isToday(date: Date): boolean { 2 | const today = new Date(); 3 | return date.getDate() === today.getDate() && 4 | date.getMonth() === today.getMonth() && 5 | date.getFullYear() === today.getFullYear(); 6 | } 7 | 8 | export function isThisWeek(date: Date): boolean { 9 | const now = new Date(); 10 | const weekStart = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay()); 11 | const weekEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate() + (6 - now.getDay())); 12 | return date >= weekStart && date <= weekEnd; 13 | } 14 | 15 | export function isThisMonth(date: Date): boolean { 16 | const now = new Date(); 17 | return date.getMonth() === now.getMonth() && 18 | date.getFullYear() === now.getFullYear(); 19 | } -------------------------------------------------------------------------------- /apps/rowboat/middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextFetchEvent, NextRequest, NextResponse } from "next/server"; 2 | import { withMiddlewareAuthRequired } from "@auth0/nextjs-auth0/edge"; 3 | 4 | const corsOptions = { 5 | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 6 | 'Access-Control-Allow-Headers': 'Content-Type, x-client-id, Authorization', 7 | } 8 | 9 | const auth0MiddlewareHandler = withMiddlewareAuthRequired(); 10 | 11 | export async function middleware(request: NextRequest, event: NextFetchEvent) { 12 | // Check if the request path starts with /api/ 13 | if (request.nextUrl.pathname.startsWith('/api/')) { 14 | // Handle preflighted requests 15 | if (request.method === 'OPTIONS') { 16 | const preflightHeaders = { 17 | 'Access-Control-Allow-Origin': '*', 18 | ...corsOptions, 19 | } 20 | return NextResponse.json({}, { headers: preflightHeaders }); 21 | } 22 | 23 | // Handle simple requests 24 | const response = NextResponse.next(); 25 | 26 | // Set CORS headers for all origins 27 | response.headers.set('Access-Control-Allow-Origin', '*'); 28 | 29 | Object.entries(corsOptions).forEach(([key, value]) => { 30 | response.headers.set(key, value); 31 | }) 32 | 33 | return response; 34 | } 35 | 36 | if (request.nextUrl.pathname.startsWith('/projects')) { 37 | // Skip auth check if USE_AUTH is not enabled 38 | if (process.env.USE_AUTH !== 'true') { 39 | return NextResponse.next(); 40 | } 41 | return auth0MiddlewareHandler(request, event); 42 | } 43 | 44 | return NextResponse.next(); 45 | } 46 | 47 | export const config = { 48 | matcher: ['/projects/:path*', '/api/v1/:path*', '/api/widget/v1/:path*'], 49 | }; 50 | -------------------------------------------------------------------------------- /apps/rowboat/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | output: 'standalone', 4 | }; 5 | 6 | export default nextConfig; 7 | -------------------------------------------------------------------------------- /apps/rowboat/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /apps/rowboat/public/dark-logo-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat/public/dark-logo-only.png -------------------------------------------------------------------------------- /apps/rowboat/public/dark-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat/public/dark-logo.png -------------------------------------------------------------------------------- /apps/rowboat/public/landing-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat/public/landing-bg.jpg -------------------------------------------------------------------------------- /apps/rowboat/public/logo-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat/public/logo-only.png -------------------------------------------------------------------------------- /apps/rowboat/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat/public/logo.png -------------------------------------------------------------------------------- /apps/rowboat/scripts.Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker.io/docker/dockerfile:1 2 | 3 | FROM node:18-alpine AS base 4 | 5 | # Install dependencies only when needed 6 | FROM base AS deps 7 | # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. 8 | RUN apk add --no-cache libc6-compat 9 | WORKDIR /app 10 | 11 | # Install dependencies based on the preferred package manager 12 | COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./ 13 | RUN \ 14 | if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ 15 | elif [ -f package-lock.json ]; then npm ci; \ 16 | elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ 17 | else echo "Lockfile not found." && exit 1; \ 18 | fi 19 | 20 | 21 | # Rebuild the source code only when needed 22 | FROM base AS builder 23 | WORKDIR /app 24 | COPY --from=deps /app/node_modules ./node_modules 25 | COPY . . 26 | 27 | # Next.js collects completely anonymous telemetry data about general usage. 28 | # Learn more here: https://nextjs.org/telemetry 29 | # Uncomment the following line in case you want to disable telemetry during the build. 30 | # ENV NEXT_TELEMETRY_DISABLED=1 31 | 32 | RUN \ 33 | if [ -f yarn.lock ]; then yarn run build; \ 34 | elif [ -f package-lock.json ]; then npm run build; \ 35 | elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ 36 | else echo "Lockfile not found." && exit 1; \ 37 | fi 38 | 39 | ENV NODE_ENV=production 40 | # Uncomment the following line in case you want to disable telemetry during runtime. 41 | # ENV NEXT_TELEMETRY_DISABLED=1 42 | 43 | RUN addgroup --system --gid 1001 nodejs 44 | RUN adduser --system --uid 1001 nextjs 45 | 46 | USER nextjs -------------------------------------------------------------------------------- /apps/rowboat/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /apps/rowboat/types/project_types.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { MCPServer } from "@/app/lib/types/types"; 3 | 4 | export const Project = z.object({ 5 | _id: z.string().uuid(), 6 | name: z.string(), 7 | createdAt: z.string().datetime(), 8 | lastUpdatedAt: z.string().datetime(), 9 | createdByUserId: z.string(), 10 | secret: z.string(), 11 | chatClientId: z.string(), 12 | webhookUrl: z.string().optional(), 13 | publishedWorkflowId: z.string().optional(), 14 | nextWorkflowNumber: z.number().optional(), 15 | testRunCounter: z.number().default(0), 16 | mcpServers: z.array(MCPServer).optional(), 17 | }); 18 | 19 | export const ProjectMember = z.object({ 20 | userId: z.string(), 21 | projectId: z.string(), 22 | createdAt: z.string().datetime(), 23 | lastUpdatedAt: z.string().datetime(), 24 | }); 25 | 26 | export const ApiKey = z.object({ 27 | projectId: z.string(), 28 | key: z.string(), 29 | createdAt: z.string().datetime(), 30 | lastUsedAt: z.string().datetime().optional(), 31 | }); -------------------------------------------------------------------------------- /apps/rowboat_agents/.dockerignore: -------------------------------------------------------------------------------- 1 | .env 2 | .env* 3 | __pycache__/ 4 | venv/ 5 | .venv/ -------------------------------------------------------------------------------- /apps/rowboat_agents/.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY= 2 | API_KEY=test -------------------------------------------------------------------------------- /apps/rowboat_agents/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all __pycache__ directories 2 | /__pycache__/ 3 | **/__pycache__/ 4 | **/**/__pycache__/ 5 | **/**/__pycache__/** 6 | 7 | # Ignore all .pyc files 8 | *.pyc 9 | **/*.pyc 10 | **/**/*.pyc 11 | 12 | # Ignore .DS_Store files 13 | .DS_Store 14 | **/.DS_Store 15 | **/**/.DS_Store 16 | 17 | # Ignore VSCode files 18 | .vscode/* 19 | **/.vscode/* 20 | **/**/.vscode/* 21 | 22 | # Environment files 23 | .env 24 | .env.local 25 | 26 | # Api keys 27 | **/*api_keys* 28 | 29 | # Ignore .out, .lock, .log files 30 | *.out 31 | **/*.out 32 | **/**/*.out 33 | *.log 34 | **/*.log 35 | **/**/*.log 36 | 37 | # Ignore tmp.json files 38 | **/*tmp.json 39 | **/**/*tmp.json 40 | **/**/**/*tmp.json 41 | -------------------------------------------------------------------------------- /apps/rowboat_agents/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use official Python runtime as base image 2 | FROM python:3.12-slim 3 | 4 | # Set working directory in container 5 | WORKDIR /app 6 | 7 | # Install poetry 8 | RUN pip install poetry 9 | 10 | # Copy poetry files 11 | COPY pyproject.toml poetry.lock ./ 12 | 13 | # Configure poetry to not create virtual environment in container 14 | RUN poetry config virtualenvs.create false 15 | 16 | # Install dependencies 17 | RUN poetry install --no-interaction --no-ansi 18 | 19 | # Copy project files 20 | COPY . . 21 | 22 | # Set environment variables 23 | ENV QUART_APP=src.app.main 24 | ENV PYTHONUNBUFFERED=1 25 | ENV PYTHONPATH=/app 26 | 27 | # Command to run Flask development server 28 | CMD ["quart", "run", "--host=0.0.0.0", "--port=3001"] 29 | -------------------------------------------------------------------------------- /apps/rowboat_agents/NOTICE.md: -------------------------------------------------------------------------------- 1 | # Attribution to OpenAI Swarm 2 | 3 | - The Rowboat Agents framework has been built upon [OpenAI Swarm](https://github.com/openai/swarm), with modifications and improvements. 4 | - The original OpenAI Swarm is available under the [MIT license](https://github.com/openai/swarm/blob/main/LICENSE) as of the time of this writing. It is an experimental sample framework at the time of this writing. 5 | 6 | ### OpenAI Swarm License 7 | Below is the license text from OpenAI Swarm, as required by the MIT license: 8 | 9 | ``` 10 | MIT License 11 | 12 | Copyright (c) 2024 OpenAI 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in all 22 | copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | SOFTWARE. 31 | ``` 32 | 33 | # High-level changes 34 | These are the high-level changes made to OpenAI Swarm to build in RowBoat's custom implementation: 35 | - Added localized agent-level history 36 | - Added parent-child agent relationships with parents' history containing children's history 37 | - Added usage tracking of tokens per llm 38 | - Added turn-level error handling 39 | - Added converstaion turn limits 40 | - Removed streaming support as RowBoat Agents does not support streaming currently 41 | - Modified the `Agent` and `Response` classes to be more comprehensive 42 | 43 | The above is not an exhaustive list. -------------------------------------------------------------------------------- /apps/rowboat_agents/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat_agents/__init__.py -------------------------------------------------------------------------------- /apps/rowboat_agents/configs/default_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "localize_history": true, 3 | "return_diff_messages": true, 4 | "start_turn_with_start_agent": false, 5 | "children_aware_of_parent": false, 6 | "parent_has_child_history": true, 7 | "max_messages_per_turn": 20, 8 | "max_messages_per_error_escalation_turn": 15, 9 | "escalate_errors": true, 10 | "max_overall_turns": 25 11 | } -------------------------------------------------------------------------------- /apps/rowboat_agents/src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat_agents/src/__init__.py -------------------------------------------------------------------------------- /apps/rowboat_agents/src/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat_agents/src/app/__init__.py -------------------------------------------------------------------------------- /apps/rowboat_agents/src/graph/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat_agents/src/graph/__init__.py -------------------------------------------------------------------------------- /apps/rowboat_agents/src/graph/helpers/access.py: -------------------------------------------------------------------------------- 1 | from src.utils.common import common_logger 2 | logger = common_logger 3 | 4 | def get_external_tools(tool_configs): 5 | logger.debug("Getting external tools") 6 | tools = [tool["name"] for tool in tool_configs] 7 | logger.debug(f"Found {len(tools)} external tools") 8 | return tools 9 | 10 | def get_agent_by_name(agent_name, agents): 11 | agent = next((a for a in agents if getattr(a, "name", None) == agent_name), None) 12 | if not agent: 13 | logger.error(f"Agent with name {agent_name} not found") 14 | raise ValueError(f"Agent with name {agent_name} not found") 15 | return agent 16 | 17 | def get_agent_config_by_name(agent_name, agent_configs): 18 | agent_config = next((ac for ac in agent_configs if ac.get("name") == agent_name), None) 19 | if not agent_config: 20 | logger.error(f"Agent config with name {agent_name} not found") 21 | raise ValueError(f"Agent config with name {agent_name} not found") 22 | return agent_config 23 | 24 | def pop_agent_config_by_type(agent_configs, agent_type): 25 | agent_config = next((ac for ac in agent_configs if ac.get("type") == agent_type), None) 26 | if agent_config: 27 | agent_configs.remove(agent_config) 28 | return agent_config, agent_configs 29 | 30 | def get_agent_by_type(agents, agent_type): 31 | return next((a for a in agents if a.type == agent_type), None) 32 | 33 | def get_prompt_by_type(prompt_configs, prompt_type): 34 | return next((pc.get("prompt") for pc in prompt_configs if pc.get("type") == prompt_type), None) 35 | 36 | def get_agent_data_by_name(agent_name, agent_data): 37 | for data in agent_data: 38 | name = data.get("name", "") 39 | if name == agent_name: 40 | return data 41 | 42 | return None 43 | 44 | def get_tool_config_by_name(tool_configs, tool_name): 45 | return next((tc for tc in tool_configs if tc.get("name", "") == tool_name), None) 46 | 47 | def get_tool_config_by_type(tool_configs, tool_type): 48 | return next((tc for tc in tool_configs if tc.get("type", "") == tool_type), None) 49 | -------------------------------------------------------------------------------- /apps/rowboat_agents/src/graph/helpers/control.py: -------------------------------------------------------------------------------- 1 | from .access import get_agent_config_by_name, get_agent_data_by_name 2 | from src.graph.types import ControlType 3 | 4 | def get_last_agent_name(state, agent_configs, start_agent_name, msg_type, latest_assistant_msg, start_turn_with_start_agent): 5 | default_last_agent_name = state.get("last_agent_name", '') 6 | last_agent_config = get_agent_config_by_name(default_last_agent_name, agent_configs) 7 | specific_agent_data = get_agent_data_by_name(default_last_agent_name, state.get("agent_data", [])) 8 | 9 | # Overrides for special cases 10 | print("Setting agent control based on last agent and control type") 11 | if msg_type == "tool": 12 | last_agent_name = default_last_agent_name 13 | assert last_agent_name == latest_assistant_msg.get("sender", ''), "Last agent name does not match sender of latest assistant message during tool call handling" 14 | 15 | elif start_turn_with_start_agent: 16 | last_agent_name = start_agent_name 17 | 18 | else: 19 | control_type = last_agent_config.get("controlType", ControlType.RETAIN.value) 20 | if control_type == ControlType.PARENT_AGENT.value: 21 | last_agent_name = specific_agent_data.get("most_recent_parent_name", None) if specific_agent_data else None 22 | if not last_agent_name: 23 | print("Most recent parent is empty, defaulting to same agent instead") 24 | last_agent_name = default_last_agent_name 25 | elif control_type == ControlType.START_AGENT.value: 26 | last_agent_name = start_agent_name 27 | else: 28 | last_agent_name = default_last_agent_name 29 | 30 | if default_last_agent_name != last_agent_name: 31 | print(f"Last agent name changed from {default_last_agent_name} to {last_agent_name} due to control settings") 32 | 33 | return last_agent_name 34 | 35 | 36 | def get_latest_assistant_msg(messages): 37 | # Find the latest message with role assistant 38 | for i in range(len(messages)-1, -1, -1): 39 | if messages[i].get("role") == "assistant": 40 | return messages[i] 41 | return None 42 | 43 | def get_latest_non_assistant_messages(messages): 44 | # Find all messages after the last assistant message 45 | for i in range(len(messages)-1, -1, -1): 46 | if messages[i].get("role") == "assistant": 47 | return messages[i+1:] 48 | return messages -------------------------------------------------------------------------------- /apps/rowboat_agents/src/graph/helpers/state.py: -------------------------------------------------------------------------------- 1 | from src.utils.common import common_logger 2 | logger = common_logger 3 | from .access import get_agent_data_by_name 4 | 5 | def reset_current_turn(messages): 6 | # Set all messages' current_turn to False 7 | for msg in messages: 8 | msg["current_turn"] = False 9 | 10 | # Find most recent user message 11 | messages[-1]["current_turn"] = True 12 | 13 | return messages 14 | 15 | def reset_current_turn_agent_history(agent_data, agent_names): 16 | for name in agent_names: 17 | data = get_agent_data_by_name(name, agent_data) 18 | if data: 19 | for msg in data["history"]: 20 | msg["current_turn"] = False 21 | return agent_data 22 | 23 | def add_recent_messages_to_history(recent_messages, last_agent_name, agent_data, messages, parent_has_child_history): 24 | last_msg = messages[-1] 25 | specific_agent_data = get_agent_data_by_name(last_agent_name, agent_data) 26 | if specific_agent_data: 27 | specific_agent_data["history"].extend(recent_messages) 28 | if parent_has_child_history: 29 | current_agent_data = specific_agent_data 30 | while current_agent_data.get("most_recent_parent_name"): 31 | parent_name = current_agent_data.get("most_recent_parent_name") 32 | parent_agent_data = get_agent_data_by_name(parent_name, agent_data) 33 | if parent_agent_data: 34 | parent_agent_data["history"].extend(recent_messages) 35 | current_agent_data = parent_agent_data 36 | else: 37 | logger.error(f"Parent agent data for {current_agent_data['name']} not found in agent_data") 38 | raise ValueError(f"Parent agent data for {current_agent_data['name']} not found in agent_data") 39 | else: 40 | agent_data.append({ 41 | "name": last_agent_name, 42 | "history": [last_msg] 43 | }) 44 | 45 | return agent_data 46 | 47 | def construct_state_from_response(response, agents): 48 | agent_data = [] 49 | for agent in agents: 50 | agent_data.append({ 51 | "name": agent.name, 52 | "instructions": agent.instructions 53 | }) 54 | 55 | state = { 56 | "last_agent_name": response.agent.name, 57 | "agent_data": agent_data 58 | } 59 | 60 | return state -------------------------------------------------------------------------------- /apps/rowboat_agents/src/graph/helpers/transfer.py: -------------------------------------------------------------------------------- 1 | from src.utils.common import common_logger 2 | logger = common_logger 3 | 4 | def create_transfer_function_to_agent(agent): 5 | agent_name = agent.name 6 | 7 | fn_spec = { 8 | "name": f"transfer_to_{agent_name.lower().replace(' ', '_')}", 9 | "description": f"Function to transfer the chat to {agent_name}.", 10 | "return_value": agent 11 | } 12 | 13 | def generated_function(*args, **kwargs): 14 | logger.info(f"Transferring chat to {agent_name}") 15 | return fn_spec.get('return_value', None) 16 | 17 | generated_function.__name__ = fn_spec['name'] 18 | generated_function.__doc__ = fn_spec.get('description', '') 19 | 20 | return generated_function 21 | 22 | def create_transfer_function_to_parent_agent(parent_agent, children_aware_of_parent, transfer_functions): 23 | if children_aware_of_parent: 24 | name = f"{transfer_functions[parent_agent.name].__name__}_from_child" 25 | description = f"Function to transfer the chat to your parent agent: {parent_agent.name}." 26 | else: 27 | name = "give_up_chat_control" 28 | description = "Function to give up control of the chat when you are unable to handle it." 29 | 30 | 31 | fn_spec = { 32 | "name": name, 33 | "description": description, 34 | "return_value": parent_agent 35 | } 36 | 37 | def generated_function(*args, **kwargs): 38 | logger.info(f"Transferring chat to parent agent: {parent_agent.name}") 39 | return fn_spec.get('return_value', None) 40 | 41 | generated_function.__name__ = fn_spec['name'] 42 | generated_function.__doc__ = fn_spec.get('description', '') 43 | 44 | return generated_function -------------------------------------------------------------------------------- /apps/rowboat_agents/src/graph/tools.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | 4 | def tool_raise_error(error_message): 5 | print(f"Raising error: {error_message}") 6 | raise ValueError(f"Raising error: {error_message}") 7 | 8 | def respond_to_tool_raise_error(tool_calls, mock=False): 9 | error_message = json.loads(tool_calls[0]["function"]["arguments"]).get("error_message", "") 10 | return _create_tool_response(tool_calls, tool_raise_error(error_message)) 11 | 12 | def tool_close_chat(error_message): 13 | print(f"Closing chat: {error_message}") 14 | raise ValueError(f"Closing chat: {error_message}") 15 | 16 | def respond_to_tool_close_chat(tool_calls, mock=False): 17 | error_message = json.loads(tool_calls[0]["function"]["arguments"]).get("error_message", "") 18 | return _create_tool_response(tool_calls, tool_close_chat(error_message)) 19 | 20 | def _create_tool_response(tool_calls, content, mock=False): 21 | """ 22 | Creates a standardized tool response format. 23 | """ 24 | return { 25 | "role": "tool", 26 | "content": content, 27 | "tool_call_id": tool_calls[0]["id"], 28 | "name": tool_calls[0]["function"]["name"] 29 | } 30 | 31 | def create_error_tool_call(error_message): 32 | error_message_tool_call = { 33 | "role": "assistant", 34 | "sender": "system", 35 | "tool_calls": [ 36 | { 37 | "function": { 38 | "name": "raise_error", 39 | "arguments": "{\"error_message\":\"" + error_message + "\"}" 40 | }, 41 | "id": "call_" + ''.join(random.choices('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=24)), 42 | "type": "function" 43 | } 44 | ] 45 | } 46 | return error_message_tool_call -------------------------------------------------------------------------------- /apps/rowboat_agents/src/graph/types.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | class AgentRole(Enum): 3 | ESCALATION = "escalation" 4 | POST_PROCESSING = "post_process" 5 | GUARDRAILS = "guardrails" 6 | 7 | class outputVisibility(Enum): 8 | EXTERNAL = "user_facing" 9 | INTERNAL = "internal" 10 | 11 | class ResponseType(Enum): 12 | INTERNAL = "internal" 13 | EXTERNAL = "external" 14 | 15 | class ControlType(Enum): 16 | RETAIN = "retain" 17 | PARENT_AGENT = "relinquish_to_parent" 18 | START_AGENT = "start_agent" 19 | 20 | class PromptType(Enum): 21 | STYLE = "style_prompt" 22 | GREETING = "greeting" 23 | 24 | class ErrorType(Enum): 25 | FATAL = "fatal" 26 | ESCALATE = "escalate" -------------------------------------------------------------------------------- /apps/rowboat_agents/src/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat_agents/src/utils/__init__.py -------------------------------------------------------------------------------- /apps/rowboat_agents/src/utils/client.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from openai import AsyncOpenAI, OpenAI 4 | import dotenv 5 | dotenv.load_dotenv() 6 | 7 | PROVIDER_BASE_URL = os.getenv('PROVIDER_BASE_URL', '') 8 | PROVIDER_API_KEY = os.getenv('PROVIDER_API_KEY') 9 | PROVIDER_DEFAULT_MODEL = os.getenv('PROVIDER_DEFAULT_MODEL') 10 | 11 | client = None 12 | if not PROVIDER_API_KEY: 13 | PROVIDER_API_KEY = os.getenv('OPENAI_API_KEY') 14 | 15 | if not PROVIDER_API_KEY: 16 | raise(ValueError("No LLM Provider API key found")) 17 | 18 | if not PROVIDER_DEFAULT_MODEL: 19 | PROVIDER_DEFAULT_MODEL = 'gpt-4.1' 20 | 21 | if PROVIDER_BASE_URL: 22 | print(f"Using provider {PROVIDER_BASE_URL}") 23 | client = AsyncOpenAI(base_url=PROVIDER_BASE_URL, api_key=PROVIDER_API_KEY) 24 | else: 25 | print("No provider base URL configured, using OpenAI directly") 26 | 27 | completions_client = None 28 | if PROVIDER_BASE_URL: 29 | print(f"Using provider {PROVIDER_BASE_URL} for completions") 30 | completions_client = OpenAI( 31 | base_url=PROVIDER_BASE_URL, 32 | api_key=PROVIDER_API_KEY 33 | ) 34 | else: 35 | print(f"Using OpenAI directly for completions") 36 | completions_client = OpenAI( 37 | api_key=PROVIDER_API_KEY 38 | ) -------------------------------------------------------------------------------- /apps/rowboat_agents/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/apps/rowboat_agents/tests/__init__.py -------------------------------------------------------------------------------- /apps/rowboat_agents/tests/app_client.py: -------------------------------------------------------------------------------- 1 | from src.utils.common import common_logger, read_json_from_file 2 | logger = common_logger 3 | logger.info("Running swarm_flask_client.py") 4 | import requests 5 | 6 | if __name__ == "__main__": 7 | import argparse 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument('--sample_request', type=str, required=True, help='Sample request JSON file name under tests/sample_requests/') 10 | parser.add_argument('--api_key', type=str, required=True, help='API key to use for authentication') 11 | parser.add_argument('--host', type=str, required=False, help='Host to use for the request', default='http://localhost:4040') 12 | args = parser.parse_args() 13 | 14 | request = read_json_from_file(f"./tests/sample_requests/{args.sample_request}").get("lastRequest", {}) 15 | print("Sending request...") 16 | response = requests.post( 17 | f"{args.host}/chat", 18 | json=request, 19 | headers={'Authorization': f'Bearer {args.api_key}'} 20 | ).json() 21 | print("Output: ") 22 | print(response) -------------------------------------------------------------------------------- /assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/assets/banner.png -------------------------------------------------------------------------------- /assets/mcp-import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/assets/mcp-import.png -------------------------------------------------------------------------------- /assets/rb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/assets/rb-logo.png -------------------------------------------------------------------------------- /assets/ui_revamp_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rowboatlabs/rowboat/b5ff40af8e22d0d78a87f5376dea25d836c6f324/assets/ui_revamp_screenshot.png -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ensure data dirs exist 4 | mkdir -p data/uploads 5 | mkdir -p data/qdrant 6 | mkdir -p data/mongo 7 | 8 | # set the following environment variables 9 | export USE_RAG=true 10 | export USE_RAG_UPLOADS=true 11 | 12 | # Start with the base command and profile flags 13 | CMD="docker-compose" 14 | CMD="$CMD --profile setup_qdrant" 15 | CMD="$CMD --profile qdrant" 16 | CMD="$CMD --profile rag_text_worker" 17 | CMD="$CMD --profile rag_files_worker" 18 | 19 | # enable rag urls worker 20 | if [ "$USE_RAG_SCRAPING" = "true" ]; then 21 | CMD="$CMD --profile rag_urls_worker" 22 | fi 23 | 24 | # Add more mappings as needed 25 | # if [ "$SOME_OTHER_ENV" = "true" ]; then 26 | # CMD="$CMD --profile some_other_profile" 27 | # fi 28 | 29 | # Add the up and build flags at the end 30 | CMD="$CMD up --build" 31 | 32 | echo "Running: $CMD" 33 | exec $CMD 34 | --------------------------------------------------------------------------------